diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ac9a2e7521..ff261bad78 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} USER vscode -RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash +RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash ENV PATH=/home/vscode/.rye/shims:$PATH -RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc +RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bbeb30b148..c17fdc169f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,6 +24,9 @@ } } } + }, + "features": { + "ghcr.io/devcontainers/features/node:1": {} } // Features to add to the dev container. More info: https://containers.dev/features. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de70348b9c..527e036a0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,51 +2,124 @@ name: CI on: push: branches: - - main + - '**' + - '!integrated/**' + - '!stl-preview-head/**' + - '!stl-preview-base/**' + - '!generated' + - '!codegen/**' + - 'codegen/stl/**' pull_request: - branches: - - main + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: + timeout-minutes: 10 name: lint - runs-on: ubuntu-latest - if: github.repository == 'openai/openai-python' - + runs-on: ${{ github.repository == 'stainless-sdks/openai-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Install Rye - run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_VERSION: '0.35.0' - RYE_INSTALL_OPTION: '--yes' + - name: Set up Rye + uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 + with: + version: '0.44.0' + enable-cache: true - name: Install dependencies run: rye sync --all-features - name: Run lints run: ./scripts/lint - test: - name: test - runs-on: ubuntu-latest - if: github.repository == 'openai/openai-python' + build: + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') + timeout-minutes: 10 + name: build + permissions: + contents: read + id-token: write + runs-on: ${{ github.repository == 'stainless-sdks/openai-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Install Rye - run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH + - name: Set up Rye + uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 + with: + version: '0.44.0' + enable-cache: true + + - name: Install dependencies + run: rye sync --all-features + + - name: Run build + run: rye build + + - name: Get GitHub OIDC Token + if: |- + github.repository == 'stainless-sdks/openai-python' && + !startsWith(github.ref, 'refs/heads/stl/') + id: github-oidc + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + if: |- + github.repository == 'stainless-sdks/openai-python' && + !startsWith(github.ref, 'refs/heads/stl/') env: - RYE_VERSION: '0.35.0' - RYE_INSTALL_OPTION: '--yes' + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh + + test: + timeout-minutes: 10 + name: test + runs-on: ${{ github.repository == 'stainless-sdks/openai-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up Rye + uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 + with: + version: '0.44.0' + enable-cache: true - name: Bootstrap run: ./scripts/bootstrap - name: Run tests run: ./scripts/test + + examples: + timeout-minutes: 10 + name: examples + runs-on: ${{ github.repository == 'stainless-sdks/openai-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.repository == 'openai/openai-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up Rye + uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 + with: + version: '0.44.0' + enable-cache: true + - name: Install dependencies + run: | + rye sync --all-features + + - env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + rye run python examples/demo.py + - env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + rye run python examples/async_demo.py diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml index 2a97049033..9d65ef4e8e 100644 --- a/.github/workflows/create-releases.yml +++ b/.github/workflows/create-releases.yml @@ -1,7 +1,5 @@ name: Create releases on: - schedule: - - cron: '0 5 * * *' # every day at 5am UTC push: branches: - main @@ -12,28 +10,69 @@ jobs: if: github.ref == 'refs/heads/main' && github.repository == 'openai/openai-python' runs-on: ubuntu-latest environment: publish + outputs: + releases_created: ${{ steps.release.outputs.releases_created }} + permissions: + contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - uses: stainless-api/trigger-release-please@v1 + - uses: stainless-api/trigger-release-please@bb6677c5a04578eec1ccfd9e1913b5b78ed64c61 # v1.4.0 id: release with: repo: ${{ github.event.repository.full_name }} stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} - - name: Install Rye - if: ${{ steps.release.outputs.releases_created }} + build: + name: build + needs: release + if: ${{ needs.release.outputs.releases_created == 'true' }} + runs-on: ubuntu-latest + # Build distributions without OIDC access so package build code cannot mint + # a PyPI publishing token. The publish job handles only the upload. + permissions: + contents: read + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up Rye + uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 + with: + version: '0.44.0' + enable-cache: true + + - name: Build package run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_VERSION: '0.35.0' - RYE_INSTALL_OPTION: '--yes' + mkdir -p dist + rye build --clean + + - name: Upload package distributions + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: python-package-distributions + path: dist/ + if-no-files-found: error + retention-days: 1 + + publish: + name: publish + needs: build + runs-on: ubuntu-latest + environment: publish + # PyPI Trusted Publishing requires id-token: write. Keep it scoped to this + # minimal upload-only job rather than the build job. + permissions: + contents: read + id-token: write + + steps: + - name: Download package distributions + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: python-package-distributions + path: dist/ - name: Publish to PyPI - if: ${{ steps.release.outputs.releases_created }} - run: | - bash ./bin/publish-pypi - env: - PYPI_TOKEN: ${{ secrets.OPENAI_PYPI_TOKEN || secrets.PYPI_TOKEN }} + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 diff --git a/.github/workflows/detect-breaking-changes.yml b/.github/workflows/detect-breaking-changes.yml new file mode 100644 index 0000000000..641129f010 --- /dev/null +++ b/.github/workflows/detect-breaking-changes.yml @@ -0,0 +1,83 @@ +name: CI +on: + pull_request: + branches: + - main + - next + +jobs: + detect_breaking_changes: + runs-on: 'ubuntu-latest' + name: detect-breaking-changes + if: github.repository == 'openai/openai-python' + steps: + - name: Calculate fetch-depth + run: | + echo "FETCH_DEPTH=$(expr ${{ github.event.pull_request.commits }} + 1)" >> $GITHUB_ENV + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + # Ensure we can check out the pull request base in the script below. + fetch-depth: ${{ env.FETCH_DEPTH }} + + - name: Set up Rye + uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 + with: + version: '0.44.0' + enable-cache: true + - name: Install dependencies + run: | + rye sync --all-features + - name: Detect removed symbols + run: | + rye run python scripts/detect-breaking-changes.py "${{ github.event.pull_request.base.sha }}" + + - name: Detect breaking changes + run: | + test -f ./scripts/detect-breaking-changes || { echo "Missing scripts/detect-breaking-changes"; exit 1; } + ./scripts/detect-breaking-changes ${{ github.event.pull_request.base.sha }} + + agents_sdk: + runs-on: 'ubuntu-latest' + name: Detect Agents SDK regressions + if: github.repository == 'openai/openai-python' + steps: + # Setup this sdk + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + path: openai-python + + - name: Set up Rye + uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 + with: + version: '0.44.0' + enable-cache: true + working-directory: openai-python + + - name: Install dependencies + working-directory: openai-python + run: | + rye sync --all-features + + # Setup the agents lib + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + repository: openai/openai-agents-python + path: openai-agents-python + + - name: Setup uv + uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5 + with: + enable-cache: true + + - name: Link to local SDK + working-directory: openai-agents-python + run: uv add ../openai-python + + - name: Install dependencies + working-directory: openai-agents-python + run: make sync + + - name: Run integration type checks + working-directory: openai-agents-python + run: make mypy diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 44027a3c4c..d23cd66942 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -5,23 +5,55 @@ on: workflow_dispatch: jobs: - publish: - name: publish + build: + name: build + if: github.ref == 'refs/heads/main' && github.repository == 'openai/openai-python' runs-on: ubuntu-latest + # Build distributions without OIDC access so package build code cannot mint + # a PyPI publishing token. The publish job handles only the upload. + permissions: + contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up Rye + uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 + with: + version: '0.44.0' + enable-cache: true - - name: Install Rye + - name: Build package run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_VERSION: '0.35.0' - RYE_INSTALL_OPTION: '--yes' + mkdir -p dist + rye build --clean + + - name: Upload package distributions + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: python-package-distributions + path: dist/ + if-no-files-found: error + retention-days: 1 + + publish: + name: publish + needs: build + if: github.ref == 'refs/heads/main' && github.repository == 'openai/openai-python' + runs-on: ubuntu-latest + environment: publish + # PyPI Trusted Publishing requires id-token: write. Keep it scoped to this + # minimal upload-only job rather than the build job. + permissions: + contents: read + id-token: write + + steps: + - name: Download package distributions + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: python-package-distributions + path: dist/ - name: Publish to PyPI - run: | - bash ./bin/publish-pypi - env: - PYPI_TOKEN: ${{ secrets.OPENAI_PYPI_TOKEN || secrets.PYPI_TOKEN }} + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml deleted file mode 100644 index e078964a6f..0000000000 --- a/.github/workflows/release-doctor.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Release Doctor -on: - push: - branches: - - main - workflow_dispatch: - -jobs: - release_doctor: - name: release doctor - runs-on: ubuntu-latest - environment: publish - if: github.repository == 'openai/openai-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') - - steps: - - uses: actions/checkout@v4 - - - name: Check release environment - run: | - bash ./bin/check-release-environment - env: - STAINLESS_API_KEY: ${{ secrets.STAINLESS_API_KEY }} - PYPI_TOKEN: ${{ secrets.OPENAI_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index 8779740800..3a7a575390 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .prism.log -.vscode +.stdy.log _dev __pycache__ @@ -14,3 +14,7 @@ dist .envrc codegen.log Brewfile.lock.json + +.DS_Store + +examples/*.mp3 diff --git a/.inline-snapshot/external/038a5c69c34c9513021b52aa61661f4f5bea321c0aac9e164f2ed3e409aebc48.bin b/.inline-snapshot/external/038a5c69c34c9513021b52aa61661f4f5bea321c0aac9e164f2ed3e409aebc48.bin deleted file mode 100644 index a5a0aeb4c0..0000000000 --- a/.inline-snapshot/external/038a5c69c34c9513021b52aa61661f4f5bea321c0aac9e164f2ed3e409aebc48.bin +++ /dev/null @@ -1,102 +0,0 @@ -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"I'm"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" unable"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" provide"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" real"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"-time"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" updates"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" including"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" current"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" information"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" For"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" the"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" latest"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" San"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" I"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" recommend"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" checking"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" reliable"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" website"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" or"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" app"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" such"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" as"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" the"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" Weather"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" Channel"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" BBC"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" Weather"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" or"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" local"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" San"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" news"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" station"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - -data: {"id":"chatcmpl-9tXjg9DdaOfymTPDrSLfxslQEH0C2","object":"chat.completion.chunk","created":1723024748,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":47,"total_tokens":61}} - -data: [DONE] - diff --git a/.inline-snapshot/external/0898f3d1651e3244eeb3651d012625e557f9e6763bd8c03bcd88e220149a3367.bin b/.inline-snapshot/external/0898f3d1651e3244eeb3651d012625e557f9e6763bd8c03bcd88e220149a3367.bin deleted file mode 100644 index 4b42ada8d2..0000000000 --- a/.inline-snapshot/external/0898f3d1651e3244eeb3651d012625e557f9e6763bd8c03bcd88e220149a3367.bin +++ /dev/null @@ -1,224 +0,0 @@ -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"location"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" CA"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"forecast"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"_date"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"202"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"3"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"11"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"-"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"02"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"weather"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"current"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"N"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"/A"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"high"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"N"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"/A"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"low"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"N"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"/A"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"N"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"/A"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"humidity"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"N"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"/A"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"wind"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"_speed"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"N"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"/A"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"note"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"Please"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" check"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" reliable"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" service"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" for"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" the"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" most"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" current"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" information"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":".\"\n"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" }"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - -data: {"id":"chatcmpl-9tXjrL8ZwahfIfWjgwcnHRzZrzVL4","object":"chat.completion.chunk","created":1723024759,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":19,"completion_tokens":108,"total_tokens":127}} - -data: [DONE] - diff --git a/.inline-snapshot/external/0a00cd46c61030ff70241d432dcf4aa41fc428937c231e17c3460f3237f6a018.bin b/.inline-snapshot/external/0a00cd46c61030ff70241d432dcf4aa41fc428937c231e17c3460f3237f6a018.bin deleted file mode 100644 index 73de9d6cbc..0000000000 --- a/.inline-snapshot/external/0a00cd46c61030ff70241d432dcf4aa41fc428937c231e17c3460f3237f6a018.bin +++ /dev/null @@ -1,28 +0,0 @@ -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":{"content":null,"refusal":[]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":"I'm"},"logprobs":{"content":null,"refusal":[{"token":"I'm","logprob":-0.0016157961,"bytes":[73,39,109],"top_logprobs":[]}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" sorry"},"logprobs":{"content":null,"refusal":[{"token":" sorry","logprob":-0.78663874,"bytes":[32,115,111,114,114,121],"top_logprobs":[]}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":","},"logprobs":{"content":null,"refusal":[{"token":",","logprob":-0.0000779144,"bytes":[44],"top_logprobs":[]}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" I"},"logprobs":{"content":null,"refusal":[{"token":" I","logprob":-0.5234622,"bytes":[32,73],"top_logprobs":[]}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" cannot"},"logprobs":{"content":null,"refusal":[{"token":" cannot","logprob":-0.52499557,"bytes":[32,99,97,110,110,111,116],"top_logprobs":[]}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" assist"},"logprobs":{"content":null,"refusal":[{"token":" assist","logprob":-0.015198289,"bytes":[32,97,115,115,105,115,116],"top_logprobs":[]}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" with"},"logprobs":{"content":null,"refusal":[{"token":" with","logprob":-0.00071648485,"bytes":[32,119,105,116,104],"top_logprobs":[]}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" that"},"logprobs":{"content":null,"refusal":[{"token":" that","logprob":-0.008114983,"bytes":[32,116,104,97,116],"top_logprobs":[]}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" request"},"logprobs":{"content":null,"refusal":[{"token":" request","logprob":-0.0013802331,"bytes":[32,114,101,113,117,101,115,116],"top_logprobs":[]}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":"."},"logprobs":{"content":null,"refusal":[{"token":".","logprob":-3.4121115e-6,"bytes":[46],"top_logprobs":[]}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - -data: {"id":"chatcmpl-9tXjmhJIrvp7TBeVxzzxmx8pp2UGY","object":"chat.completion.chunk","created":1723024754,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":17,"completion_tokens":11,"total_tokens":28}} - -data: [DONE] - diff --git a/.inline-snapshot/external/15ae68f793c7b390fc8af9e21a6aea6d0356b63140161758a2e576d4e3092cfa.bin b/.inline-snapshot/external/15ae68f793c7b390fc8af9e21a6aea6d0356b63140161758a2e576d4e3092cfa.bin deleted file mode 100644 index 1bcca1fceb..0000000000 --- a/.inline-snapshot/external/15ae68f793c7b390fc8af9e21a6aea6d0356b63140161758a2e576d4e3092cfa.bin +++ /dev/null @@ -1,36 +0,0 @@ -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"68"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - -data: {"id":"chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD","object":"chat.completion.chunk","created":1723024750,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":17,"completion_tokens":14,"total_tokens":31}} - -data: [DONE] - diff --git a/.inline-snapshot/external/173417d553406f034f643e5db3f8d591fb691ebac56f5ae39a22cc7d455c5353.bin b/.inline-snapshot/external/173417d553406f034f643e5db3f8d591fb691ebac56f5ae39a22cc7d455c5353.bin new file mode 100644 index 0000000000..49c6dce93f --- /dev/null +++ b/.inline-snapshot/external/173417d553406f034f643e5db3f8d591fb691ebac56f5ae39a22cc7d455c5353.bin @@ -0,0 +1,28 @@ +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"I'm"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" sorry"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":","},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" I"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" can't"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" assist"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" with"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" that"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" request"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"."},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + +data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":11,"total_tokens":90,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/2018feb66ae13fcf5333d61b95849decc68d3f63bd38172889367e1afb1e04f7.bin b/.inline-snapshot/external/2018feb66ae13fcf5333d61b95849decc68d3f63bd38172889367e1afb1e04f7.bin new file mode 100644 index 0000000000..871970676f --- /dev/null +++ b/.inline-snapshot/external/2018feb66ae13fcf5333d61b95849decc68d3f63bd38172889367e1afb1e04f7.bin @@ -0,0 +1,22 @@ +data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_4XzlGBLtUe9dy3GVNV4jhq7h","type":"function","function":{"name":"get_weather","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"New"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" York"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" City"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} + +data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[],"usage":{"prompt_tokens":44,"completion_tokens":16,"total_tokens":60,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/24aaf30663f9a568a0e77970b4fd3deafe041da94d541071009596234d8c84a6.bin b/.inline-snapshot/external/24aaf30663f9a568a0e77970b4fd3deafe041da94d541071009596234d8c84a6.bin deleted file mode 100644 index 49962cff27..0000000000 --- a/.inline-snapshot/external/24aaf30663f9a568a0e77970b4fd3deafe041da94d541071009596234d8c84a6.bin +++ /dev/null @@ -1,36 +0,0 @@ -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_7PhhveOvvpPK53s1fV8TWhoV","type":"function","function":{"name":"GetWeatherArgs","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Ed"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"inburgh"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"country"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"GB"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"units"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"c"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} - -data: {"id":"chatcmpl-9tXjnXkjzholyB3ceNegQC7g5zP57","object":"chat.completion.chunk","created":1723024755,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":76,"completion_tokens":24,"total_tokens":100}} - -data: [DONE] - diff --git a/.inline-snapshot/external/453df473e96274dd8ab61ab4d13dfcc25dc2f57a5e05eb5cc46c70b51d8845c2.bin b/.inline-snapshot/external/453df473e96274dd8ab61ab4d13dfcc25dc2f57a5e05eb5cc46c70b51d8845c2.bin deleted file mode 100644 index adcdddd317..0000000000 --- a/.inline-snapshot/external/453df473e96274dd8ab61ab4d13dfcc25dc2f57a5e05eb5cc46c70b51d8845c2.bin +++ /dev/null @@ -1,52 +0,0 @@ -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"role":"assistant","content":null},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_lQnnsesjFMWMQ5IeWPHzR4th","type":"function","function":{"name":"GetWeatherArgs","arguments":""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"ci"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ty\": "}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"Edinb"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"urgh"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\", \"c"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ountry"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\": \""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"UK\", "}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"units"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\": \""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"c\"}"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"id":"call_2xjOUgaCdiwAcl9ZBL9LyMUU","type":"function","function":{"name":"get_stock_price","arguments":""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"{\"ti"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"cker\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":": \"AAP"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"L\", "}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"\"exch"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"ange\":"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":" \"NA"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"SDAQ\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"}"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} - -data: {"id":"chatcmpl-9tXjpPLJgivc9nyuBCCWX8HNg9L2J","object":"chat.completion.chunk","created":1723024757,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[],"usage":{"prompt_tokens":149,"completion_tokens":60,"total_tokens":209}} - -data: [DONE] - diff --git a/.inline-snapshot/external/4cc50a6135d254573a502310e6af1246f55edb6ad95fa24059f160996b68866d.bin b/.inline-snapshot/external/4cc50a6135d254573a502310e6af1246f55edb6ad95fa24059f160996b68866d.bin new file mode 100644 index 0000000000..c3392883be --- /dev/null +++ b/.inline-snapshot/external/4cc50a6135d254573a502310e6af1246f55edb6ad95fa24059f160996b68866d.bin @@ -0,0 +1,10 @@ +data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"length"}]} + +data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":1,"total_tokens":80,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/4d75e4d7c3e0b532a67fb2114ff7868df0b6d8a02dfcd23f6bc7196cf0eadb6e.bin b/.inline-snapshot/external/4d75e4d7c3e0b532a67fb2114ff7868df0b6d8a02dfcd23f6bc7196cf0eadb6e.bin deleted file mode 100644 index 008d5882ec..0000000000 --- a/.inline-snapshot/external/4d75e4d7c3e0b532a67fb2114ff7868df0b6d8a02dfcd23f6bc7196cf0eadb6e.bin +++ /dev/null @@ -1,28 +0,0 @@ -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":"I'm"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" sorry"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":","},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" I"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" cannot"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" assist"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" with"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" that"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" request"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":"."},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - -data: {"id":"chatcmpl-9tXjkxJ4omrCOJoVbZIgaPWZS8TLD","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":17,"completion_tokens":11,"total_tokens":28}} - -data: [DONE] - diff --git a/.inline-snapshot/external/569c877e69429d4cbc1577d2cd6dd33878095c68badc6b6654a69769b391a1c1.bin b/.inline-snapshot/external/569c877e69429d4cbc1577d2cd6dd33878095c68badc6b6654a69769b391a1c1.bin new file mode 100644 index 0000000000..47dd73151c --- /dev/null +++ b/.inline-snapshot/external/569c877e69429d4cbc1577d2cd6dd33878095c68badc6b6654a69769b391a1c1.bin @@ -0,0 +1,30 @@ +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":{"content":null,"refusal":[]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"I'm"},"logprobs":{"content":null,"refusal":[{"token":"I'm","logprob":-0.0012038043,"bytes":[73,39,109],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" very"},"logprobs":{"content":null,"refusal":[{"token":" very","logprob":-0.8438816,"bytes":[32,118,101,114,121],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" sorry"},"logprobs":{"content":null,"refusal":[{"token":" sorry","logprob":-3.4121115e-6,"bytes":[32,115,111,114,114,121],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":","},"logprobs":{"content":null,"refusal":[{"token":",","logprob":-0.000033809047,"bytes":[44],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" but"},"logprobs":{"content":null,"refusal":[{"token":" but","logprob":-0.038048144,"bytes":[32,98,117,116],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" I"},"logprobs":{"content":null,"refusal":[{"token":" I","logprob":-0.0016109125,"bytes":[32,73],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" can't"},"logprobs":{"content":null,"refusal":[{"token":" can't","logprob":-0.0073532974,"bytes":[32,99,97,110,39,116],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" assist"},"logprobs":{"content":null,"refusal":[{"token":" assist","logprob":-0.0020837625,"bytes":[32,97,115,115,105,115,116],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" with"},"logprobs":{"content":null,"refusal":[{"token":" with","logprob":-0.00318354,"bytes":[32,119,105,116,104],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" that"},"logprobs":{"content":null,"refusal":[{"token":" that","logprob":-0.0017186158,"bytes":[32,116,104,97,116],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"."},"logprobs":{"content":null,"refusal":[{"token":".","logprob":-0.57687104,"bytes":[46],"top_logprobs":[]}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + +data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":12,"total_tokens":91,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/69363a555f8ea9b6eee0bb022af0afcd22d4f0e85418ab38ee24d2a570a84ff0.bin b/.inline-snapshot/external/69363a555f8ea9b6eee0bb022af0afcd22d4f0e85418ab38ee24d2a570a84ff0.bin deleted file mode 100644 index 852a7758f9..0000000000 --- a/.inline-snapshot/external/69363a555f8ea9b6eee0bb022af0afcd22d4f0e85418ab38ee24d2a570a84ff0.bin +++ /dev/null @@ -1,10 +0,0 @@ -data: {"id":"chatcmpl-9tXjkSxyTVUSWZRJFSZJgWBHzh2c3","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkSxyTVUSWZRJFSZJgWBHzh2c3","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjkSxyTVUSWZRJFSZJgWBHzh2c3","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"length"}]} - -data: {"id":"chatcmpl-9tXjkSxyTVUSWZRJFSZJgWBHzh2c3","object":"chat.completion.chunk","created":1723024752,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":17,"completion_tokens":1,"total_tokens":18}} - -data: [DONE] - diff --git a/.inline-snapshot/external/7e5ea4d12e7cc064399b6631415e65923f182256b6e6b752950a3aaa2ad2320a.bin b/.inline-snapshot/external/7e5ea4d12e7cc064399b6631415e65923f182256b6e6b752950a3aaa2ad2320a.bin new file mode 100644 index 0000000000..801db2adf2 --- /dev/null +++ b/.inline-snapshot/external/7e5ea4d12e7cc064399b6631415e65923f182256b6e6b752950a3aaa2ad2320a.bin @@ -0,0 +1,36 @@ +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"61"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + +data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":14,"total_tokens":93,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/83b060bae42eb41c4f1edbb7c1542b954b37d9dfd1910b964ddebc9677e6ae85.bin b/.inline-snapshot/external/83b060bae42eb41c4f1edbb7c1542b954b37d9dfd1910b964ddebc9677e6ae85.bin new file mode 100644 index 0000000000..e9f34b6334 --- /dev/null +++ b/.inline-snapshot/external/83b060bae42eb41c4f1edbb7c1542b954b37d9dfd1910b964ddebc9677e6ae85.bin @@ -0,0 +1,12 @@ +data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":{"content":[],"refusal":null},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Foo"},"logprobs":{"content":[{"token":"Foo","logprob":-0.0025094282,"bytes":[70,111,111],"top_logprobs":[]}],"refusal":null},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"!"},"logprobs":{"content":[{"token":"!","logprob":-0.26638845,"bytes":[33],"top_logprobs":[]}],"refusal":null},"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + +data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":9,"completion_tokens":2,"total_tokens":11,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/83d3d003e6fdaa69b7a398440f9d46ee41408a688758219f3f58ac1ee2084db3.bin b/.inline-snapshot/external/83d3d003e6fdaa69b7a398440f9d46ee41408a688758219f3f58ac1ee2084db3.bin deleted file mode 100644 index 05e08e3475..0000000000 --- a/.inline-snapshot/external/83d3d003e6fdaa69b7a398440f9d46ee41408a688758219f3f58ac1ee2084db3.bin +++ /dev/null @@ -1,28 +0,0 @@ -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_pVHYsU0gmSfX5TqxOyVbB2ma","type":"function","function":{"name":"get_weather","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"San"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Francisco"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"state"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"CA"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} - -data: {"id":"chatcmpl-9tXjq87CydgLGv4TnzV0EVDybqjCA","object":"chat.completion.chunk","created":1723024758,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_baa7103b2c","choices":[],"usage":{"prompt_tokens":48,"completion_tokens":19,"total_tokens":67}} - -data: [DONE] - diff --git a/.inline-snapshot/external/a0c4f0be184e8234cdc0e3abae5dfafc1d712c253b42bafe07991b3058541016.bin b/.inline-snapshot/external/a0c4f0be184e8234cdc0e3abae5dfafc1d712c253b42bafe07991b3058541016.bin deleted file mode 100644 index df20d6fda5..0000000000 --- a/.inline-snapshot/external/a0c4f0be184e8234cdc0e3abae5dfafc1d712c253b42bafe07991b3058541016.bin +++ /dev/null @@ -1,156 +0,0 @@ -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":"I'm"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" sorry"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":","},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" but"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" I"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" can't"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" accurately"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" provide"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"63"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"58"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" the"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"6"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" current"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" weather"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" for"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" San"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" Francisco"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" as"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" my"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" data"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" is"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" up"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" to"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" October"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" "},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":"202"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":"3"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":"."},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" You"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" can"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" try"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" checking"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" a"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" reliable"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" weather"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" website"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" or"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" app"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" for"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" real"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":"-time"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":" updates"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{"refusal":"."},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":1,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[{"index":2,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - -data: {"id":"chatcmpl-9tXjjpr5ZWilqbUE2tn3H1lwvMnDu","object":"chat.completion.chunk","created":1723024751,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_2a322c9ffc","choices":[],"usage":{"prompt_tokens":17,"completion_tokens":71,"total_tokens":88}} - -data: [DONE] - diff --git a/.inline-snapshot/external/a247c49c5fcd492bfb7a02a3306ad615ed8d8f649888ebfddfbc3ee151f44d46.bin b/.inline-snapshot/external/a247c49c5fcd492bfb7a02a3306ad615ed8d8f649888ebfddfbc3ee151f44d46.bin new file mode 100644 index 0000000000..b44d334ac5 --- /dev/null +++ b/.inline-snapshot/external/a247c49c5fcd492bfb7a02a3306ad615ed8d8f649888ebfddfbc3ee151f44d46.bin @@ -0,0 +1,28 @@ +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_CTf1nWJLqSeRgDqaCG27xZ74","type":"function","function":{"name":"get_weather","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"San"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Francisco"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"state"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"CA"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} + +data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[],"usage":{"prompt_tokens":48,"completion_tokens":19,"total_tokens":67,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/a491adda08c3d4fde95f5b2ee3f60f7f745f1a56d82e62f58031cc2add502380.bin b/.inline-snapshot/external/a491adda08c3d4fde95f5b2ee3f60f7f745f1a56d82e62f58031cc2add502380.bin new file mode 100644 index 0000000000..160e65de49 --- /dev/null +++ b/.inline-snapshot/external/a491adda08c3d4fde95f5b2ee3f60f7f745f1a56d82e62f58031cc2add502380.bin @@ -0,0 +1,100 @@ +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"65"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"61"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"59"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + +data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":42,"total_tokens":121,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/be1089999ca5f1e63b149447f1613bdb9c4a2ad8262027d158cc94e6f9765164.bin b/.inline-snapshot/external/be1089999ca5f1e63b149447f1613bdb9c4a2ad8262027d158cc94e6f9765164.bin deleted file mode 100644 index f2a8158310..0000000000 --- a/.inline-snapshot/external/be1089999ca5f1e63b149447f1613bdb9c4a2ad8262027d158cc94e6f9765164.bin +++ /dev/null @@ -1,12 +0,0 @@ -data: {"id":"chatcmpl-9tXjliCPGY1wrAHNJ4DBnWJxKYyuf","object":"chat.completion.chunk","created":1723024753,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":{"content":[],"refusal":null},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjliCPGY1wrAHNJ4DBnWJxKYyuf","object":"chat.completion.chunk","created":1723024753,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"Foo"},"logprobs":{"content":[{"token":"Foo","logprob":-0.0067602484,"bytes":[70,111,111],"top_logprobs":[]}],"refusal":null},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjliCPGY1wrAHNJ4DBnWJxKYyuf","object":"chat.completion.chunk","created":1723024753,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"."},"logprobs":{"content":[{"token":".","logprob":-2.4962392,"bytes":[46],"top_logprobs":[]}],"refusal":null},"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjliCPGY1wrAHNJ4DBnWJxKYyuf","object":"chat.completion.chunk","created":1723024753,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - -data: {"id":"chatcmpl-9tXjliCPGY1wrAHNJ4DBnWJxKYyuf","object":"chat.completion.chunk","created":1723024753,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":9,"completion_tokens":2,"total_tokens":11}} - -data: [DONE] - diff --git a/.inline-snapshot/external/c6aa7e397b7123c3501f25df3a05d4daf7e8ad6d61ffa406ab5361fe36a8d5b1.bin b/.inline-snapshot/external/c6aa7e397b7123c3501f25df3a05d4daf7e8ad6d61ffa406ab5361fe36a8d5b1.bin new file mode 100644 index 0000000000..f20333fbef --- /dev/null +++ b/.inline-snapshot/external/c6aa7e397b7123c3501f25df3a05d4daf7e8ad6d61ffa406ab5361fe36a8d5b1.bin @@ -0,0 +1,36 @@ +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_c91SqDXlYFuETYv8mUHzz6pp","type":"function","function":{"name":"GetWeatherArgs","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Ed"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"inburgh"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"country"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"UK"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"units"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"c"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} + +data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[],"usage":{"prompt_tokens":76,"completion_tokens":24,"total_tokens":100,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/ca015b8b1ebaac98be76f2f855f8694b77f608de5e2a3799276be06ce3fbb15b.bin b/.inline-snapshot/external/ca015b8b1ebaac98be76f2f855f8694b77f608de5e2a3799276be06ce3fbb15b.bin deleted file mode 100644 index c0a355f9d1..0000000000 --- a/.inline-snapshot/external/ca015b8b1ebaac98be76f2f855f8694b77f608de5e2a3799276be06ce3fbb15b.bin +++ /dev/null @@ -1,30 +0,0 @@ -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":"I'm"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" sorry"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":","},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" but"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" I"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" can't"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" assist"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" with"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" that"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":" request"},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"refusal":"."},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - -data: {"id":"chatcmpl-9tXmMGFPkLS0t0u0895fzYOblnfYa","object":"chat.completion.chunk","created":1723024914,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":17,"completion_tokens":12,"total_tokens":29}} - -data: [DONE] - diff --git a/.inline-snapshot/external/d615580118391ee13492193e3a8bb74642d23ac1ca13fe37cb6e889b66f759f6.bin b/.inline-snapshot/external/d615580118391ee13492193e3a8bb74642d23ac1ca13fe37cb6e889b66f759f6.bin new file mode 100644 index 0000000000..aee8650c72 --- /dev/null +++ b/.inline-snapshot/external/d615580118391ee13492193e3a8bb74642d23ac1ca13fe37cb6e889b66f759f6.bin @@ -0,0 +1,362 @@ +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"location"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" CA"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"weather"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"18"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Part"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"ly"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Cloud"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"y"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"humidity"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"72"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"%\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"wind"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Speed"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"15"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" km"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"/h"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"wind"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Direction"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"NW"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"forecast"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" [\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"day"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Monday"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"high"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"20"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"low"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"14"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Sunny"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"day"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Tuesday"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"high"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"19"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"low"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"15"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Mostly"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Cloud"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"y"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"day"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Wednesday"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"high"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"18"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"low"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"14"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Cloud"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"y"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" }\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" ]\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" }\n"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + +data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":19,"completion_tokens":177,"total_tokens":196,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/dae1b261f19722801adc82a13181772c3010c10b37e8af3996fbdbbecb3c32a2.bin b/.inline-snapshot/external/dae1b261f19722801adc82a13181772c3010c10b37e8af3996fbdbbecb3c32a2.bin deleted file mode 100644 index f0911c575d..0000000000 --- a/.inline-snapshot/external/dae1b261f19722801adc82a13181772c3010c10b37e8af3996fbdbbecb3c32a2.bin +++ /dev/null @@ -1,22 +0,0 @@ -data: {"id":"chatcmpl-9tXjtfxZZh2FYaFVxXKf2jiqNDiSo","object":"chat.completion.chunk","created":1723024761,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_5uxEBMFySqqQGu02I5QHA8k6","type":"function","function":{"name":"get_weather","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjtfxZZh2FYaFVxXKf2jiqNDiSo","object":"chat.completion.chunk","created":1723024761,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjtfxZZh2FYaFVxXKf2jiqNDiSo","object":"chat.completion.chunk","created":1723024761,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjtfxZZh2FYaFVxXKf2jiqNDiSo","object":"chat.completion.chunk","created":1723024761,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjtfxZZh2FYaFVxXKf2jiqNDiSo","object":"chat.completion.chunk","created":1723024761,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"New"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjtfxZZh2FYaFVxXKf2jiqNDiSo","object":"chat.completion.chunk","created":1723024761,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" York"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjtfxZZh2FYaFVxXKf2jiqNDiSo","object":"chat.completion.chunk","created":1723024761,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" City"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjtfxZZh2FYaFVxXKf2jiqNDiSo","object":"chat.completion.chunk","created":1723024761,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]} - -data: {"id":"chatcmpl-9tXjtfxZZh2FYaFVxXKf2jiqNDiSo","object":"chat.completion.chunk","created":1723024761,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} - -data: {"id":"chatcmpl-9tXjtfxZZh2FYaFVxXKf2jiqNDiSo","object":"chat.completion.chunk","created":1723024761,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":44,"completion_tokens":16,"total_tokens":60}} - -data: [DONE] - diff --git a/.inline-snapshot/external/e2aad469b71d1d4894ff833ea147020a9d875eb7ce644a0ff355581690a4cbfd.bin b/.inline-snapshot/external/e2aad469b71d1d4894ff833ea147020a9d875eb7ce644a0ff355581690a4cbfd.bin new file mode 100644 index 0000000000..b68ca8a3d9 --- /dev/null +++ b/.inline-snapshot/external/e2aad469b71d1d4894ff833ea147020a9d875eb7ce644a0ff355581690a4cbfd.bin @@ -0,0 +1,68 @@ +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"I'm"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" unable"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" provide"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" real"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"-time"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" updates"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" To"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" get"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" the"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" current"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" San"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" I"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" recommend"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" checking"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" reliable"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" website"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" or"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" app"},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + +data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":30,"total_tokens":44,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.inline-snapshot/external/f82268f2fefd5cfbc7eeb59c297688be2f6ca0849a6e4f17851b517310841d9b.bin b/.inline-snapshot/external/f82268f2fefd5cfbc7eeb59c297688be2f6ca0849a6e4f17851b517310841d9b.bin new file mode 100644 index 0000000000..3b111d5e61 --- /dev/null +++ b/.inline-snapshot/external/f82268f2fefd5cfbc7eeb59c297688be2f6ca0849a6e4f17851b517310841d9b.bin @@ -0,0 +1,52 @@ +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":null},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_JMW1whyEaYG438VE1OIflxA2","type":"function","function":{"name":"GetWeatherArgs","arguments":""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"ci"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ty\": "}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"Edinb"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"urgh"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\", \"c"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ountry"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\": \""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"GB\", "}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"units"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\": \""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"c\"}"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"id":"call_DNYTawLBoN8fj3KN6qU9N1Ou","type":"function","function":{"name":"get_stock_price","arguments":""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"{\"ti"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"cker\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":": \"AAP"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"L\", "}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"\"exch"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"ange\":"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":" \"NA"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"SDAQ\""}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"}"}}]},"logprobs":null,"finish_reason":null}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} + +data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":149,"completion_tokens":60,"total_tokens":209,"completion_tokens_details":{"reasoning_tokens":0}}} + +data: [DONE] + diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c37d66f738..29ad411bac 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.45.1" + ".": "2.41.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 2fc39385e9..a62e7b979c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,4 @@ -configured_endpoints: 68 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai-ff407aa10917e62f2b0c12d1ad2c4f1258ed083bd45753c70eaaf5b1cf8356ae.yml +configured_endpoints: 262 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai/openai-0161cb2a0faadedfc17ff59c7fcad9671962dbd170f83811003c23702148cf36.yml +openapi_spec_hash: 1023c7442d0e2e7e213e8b3a253921c5 +config_hash: e02ca1082421dfe55b145c45e95d6126 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..5b01030785 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.importFormat": "relative", +} diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f344cfb3..1a9e138e0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,2608 @@ # Changelog +## 2.41.0 (2026-06-03) + +Full Changelog: [v2.40.0...v2.41.0](https://github.com/openai/openai-python/compare/v2.40.0...v2.41.0) + +### Features + +* **api:** responses.moderation and chat_completions.moderation ([87e46c2](https://github.com/openai/openai-python/commit/87e46c25ac9ca8cff407b52ad9fb33e326c059d6)) + +## 2.40.0 (2026-06-01) + +Full Changelog: [v2.39.0...v2.40.0](https://github.com/openai/openai-python/compare/v2.39.0...v2.40.0) + +### Features +* **api:** Add Amazon Bedrock Responses support + +### Bug Fixes + +* **api:** allow setting bedrock api keys on the client directly ([4d5bfde](https://github.com/openai/openai-python/commit/4d5bfdec37fa8a2b2a0413724755e586e627e28d)) + +## 2.39.0 (2026-06-01) + +Full Changelog: [v2.38.0...v2.39.0](https://github.com/openai/openai-python/compare/v2.38.0...v2.39.0) + +### Features + +* **api:** workload identity in audit logs, additional_tools item in responses, fix ActionSearch.query to be optional. ([ab60d7a](https://github.com/openai/openai-python/commit/ab60d7a52c310bb0490ff36b8bdc33b8d4ea725f)) + +## 2.38.0 (2026-05-21) + +Full Changelog: [v2.37.0...v2.38.0](https://github.com/openai/openai-python/compare/v2.37.0...v2.38.0) + +### Features + +* **api:** api update ([33d1d01](https://github.com/openai/openai-python/commit/33d1d013250053886a73d178136e6bd1b09df059)) +* **api:** manual updates ([a21700a](https://github.com/openai/openai-python/commit/a21700a2cd510cb9e6c88065ac8e942d4c041aa8)) +* **api:** update OpenAPI spec or Stainless config ([00265c5](https://github.com/openai/openai-python/commit/00265c5daba4d2481452ad35220f1556dab6bcf6)) + + +### Chores + +* **api:** docs updates ([ee10152](https://github.com/openai/openai-python/commit/ee101520d49e22c09cf8096f8cbb3848ea58a1f9)) +* check release PR custom code sync ([2638779](https://github.com/openai/openai-python/commit/2638779a5b8fffaa8fdb6eebc1d734f15d2491f8)) +* remove release automation trigger ([bd6eea5](https://github.com/openai/openai-python/commit/bd6eea559f2996d914258a65e645981bdce3cad4)) +* trigger release automation ([f62d082](https://github.com/openai/openai-python/commit/f62d08201eea8e08d4bb3385662f934d4adccb29)) + +## 2.37.0 (2026-05-13) + +Full Changelog: [v2.36.0...v2.37.0](https://github.com/openai/openai-python/compare/v2.36.0...v2.37.0) + +### Features + +* **api:** add service_tier parameter to responses compact method ([625827c](https://github.com/openai/openai-python/commit/625827c5509ece3c40e5002be37a9bd9d91b5374)) +* **internal/types:** support eagerly validating pydantic iterators ([7e527bc](https://github.com/openai/openai-python/commit/7e527bc927cc58b74d7619abf7f1fbcfff8bddfa)) +* Remove unnecessary client_id when using workload identity provider for auth ([c39ea8d](https://github.com/openai/openai-python/commit/c39ea8d12a010052d7f02cebe8daabd2d1f89597)) + + +### Bug Fixes + +* **client:** add missing f-string prefix in file type error message ([c85ebd9](https://github.com/openai/openai-python/commit/c85ebd935cb4b80e7e97ce255437684f6411fb00)) + +## 2.36.0 (2026-05-07) + +Full Changelog: [v2.35.1...v2.36.0](https://github.com/openai/openai-python/compare/v2.35.1...v2.36.0) + +### Features + +* **api:** manual updates ([13c639c](https://github.com/openai/openai-python/commit/13c639cc7d57e4fbd4406563511e15eeb88a54b2)) +* **api:** realtime 2 ([8fe0ab8](https://github.com/openai/openai-python/commit/8fe0ab87e67eeb3cc27426b50093845229520f0e)) + +## 2.35.1 (2026-05-06) + +Full Changelog: [v2.35.0...v2.35.1](https://github.com/openai/openai-python/compare/v2.35.0...v2.35.1) + +### Bug Fixes + +* **api:** fix imagegen `size` enum regression ([4484653](https://github.com/openai/openai-python/commit/44846536bc3b02c393daa5bae70a85de04c7f621)) + +## 2.35.0 (2026-05-06) + +Full Changelog: [v2.34.0...v2.35.0](https://github.com/openai/openai-python/compare/v2.34.0...v2.35.0) + +### Features + +* **api:** update image 2 ([0ba55d7](https://github.com/openai/openai-python/commit/0ba55d7569565045426e1587906a70d5682a4bba)) +* **api:** manual updates ([72bf67a](https://github.com/openai/openai-python/commit/72bf67acbc9f030c20db3d5a1a74ea6d67d55f51)) + + +### Chores + +* remove legacy python cli ([32f36e4](https://github.com/openai/openai-python/commit/32f36e447d02c3124af8ab48fcc3537df2fed66e)) +* rename legacy python cli entrypoint ([a3b182d](https://github.com/openai/openai-python/commit/a3b182d6d2c2e6fe1d53ca7550b2d43e0f8b2cd3)) + + +### Documentation + +* **api:** update top_logprobs parameter description across chat and responses ([f9d339f](https://github.com/openai/openai-python/commit/f9d339fcea63feaa1bdf918a4599f2b032c83517)) + +## 2.34.0 (2026-05-04) + +Full Changelog: [v2.33.0...v2.34.0](https://github.com/openai/openai-python/compare/v2.33.0...v2.34.0) + +### Features + +* **api:** add external_key_id to projects, email/metadata params to users, update types ([2d232ee](https://github.com/openai/openai-python/commit/2d232eebb2fe021bb21f2576b17d1d588f81a608)) +* **api:** add support for Admin API Keys per endpoint ([b8b176a](https://github.com/openai/openai-python/commit/b8b176af84172f27d2fde8dca062ca4c41f94bf7)) +* **api:** admin API updates ([4ae1138](https://github.com/openai/openai-python/commit/4ae1138ae1f76e81a2267e4deb45b435c10774d5)) +* **api:** manual updates ([c1870f1](https://github.com/openai/openai-python/commit/c1870f1b881bb914e4e62a6c8b08d4c2b9a6fd54)) +* **api:** manual updates ([f6bb9c7](https://github.com/openai/openai-python/commit/f6bb9c7d7bdcc45425d37722358bed097e83d493)) +* support setting headers via env ([1e89d8b](https://github.com/openai/openai-python/commit/1e89d8b56aba12f99a8ef2b1b78fdee84751275a)) + + +### Bug Fixes + +* allow explicit Azure auth headers ([a0626ba](https://github.com/openai/openai-python/commit/a0626babf0548fb03cf3c2d054da116dd6466701)) +* **api:** correct prompt_cache_retention enum value from in-memory to in_memory ([d47d9f0](https://github.com/openai/openai-python/commit/d47d9f0f79c612c4d14005a0a3cf44e1968c9bff)) +* **api:** preserve python api key attribute type ([62607f6](https://github.com/openai/openai-python/commit/62607f61c542ed559ef114849e31307c0c290286)) +* **api:** resolve python auth type checks ([42a31a7](https://github.com/openai/openai-python/commit/42a31a7efb6784633108c1a73e1779ed79ab8bed)) +* **api:** support admin api key auth ([f029eb9](https://github.com/openai/openai-python/commit/f029eb937f976110c1a67b9342525a38a214072e)) +* avoid bearer fallback for admin auth ([22e01a8](https://github.com/openai/openai-python/commit/22e01a8cf791a143ecc576f46de50eee9b3c2147)) +* preserve selected auth credentials ([0d27f9d](https://github.com/openai/openai-python/commit/0d27f9dbd3b2ae82b2e8c2eeb9e7e78f3edecdf1)) +* require bearer auth for stream helpers ([d055539](https://github.com/openai/openai-python/commit/d0555390bcf4a704c10d318c7de2fe006750c3d0)) +* **types:** correct created_at and completed_at to float in Response ([7da4b88](https://github.com/openai/openai-python/commit/7da4b88c1985028f7ee9a98b919e71f863f979f0)) +* **types:** correct timestamp types to int in Response model ([e55631c](https://github.com/openai/openai-python/commit/e55631c868b1d0b720fda0abdbc342787cd95e2c)) +* use correct field name format for multipart file arrays ([9ee4825](https://github.com/openai/openai-python/commit/9ee482576c2bd6b33b6cf7458c37ab2e7d5bc725)) + + +### Performance Improvements + +* **client:** optimize file structure copying in multipart requests ([dca474e](https://github.com/openai/openai-python/commit/dca474e5beac7cc8e05855f042c3227843030c1b)) + + +### Chores + +* **internal:** more robust bootstrap script ([9ec1600](https://github.com/openai/openai-python/commit/9ec1600d48fda10abb144b2a62d07c5abd7e9ab1)) +* **internal:** reformat pyproject.toml ([12ad57b](https://github.com/openai/openai-python/commit/12ad57b8da5b5c0615641af273d4bbf2981d6bf7)) +* **tests:** bump steady to v0.22.1 ([486dfed](https://github.com/openai/openai-python/commit/486dfedfec8484bb00318b0ea798c2260f7a720c)) + + +### Documentation + +* **api:** add rate limit and vector store info to files create ([4f776df](https://github.com/openai/openai-python/commit/4f776df78d757fdbf25662c4be98b5c98183aaaf)) +* **api:** update files rate limit documentation ([b141a20](https://github.com/openai/openai-python/commit/b141a20e948b5af3b8fbe4261798c191d2857b4a)) + +## 2.33.0 (2026-04-28) + +Full Changelog: [v2.32.0...v2.33.0](https://github.com/openai/openai-python/compare/v2.32.0...v2.33.0) + +### Features + +* **api:** api update ([18f834a](https://github.com/openai/openai-python/commit/18f834a54f92ea827452471a46a4f442f251e2c8)) + + +### Bug Fixes + +* **api:** correct prompt_cache_retention enum value from in-memory to in_memory ([#1822](https://github.com/openai/openai-python/issues/1822)) ([f9d2d13](https://github.com/openai/openai-python/commit/f9d2d1359688a6247ecba858fc687173c480c9c8)) + + +### Chores + +* **ci:** remove release-doctor workflow ([00b2091](https://github.com/openai/openai-python/commit/00b20910e3539842f21d86ab5928fb5216d3a765)) + +## 2.32.0 (2026-04-15) + +Full Changelog: [v2.31.0...v2.32.0](https://github.com/openai/openai-python/compare/v2.31.0...v2.32.0) + +### Features + +* **api:** Add detail to InputFileContent ([60de21d](https://github.com/openai/openai-python/commit/60de21d1fcfbcadea0d9b8d884c73c9dc49d14ff)) +* **api:** add OAuthErrorCode type ([0c8d2c3](https://github.com/openai/openai-python/commit/0c8d2c3b44242c9139dc554896ea489b56e236b8)) +* **client:** add event handler implementation for websockets ([0280d05](https://github.com/openai/openai-python/commit/0280d0568f706684ecbf0aabf3575cdcb7fd22d5)) +* **client:** allow enqueuing to websockets even when not connected ([67aa20e](https://github.com/openai/openai-python/commit/67aa20e69bc0e4a3b7694327c808606bfa24a966)) +* **client:** support reconnection in websockets ([eb72a95](https://github.com/openai/openai-python/commit/eb72a953ea9dc5beec3eef537be6eb32292c3f65)) + + +### Bug Fixes + +* ensure file data are only sent as 1 parameter ([c0c2ecd](https://github.com/openai/openai-python/commit/c0c2ecd0f6b64fa5fafda6134bb06995b143a2cf)) + + +### Documentation + +* improve examples ([84712fa](https://github.com/openai/openai-python/commit/84712fa0f094b53151a0fe6ac85aa98018b2a7e2)) + +## 2.31.0 (2026-04-08) + +Full Changelog: [v2.30.0...v2.31.0](https://github.com/openai/openai-python/compare/v2.30.0...v2.31.0) + +### Features + +* **api:** add phase field to conversations message ([3e5834e](https://github.com/openai/openai-python/commit/3e5834efb39b24e019a29dc54d890c67d18cbb54)) +* **api:** add web_search_call.results to ResponseIncludable type ([ffd8741](https://github.com/openai/openai-python/commit/ffd8741dd38609a5af0159ceb800d8ddba7925f8)) +* **client:** add support for short-lived tokens ([#1608](https://github.com/openai/openai-python/issues/1608)) ([22fe722](https://github.com/openai/openai-python/commit/22fe7228d4990c197cd721b3ad7931ad05cca5dd)) +* **client:** support sending raw data over websockets ([f1bc52e](https://github.com/openai/openai-python/commit/f1bc52ef641dfca6fdf2a5b00ce3b09bff2552f5)) +* **internal:** implement indices array format for query and form serialization ([49194cf](https://github.com/openai/openai-python/commit/49194cfa711328216ff131d6f65c9298822a7c51)) + + +### Bug Fixes + +* **client:** preserve hardcoded query params when merging with user params ([92e109c](https://github.com/openai/openai-python/commit/92e109c3d9569a942e1919e75977dc13fa015f9a)) +* **types:** remove web_search_call.results from ResponseIncludable ([d3cc401](https://github.com/openai/openai-python/commit/d3cc40165cd86015833d15167cc7712b4102f932)) + + +### Chores + +* **tests:** bump steady to v0.20.1 ([d60e2ee](https://github.com/openai/openai-python/commit/d60e2eea7f6916540cd4ba901dceb07051119da4)) +* **tests:** bump steady to v0.20.2 ([6508d47](https://github.com/openai/openai-python/commit/6508d474332d4e82d9615c0a9a77379f9b5e4412)) + + +### Documentation + +* **api:** update file parameter descriptions in vector_stores files and file_batches ([a9e7ebd](https://github.com/openai/openai-python/commit/a9e7ebd505b9ae90514339aa63c6f1984a08cf6b)) + +## 2.30.0 (2026-03-25) + +Full Changelog: [v2.29.0...v2.30.0](https://github.com/openai/openai-python/compare/v2.29.0...v2.30.0) + +### Features + +* **api:** add keys field to Click/DoubleClick/Drag/Move/Scroll computer actions ([ee1bbed](https://github.com/openai/openai-python/commit/ee1bbeddbb38dab817557412dc106354409bb950)) + + +### Bug Fixes + +* **api:** align SDK response types with expanded item schemas ([f3f258a](https://github.com/openai/openai-python/commit/f3f258a9d4d19db3fb0c6c35e25ad3cedbe71254)) +* sanitize endpoint path params ([89f6698](https://github.com/openai/openai-python/commit/89f66988fde790c0c83ff8b876d1e1b10d616367)) +* **types:** make type required in ResponseInputMessageItem ([cfdb167](https://github.com/openai/openai-python/commit/cfdb1676ea0550840330a58f1a31a40a41a0a53f)) + + +### Chores + +* **ci:** skip lint on metadata-only changes ([faa93e1](https://github.com/openai/openai-python/commit/faa93e19a1d5c30c7dd672a08dbbdbb3c0374714)) +* **internal:** update gitignore ([c468477](https://github.com/openai/openai-python/commit/c468477f1546579618865a726e35a685cffeacd9)) +* **tests:** bump steady to v0.19.4 ([f350af8](https://github.com/openai/openai-python/commit/f350af86c13ade0237778010d264c55fda443354)) +* **tests:** bump steady to v0.19.5 ([5c03401](https://github.com/openai/openai-python/commit/5c0340128fc1a416e2dfdc6ab4b05f1e954e8482)) +* **tests:** bump steady to v0.19.6 ([b6353b8](https://github.com/openai/openai-python/commit/b6353b8411d31dcc95875d801ce9e90a21e0fd52)) +* **tests:** bump steady to v0.19.7 ([1d654be](https://github.com/openai/openai-python/commit/1d654bea74ac9c3d43302587f98f33cfff502e48)) + + +### Refactors + +* **tests:** switch from prism to steady ([4a82035](https://github.com/openai/openai-python/commit/4a82035669b739d16a0e85d4ded778d51e061948)) + +## 2.29.0 (2026-03-17) + +Full Changelog: [v2.28.0...v2.29.0](https://github.com/openai/openai-python/compare/v2.28.0...v2.29.0) + +### Features + +* **api:** 5.4 nano and mini model slugs ([3b45666](https://github.com/openai/openai-python/commit/3b456661f77ca3196aceb5ab3350664a63481114)) +* **api:** add /v1/videos endpoint to batches create method ([c0e7a16](https://github.com/openai/openai-python/commit/c0e7a161a996854021e9eb69ea2a60ca0d08047f)) +* **api:** add defer_loading field to ToolFunction ([3167595](https://github.com/openai/openai-python/commit/3167595432bdda2f90721901d30ad316db49323e)) +* **api:** add in and nin operators to ComparisonFilter type ([664f02b](https://github.com/openai/openai-python/commit/664f02b051af84e1ca3fa313981ec72fdea269b3)) + + +### Bug Fixes + +* **deps:** bump minimum typing-extensions version ([a2fb2ca](https://github.com/openai/openai-python/commit/a2fb2ca55142c6658a18be7bd1392a01f5a83f35)) +* **pydantic:** do not pass `by_alias` unless set ([8ebe8fb](https://github.com/openai/openai-python/commit/8ebe8fbcb011c6a005a715cae50c6400a8596ee0)) + + +### Chores + +* **internal:** tweak CI branches ([96ccc3c](https://github.com/openai/openai-python/commit/96ccc3cca35645fd3140f99b0fc8e55545065212)) + +## 2.28.0 (2026-03-13) + +Full Changelog: [v2.27.0...v2.28.0](https://github.com/openai/openai-python/compare/v2.27.0...v2.28.0) + +### Features + +* **api:** custom voices ([50dc060](https://github.com/openai/openai-python/commit/50dc060b55767615419219ef567d31210517e613)) + +## 2.27.0 (2026-03-13) + +Full Changelog: [v2.26.0...v2.27.0](https://github.com/openai/openai-python/compare/v2.26.0...v2.27.0) + +### Features + +* **api:** api update ([60ab24a](https://github.com/openai/openai-python/commit/60ab24ae722a7fa280eb4b2273da4ded1f930231)) +* **api:** manual updates ([b244b09](https://github.com/openai/openai-python/commit/b244b0946045aaa0dbfa8c0ce5164b64e1156834)) +* **api:** manual updates ([d806635](https://github.com/openai/openai-python/commit/d806635081a736cc81344bf1e62b57956a88d093)) +* **api:** sora api improvements: character api, video extensions/edits, higher resolution exports. ([58b70d3](https://github.com/openai/openai-python/commit/58b70d304a4b2cf70eae4db4b448d439fc8b8ba3)) + + +### Bug Fixes + +* **api:** repair merged videos resource ([742d8ee](https://github.com/openai/openai-python/commit/742d8ee1f969ee1bbb39ba9d799dcd5c480d8ddb)) + + +### Chores + +* **internal:** codegen related update ([4e6498e](https://github.com/openai/openai-python/commit/4e6498e2d222dd35d76bb397ba976ff53c852e12)) +* **internal:** codegen related update ([93af129](https://github.com/openai/openai-python/commit/93af129e8919de6d3aee19329c8bdef0532bd20a)) +* match http protocol with ws protocol instead of wss ([026f9de](https://github.com/openai/openai-python/commit/026f9de35d2aa74f35c91261eb5ea43d4ab1b8ba)) +* use proper capitalization for WebSockets ([a2f9b07](https://github.com/openai/openai-python/commit/a2f9b0722597627e8d01aa05c27a52015072726b)) + +## 2.26.0 (2026-03-05) + +Full Changelog: [v2.25.0...v2.26.0](https://github.com/openai/openai-python/compare/v2.25.0...v2.26.0) + +### Features + +* **api:** The GA ComputerTool now uses the CompuerTool class. The 'computer_use_preview' tool is moved to ComputerUsePreview ([78f5b3c](https://github.com/openai/openai-python/commit/78f5b3c287b71ed6fbeb71fb6b5c0366db704cd2)) + +## 2.25.0 (2026-03-05) + +Full Changelog: [v2.24.0...v2.25.0](https://github.com/openai/openai-python/compare/v2.24.0...v2.25.0) + +### Features + +* **api:** gpt-5.4, tool search tool, and new computer tool ([6b2043f](https://github.com/openai/openai-python/commit/6b2043f3d63058f5582eab7a7705b30a3d5536f0)) +* **api:** remove prompt_cache_key param from responses, phase field from message types ([44fb382](https://github.com/openai/openai-python/commit/44fb382698872d98d5f72c880b47846c7b594f4f)) + + +### Bug Fixes + +* **api:** internal schema fixes ([0c0f970](https://github.com/openai/openai-python/commit/0c0f970cbd164131bf06f7ab38f170bbcb323683)) +* **api:** manual updates ([9fc323f](https://github.com/openai/openai-python/commit/9fc323f4da6cfca9de194e12c1486a3cd1bfa4b5)) +* **api:** readd phase ([1b27b5a](https://github.com/openai/openai-python/commit/1b27b5a834f5cb75f80c597259d0df0352ba83bd)) + + +### Chores + +* **internal:** codegen related update ([bdb837d](https://github.com/openai/openai-python/commit/bdb837d2c1d2a161cc4b22ef26e9e8446d5dc2a3)) +* **internal:** codegen related update ([b1de941](https://github.com/openai/openai-python/commit/b1de9419a68fd6fb97a63f415fb3d1e5851582cb)) +* **internal:** reduce warnings ([7cdbd06](https://github.com/openai/openai-python/commit/7cdbd06d3ca41af64d616b4b4bb61226cc38b662)) + +## 2.24.0 (2026-02-24) + +Full Changelog: [v2.23.0...v2.24.0](https://github.com/openai/openai-python/compare/v2.23.0...v2.24.0) + +### Features + +* **api:** add phase ([391deb9](https://github.com/openai/openai-python/commit/391deb99f6a92e51bffb25efd8dfe367d144bb9d)) + + +### Bug Fixes + +* **api:** fix phase enum ([42ebf7c](https://github.com/openai/openai-python/commit/42ebf7c30b7e27a175c0d75fcf42c8dc858e56d6)) +* **api:** phase docs ([7ddc61c](https://github.com/openai/openai-python/commit/7ddc61cd0f7825d5e7f3a10daf809135511d8d20)) + + +### Chores + +* **internal:** make `test_proxy_environment_variables` more resilient to env ([65af8fd](https://github.com/openai/openai-python/commit/65af8fd8550e99236e3f4dcb035312441788157a)) +* **internal:** refactor sse event parsing ([2344600](https://github.com/openai/openai-python/commit/23446008f06fb474d8c75d14a1bce26f4c5b95d8)) + +## 2.23.0 (2026-02-24) + +Full Changelog: [v2.22.0...v2.23.0](https://github.com/openai/openai-python/compare/v2.22.0...v2.23.0) + +### Features + +* **api:** add gpt-realtime-1.5 and gpt-audio-1.5 model options to realtime calls ([3300b61](https://github.com/openai/openai-python/commit/3300b61e1d5a34c9d28ec9cebbebd0de1fa93aa6)) + + +### Chores + +* **internal:** make `test_proxy_environment_variables` more resilient ([6b441e2](https://github.com/openai/openai-python/commit/6b441e2c43df60a773f62308e918d76b8eb3c4d3)) + +## 2.22.0 (2026-02-23) + +Full Changelog: [v2.21.0...v2.22.0](https://github.com/openai/openai-python/compare/v2.21.0...v2.22.0) + +### Features + +* **api:** websockets for responses api ([c01f6fb](https://github.com/openai/openai-python/commit/c01f6fb0d55b7454f73c4904ea7a1954553085dc)) + + +### Chores + +* **internal:** add request options to SSE classes ([cdb4315](https://github.com/openai/openai-python/commit/cdb4315ee29d5260bb373625d74cb523b4e3859c)) +* update mock server docs ([91f4da8](https://github.com/openai/openai-python/commit/91f4da80ec3dba5d3566961560dfd6feb9c2feb0)) + + +### Documentation + +* **api:** add batch size limit to file_batches parameter descriptions ([16ae76a](https://github.com/openai/openai-python/commit/16ae76a20a47f94c91ee2ca0b2ada274633abab3)) +* **api:** enhance method descriptions across audio, chat, realtime, skills, uploads, videos ([21f9e5a](https://github.com/openai/openai-python/commit/21f9e5aaf6ae27f0235fddb3ffa30fe73337f59b)) +* **api:** update safety_identifier documentation in chat completions and responses ([d74bfff](https://github.com/openai/openai-python/commit/d74bfff62c1c2b32d4dc88fd47ae7b1b2a962017)) + +## 2.21.0 (2026-02-13) + +Full Changelog: [v2.20.0...v2.21.0](https://github.com/openai/openai-python/compare/v2.20.0...v2.21.0) + +### Features + +* **api:** container network_policy and skills ([d19de2e](https://github.com/openai/openai-python/commit/d19de2ee5c74413f9dc52684b650df1898dee82b)) + + +### Bug Fixes + +* **structured outputs:** resolve memory leak in parse methods ([#2860](https://github.com/openai/openai-python/issues/2860)) ([6dcbe21](https://github.com/openai/openai-python/commit/6dcbe211f12f8470db542a5cb95724cb933786dd)) +* **webhooks:** preserve method visibility for compatibility checks ([44a8936](https://github.com/openai/openai-python/commit/44a8936d580b770f23fae79659101a27eadafad6)) + + +### Chores + +* **internal:** fix lint error on Python 3.14 ([534f215](https://github.com/openai/openai-python/commit/534f215941f504443d63509e872409a0b1236452)) + + +### Documentation + +* split `api.md` by standalone resources ([96e41b3](https://github.com/openai/openai-python/commit/96e41b398a110212ddec71436b2439343bea87d4)) +* update comment ([63def23](https://github.com/openai/openai-python/commit/63def23b7acd5c6dacf03337fe1bd08439d1dba8)) + +## 2.20.0 (2026-02-10) + +Full Changelog: [v2.19.0...v2.20.0](https://github.com/openai/openai-python/compare/v2.19.0...v2.20.0) + +### Features + +* **api:** support for images in batch api ([28edb6e](https://github.com/openai/openai-python/commit/28edb6e1b7eb30dbb7be49979cee7882e8889264)) + +## 2.19.0 (2026-02-10) + +Full Changelog: [v2.18.0...v2.19.0](https://github.com/openai/openai-python/compare/v2.18.0...v2.19.0) + +### Features + +* **api:** skills and hosted shell ([27fdf68](https://github.com/openai/openai-python/commit/27fdf6820655b5994e3c1eddb3c8d9344a8be744)) + + +### Chores + +* **internal:** bump dependencies ([fae10fd](https://github.com/openai/openai-python/commit/fae10fd6e936a044f8393a454a39906aa325a893)) + +## 2.18.0 (2026-02-09) + +Full Changelog: [v2.17.0...v2.18.0](https://github.com/openai/openai-python/compare/v2.17.0...v2.18.0) + +### Features + +* **api:** add context_management to responses ([137e992](https://github.com/openai/openai-python/commit/137e992b80956401d1867274fa7a0969edfdba54)) +* **api:** responses context_management ([c3bd017](https://github.com/openai/openai-python/commit/c3bd017318347af0a0105a7e975c8d91e22f7941)) + +## 2.17.0 (2026-02-05) + +Full Changelog: [v2.16.0...v2.17.0](https://github.com/openai/openai-python/compare/v2.16.0...v2.17.0) + +### Features + +* **api:** add shell_call_output status field ([1bbaf88](https://github.com/openai/openai-python/commit/1bbaf8865000b338c24c9fdd5e985183feaca10f)) +* **api:** image generation actions for responses; ResponseFunctionCallArgumentsDoneEvent.name ([7d96513](https://github.com/openai/openai-python/commit/7d965135f93f41b0c3dbf3dc9f01796bd9645b6c)) +* **client:** add custom JSON encoder for extended type support ([9f43c8b](https://github.com/openai/openai-python/commit/9f43c8b1a1641db2336cc6d0ec0c6dc470a89103)) + + +### Bug Fixes + +* **client:** undo change to web search Find action ([8f14eb0](https://github.com/openai/openai-python/commit/8f14eb0a74363fdfc648c5cd5c6d34a85b938d3c)) +* **client:** update type for `find_in_page` action ([ec54dde](https://github.com/openai/openai-python/commit/ec54ddeb357e49edd81cc3fe53d549c297e59a07)) + +## 2.16.0 (2026-01-27) + +Full Changelog: [v2.15.0...v2.16.0](https://github.com/openai/openai-python/compare/v2.15.0...v2.16.0) + +### Features + +* **api:** api update ([b97f9f2](https://github.com/openai/openai-python/commit/b97f9f26b9c46ca4519130e60a8bf12ad8d52bf3)) +* **api:** api updates ([9debcc0](https://github.com/openai/openai-python/commit/9debcc02370f5b76a6a609ded18fbf8dea87b9cb)) +* **client:** add support for binary request streaming ([49561d8](https://github.com/openai/openai-python/commit/49561d88279628bc400d1b09aa98765b67018ef1)) + + +### Bug Fixes + +* **api:** mark assistants as deprecated ([0419cbc](https://github.com/openai/openai-python/commit/0419cbcbf1021131c7492321436ed01ca4337835)) + + +### Chores + +* **ci:** upgrade `actions/github-script` ([5139f13](https://github.com/openai/openai-python/commit/5139f13ef35e64dadc65f2ba2bab736977985769)) +* **internal:** update `actions/checkout` version ([f276714](https://github.com/openai/openai-python/commit/f2767144c11833070c0579063ed33918089b4617)) + + +### Documentation + +* **examples:** update Azure Realtime sample to use v1 API ([#2829](https://github.com/openai/openai-python/issues/2829)) ([3b31981](https://github.com/openai/openai-python/commit/3b319819544d629c5b8c206b8b1f6ec6328c6136)) + +## 2.15.0 (2026-01-09) + +Full Changelog: [v2.14.0...v2.15.0](https://github.com/openai/openai-python/compare/v2.14.0...v2.15.0) + +### Features + +* **api:** add new Response completed_at prop ([f077752](https://github.com/openai/openai-python/commit/f077752f4a8364a74f784f8fb1cbe31277e1762b)) + + +### Chores + +* **internal:** codegen related update ([e7daba6](https://github.com/openai/openai-python/commit/e7daba6662a3c30f73d991e96cb19d2b54d772e0)) + +## 2.14.0 (2025-12-19) + +Full Changelog: [v2.13.0...v2.14.0](https://github.com/openai/openai-python/compare/v2.13.0...v2.14.0) + +### Features + +* **api:** slugs for new audio models; make all `model` params accept strings ([e517792](https://github.com/openai/openai-python/commit/e517792b58d1768cfb3432a555a354ae0a9cfa21)) + + +### Bug Fixes + +* use async_to_httpx_files in patch method ([a6af9ee](https://github.com/openai/openai-python/commit/a6af9ee5643197222f328d5e73a80ab3515c32e2)) + + +### Chores + +* **internal:** add `--fix` argument to lint script ([93107ef](https://github.com/openai/openai-python/commit/93107ef36abcfd9c6b1419533a1720031f03caec)) + +## 2.13.0 (2025-12-16) + +Full Changelog: [v2.12.0...v2.13.0](https://github.com/openai/openai-python/compare/v2.12.0...v2.13.0) + +### Features + +* **api:** gpt-image-1.5 ([1c88f03](https://github.com/openai/openai-python/commit/1c88f03bb48aa67426744e5b74f6197f30f61c73)) + + +### Chores + +* **ci:** add CI job to detect breaking changes with the Agents SDK ([#1436](https://github.com/openai/openai-python/issues/1436)) ([237c91e](https://github.com/openai/openai-python/commit/237c91ee6738b6764b139fd7afa68294d3ee0153)) +* **internal:** add missing files argument to base client ([e6d6fd5](https://github.com/openai/openai-python/commit/e6d6fd5989d76358ea5d9abb5949aa87646cbef6)) + +## 2.12.0 (2025-12-15) + +Full Changelog: [v2.11.0...v2.12.0](https://github.com/openai/openai-python/compare/v2.11.0...v2.12.0) + +### Features + +* **api:** api update ([a95c4d0](https://github.com/openai/openai-python/commit/a95c4d0952ff5eb767206574e687cb029a49a4ab)) +* **api:** fix grader input list, add dated slugs for sora-2 ([b2c389b](https://github.com/openai/openai-python/commit/b2c389bf5c3bde50bac2d9f60cce58f4aef44a41)) + +## 2.11.0 (2025-12-11) + +Full Changelog: [v2.10.0...v2.11.0](https://github.com/openai/openai-python/compare/v2.10.0...v2.11.0) + +### Features + +* **api:** gpt 5.2 ([dd9b8e8](https://github.com/openai/openai-python/commit/dd9b8e85cf91fe0d7470143fba10fe950ec740c4)) + +## 2.10.0 (2025-12-10) + +Full Changelog: [v2.9.0...v2.10.0](https://github.com/openai/openai-python/compare/v2.9.0...v2.10.0) + +### Features + +* **api:** make model required for the responses/compact endpoint ([a12936b](https://github.com/openai/openai-python/commit/a12936b18cf19009d4e6d586c9b1958359636dbe)) + + +### Bug Fixes + +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([8f0d230](https://github.com/openai/openai-python/commit/8f0d23066c1edc38a6e9858b054dceaf92ae001b)) + + +### Chores + +* add missing docstrings ([f20a9a1](https://github.com/openai/openai-python/commit/f20a9a18a421ba69622c77ab539509d218e774eb)) +* **internal:** update docstring ([9a993f2](https://github.com/openai/openai-python/commit/9a993f2261b6524aa30b955e006c7ea89f086968)) + +## 2.9.0 (2025-12-04) + +Full Changelog: [v2.8.1...v2.9.0](https://github.com/openai/openai-python/compare/v2.8.1...v2.9.0) + +### Features + +* **api:** gpt-5.1-codex-max and responses/compact ([22f646e](https://github.com/openai/openai-python/commit/22f646e985b7c93782cf695edbe643844cae7017)) + + +### Bug Fixes + +* **client:** avoid mutating user-provided response config object ([#2700](https://github.com/openai/openai-python/issues/2700)) ([e040d22](https://github.com/openai/openai-python/commit/e040d22c2df068e908f69dc6b892e7f8b3fe6e99)) +* ensure streams are always closed ([0b1a27f](https://github.com/openai/openai-python/commit/0b1a27f08639d14dfe40bf80b48e2b8a1a51593c)) +* **streaming:** correct indentation ([575bbac](https://github.com/openai/openai-python/commit/575bbac13b3a57731a4e07b67636ae94463d43fa)) + + +### Chores + +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([22cd586](https://github.com/openai/openai-python/commit/22cd586dbd5484b47f625da55db697691116b22b)) +* **docs:** use environment variables for authentication in code snippets ([c2a3cd5](https://github.com/openai/openai-python/commit/c2a3cd502bfb03f68f62f50aed15a40458c0996e)) +* **internal:** codegen related update ([307a066](https://github.com/openai/openai-python/commit/307a0664383b9d1d4151bc1a05a78c4fdcdcc9b0)) +* update lockfile ([b4109c5](https://github.com/openai/openai-python/commit/b4109c5fcf971ccfb25b4bdaef0bf36999f9eca5)) + +## 2.8.1 (2025-11-17) + +Full Changelog: [v2.8.0...v2.8.1](https://github.com/openai/openai-python/compare/v2.8.0...v2.8.1) + +### Bug Fixes + +* **api:** align types of input items / output items for typescript ([64c9fb3](https://github.com/openai/openai-python/commit/64c9fb3fcc79f0049b3a36bd429faf0600d969f6)) + +## 2.8.0 (2025-11-13) + +Full Changelog: [v2.7.2...v2.8.0](https://github.com/openai/openai-python/compare/v2.7.2...v2.8.0) + +### Features + +* **api:** gpt 5.1 ([8d9f2ca](https://github.com/openai/openai-python/commit/8d9f2cab4cb2e12f6e2ab1de967f858736a656ac)) + + +### Bug Fixes + +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([c7bd234](https://github.com/openai/openai-python/commit/c7bd234b18239fcdbf0edb1b51ca9116c0ac7251)) + +## 2.7.2 (2025-11-10) + +Full Changelog: [v2.7.1...v2.7.2](https://github.com/openai/openai-python/compare/v2.7.1...v2.7.2) + +### Bug Fixes + +* compat with Python 3.14 ([15a7ec8](https://github.com/openai/openai-python/commit/15a7ec8a753d7f57d525fca60c547fd5331cb214)) + + +### Chores + +* **package:** drop Python 3.8 support ([afc14f2](https://github.com/openai/openai-python/commit/afc14f2e42e7a8174f2ff1a5672829b808a716bf)) + +## 2.7.1 (2025-11-04) + +Full Changelog: [v2.7.0...v2.7.1](https://github.com/openai/openai-python/compare/v2.7.0...v2.7.1) + +### Bug Fixes + +* **api:** fix nullability of logprobs ([373b7f6](https://github.com/openai/openai-python/commit/373b7f6e4255dfef3ccd92520011e8ba44e8b7f0)) + +## 2.7.0 (2025-11-03) + +Full Changelog: [v2.6.1...v2.7.0](https://github.com/openai/openai-python/compare/v2.6.1...v2.7.0) + +### Features + +* **api:** Realtime API token_limits, Hybrid searching ranking options ([5b43992](https://github.com/openai/openai-python/commit/5b4399219d7ed326411aec524d25ef2b8e3152fc)) +* **api:** remove InputAudio from ResponseInputContent ([bd70a33](https://github.com/openai/openai-python/commit/bd70a33234741fa68c185105e4f53cc0275a2a50)) + + +### Bug Fixes + +* **client:** close streams without requiring full consumption ([d8bb7d6](https://github.com/openai/openai-python/commit/d8bb7d6d728c5481de4198eebe668b67803ae14a)) +* **readme:** update realtime examples ([#2714](https://github.com/openai/openai-python/issues/2714)) ([d0370a8](https://github.com/openai/openai-python/commit/d0370a8d61fc2f710a34d8aad48f649a9683106d)) +* **uploads:** avoid file handle leak ([4f1b691](https://github.com/openai/openai-python/commit/4f1b691ab4db41aebd397ec41942b43fb0f0743c)) + + +### Chores + +* **internal/tests:** avoid race condition with implicit client cleanup ([933d23b](https://github.com/openai/openai-python/commit/933d23bd8d7809c77e0796becfe052167d44d40a)) +* **internal:** grammar fix (it's -> its) ([f7e9e9e](https://github.com/openai/openai-python/commit/f7e9e9e4f43039f19a41375a6d2b2bdc2264dad7)) + +## 2.6.1 (2025-10-24) + +Full Changelog: [v2.6.0...v2.6.1](https://github.com/openai/openai-python/compare/v2.6.0...v2.6.1) + +### Bug Fixes + +* **api:** docs updates ([d01a0c9](https://github.com/openai/openai-python/commit/d01a0c96ecb94c78b7e16546790c573704b7515b)) + + +### Chores + +* **client:** clean up custom translations code ([cfb9e25](https://github.com/openai/openai-python/commit/cfb9e25855b8eb020abe02cdd99566adf474e821)) + +## 2.6.0 (2025-10-20) + +Full Changelog: [v2.5.0...v2.6.0](https://github.com/openai/openai-python/compare/v2.5.0...v2.6.0) + +### Features + +* **api:** Add responses.input_tokens.count ([6dd09e2](https://github.com/openai/openai-python/commit/6dd09e2829f385f72b28620888d91a4493c96772)) + + +### Bug Fixes + +* **api:** internal openapi updates ([caabd7c](https://github.com/openai/openai-python/commit/caabd7c81f0f557f66dc0089af460185a5816c11)) + +## 2.5.0 (2025-10-17) + +Full Changelog: [v2.4.0...v2.5.0](https://github.com/openai/openai-python/compare/v2.4.0...v2.5.0) + +### Features + +* **api:** api update ([8b280d5](https://github.com/openai/openai-python/commit/8b280d57d6d361bc3a032e030158f6859c445291)) + + +### Chores + +* bump `httpx-aiohttp` version to 0.1.9 ([67f2f0a](https://github.com/openai/openai-python/commit/67f2f0afe51dab9d5899fe18b1a4e86b2c774d10)) + +## 2.4.0 (2025-10-16) + +Full Changelog: [v2.3.0...v2.4.0](https://github.com/openai/openai-python/compare/v2.3.0...v2.4.0) + +### Features + +* **api:** Add support for gpt-4o-transcribe-diarize on audio/transcriptions endpoint ([bdbe9b8](https://github.com/openai/openai-python/commit/bdbe9b8f440209afa2979db4a9eda9579b3d2550)) + + +### Chores + +* fix dangling comment ([da14e99](https://github.com/openai/openai-python/commit/da14e9960608f7ade6f5cdf91967830c8a6c1657)) +* **internal:** detect missing future annotations with ruff ([2672b8f](https://github.com/openai/openai-python/commit/2672b8f0726300f7c62c356f25545ef0b3c0bb2e)) + +## 2.3.0 (2025-10-10) + +Full Changelog: [v2.2.0...v2.3.0](https://github.com/openai/openai-python/compare/v2.2.0...v2.3.0) + +### Features + +* **api:** comparison filter in/not in ([aa49f62](https://github.com/openai/openai-python/commit/aa49f626a6ea9d77ad008badfb3741e16232d62f)) + + +### Chores + +* **package:** bump jiter to >=0.10.0 to support Python 3.14 ([#2618](https://github.com/openai/openai-python/issues/2618)) ([aa445ca](https://github.com/openai/openai-python/commit/aa445cab5c93c6908697fe98e73e16963330b141)) + +## 2.2.0 (2025-10-06) + +Full Changelog: [v2.1.0...v2.2.0](https://github.com/openai/openai-python/compare/v2.1.0...v2.2.0) + +### Features + +* **api:** dev day 2025 launches ([38ac009](https://github.com/openai/openai-python/commit/38ac0093ebb3419b1e2280d0dc2d26c74a2bbbec)) + + +### Bug Fixes + +* **client:** add chatkit to beta resource ([de3e561](https://github.com/openai/openai-python/commit/de3e5619d0a85b17906a9416039ef309e820dc0f)) + +## 2.1.0 (2025-10-02) + +Full Changelog: [v2.0.1...v2.1.0](https://github.com/openai/openai-python/compare/v2.0.1...v2.1.0) + +### Features + +* **api:** add support for realtime calls ([7f7925b](https://github.com/openai/openai-python/commit/7f7925b4074ecbf879714698000e10fa0519d51a)) + +## 2.0.1 (2025-10-01) + +Full Changelog: [v2.0.0...v2.0.1](https://github.com/openai/openai-python/compare/v2.0.0...v2.0.1) + +### Bug Fixes + +* **api:** add status, approval_request_id to MCP tool call ([2a02255](https://github.com/openai/openai-python/commit/2a022553f83b636defcfda3b1c6f4b12d901357b)) + +## 2.0.0 (2025-09-30) + +Full Changelog: [v1.109.1...v2.0.0](https://github.com/openai/openai-python/compare/v1.109.1...v2.0.0) + +### ⚠ BREAKING CHANGES + +* **api:** `ResponseFunctionToolCallOutputItem.output` and `ResponseCustomToolCallOutput.output` now return `string | Array` instead of `string` only. This may break existing callsites that assume `output` is always a string. + +### Features + +* **api:** Support images and files for function call outputs in responses, BatchUsage ([4105376](https://github.com/openai/openai-python/commit/4105376a60293581371fd5635b805b717d24aa19)) + +## 1.109.1 (2025-09-24) + +Full Changelog: [v1.109.0...v1.109.1](https://github.com/openai/openai-python/compare/v1.109.0...v1.109.1) + +### Bug Fixes + +* **compat:** compat with `pydantic<2.8.0` when using additional fields ([5d95ecf](https://github.com/openai/openai-python/commit/5d95ecf7abd65f3e4e273be14c80f9b4cd91ffe8)) + +## 1.109.0 (2025-09-23) + +Full Changelog: [v1.108.2...v1.109.0](https://github.com/openai/openai-python/compare/v1.108.2...v1.109.0) + +### Features + +* **api:** gpt-5-codex ([34502b5](https://github.com/openai/openai-python/commit/34502b5a175f8a10ea8694fcea38fe7308de89ef)) + +## 1.108.2 (2025-09-22) + +Full Changelog: [v1.108.1...v1.108.2](https://github.com/openai/openai-python/compare/v1.108.1...v1.108.2) + +### Bug Fixes + +* **api:** fix mcp tool name ([fd1c673](https://github.com/openai/openai-python/commit/fd1c673fa8d5581b38c69c37aa4fd1fd251259a2)) + + +### Chores + +* **api:** openapi updates for conversations ([3224f6f](https://github.com/openai/openai-python/commit/3224f6f9b4221b954a8f63de66bcaab389164ee5)) +* do not install brew dependencies in ./scripts/bootstrap by default ([6764b00](https://github.com/openai/openai-python/commit/6764b00bcb8aeab41e73d2fcaf6c7a18ea9f7909)) +* improve example values ([20b58e1](https://github.com/openai/openai-python/commit/20b58e164f9f28b9fc562968263fa3eacc6f5c7c)) + +## 1.108.1 (2025-09-19) + +Full Changelog: [v1.108.0...v1.108.1](https://github.com/openai/openai-python/compare/v1.108.0...v1.108.1) + +### Features + +* **api:** add reasoning_text ([18d8e12](https://github.com/openai/openai-python/commit/18d8e12061d1fd4e09d24986ff6e38c5063013e9)) + + +### Chores + +* **types:** change optional parameter type from NotGiven to Omit ([acc190a](https://github.com/openai/openai-python/commit/acc190a29526e64db6074e7f21aca800423c128c)) + +## 1.108.0 (2025-09-17) + +Full Changelog: [v1.107.3...v1.108.0](https://github.com/openai/openai-python/compare/v1.107.3...v1.108.0) + +### Features + +* **api:** type updates for conversations, reasoning_effort and results for evals ([c2ee28c](https://github.com/openai/openai-python/commit/c2ee28c1b77eed98766fbb01cf1ad2ee240f412e)) + + +### Chores + +* **internal:** update pydantic dependency ([369d10a](https://github.com/openai/openai-python/commit/369d10a40dfe744f6bfc10c99eb1f58176500120)) + +## 1.107.3 (2025-09-15) + +Full Changelog: [v1.107.2...v1.107.3](https://github.com/openai/openai-python/compare/v1.107.2...v1.107.3) + +### Chores + +* **api:** docs and spec refactoring ([9bab5da](https://github.com/openai/openai-python/commit/9bab5da1802c3575c58e73ed1470dd5fa61fd1d2)) +* **tests:** simplify `get_platform` test ([0b1f6a2](https://github.com/openai/openai-python/commit/0b1f6a28d5a59e10873264e976d2e332903eef29)) + +## 1.107.2 (2025-09-12) + +Full Changelog: [v1.107.1...v1.107.2](https://github.com/openai/openai-python/compare/v1.107.1...v1.107.2) + +### Chores + +* **api:** Minor docs and type updates for realtime ([ab6a10d](https://github.com/openai/openai-python/commit/ab6a10da4ed7e6386695b6f5f29149d4870f85c9)) +* **tests:** simplify `get_platform` test ([01f03e0](https://github.com/openai/openai-python/commit/01f03e0ad1f9ab3f2ed8b7c13d652263c6d06378)) + +## 1.107.1 (2025-09-10) + +Full Changelog: [v1.107.0...v1.107.1](https://github.com/openai/openai-python/compare/v1.107.0...v1.107.1) + +### Chores + +* **api:** fix realtime GA types ([570fc5a](https://github.com/openai/openai-python/commit/570fc5a28ada665fd658b24675361680cfeb086f)) + +## 1.107.0 (2025-09-08) + +Full Changelog: [v1.106.1...v1.107.0](https://github.com/openai/openai-python/compare/v1.106.1...v1.107.0) + +### Features + +* **api:** ship the RealtimeGA API shape ([dc319d8](https://github.com/openai/openai-python/commit/dc319d8bbb3a20108399c1d15f98e63bdd84eb5c)) + + +### Chores + +* **internal:** codegen related update ([b79b7ca](https://github.com/openai/openai-python/commit/b79b7ca3a72009a036db0a344b500f616ca0443f)) + +## 1.106.1 (2025-09-04) + +Full Changelog: [v1.106.0...v1.106.1](https://github.com/openai/openai-python/compare/v1.106.0...v1.106.1) + +### Chores + +* **internal:** move mypy configurations to `pyproject.toml` file ([ca413a2](https://github.com/openai/openai-python/commit/ca413a277496c3b883b103ad1138a886e89ae15e)) + +## 1.106.0 (2025-09-04) + +Full Changelog: [v1.105.0...v1.106.0](https://github.com/openai/openai-python/compare/v1.105.0...v1.106.0) + +### Features + +* **client:** support callable api_key ([#2588](https://github.com/openai/openai-python/issues/2588)) ([e1bad01](https://github.com/openai/openai-python/commit/e1bad015b8a2b98bfee955a24bc931347a58efc1)) +* improve future compat with pydantic v3 ([6645d93](https://github.com/openai/openai-python/commit/6645d9317a240982928b92c2f4af0381db6edc09)) + +## 1.105.0 (2025-09-03) + +Full Changelog: [v1.104.2...v1.105.0](https://github.com/openai/openai-python/compare/v1.104.2...v1.105.0) + +### Features + +* **api:** Add gpt-realtime models ([8502041](https://github.com/openai/openai-python/commit/85020414808314df9cb42e020b11baff12f18f16)) + +## 1.104.2 (2025-09-02) + +Full Changelog: [v1.104.1...v1.104.2](https://github.com/openai/openai-python/compare/v1.104.1...v1.104.2) + +### Bug Fixes + +* **types:** add aliases back for web search tool types ([2521cd8](https://github.com/openai/openai-python/commit/2521cd8445906e418dbae783b0d7c375ad91d49d)) + +## 1.104.1 (2025-09-02) + +Full Changelog: [v1.104.0...v1.104.1](https://github.com/openai/openai-python/compare/v1.104.0...v1.104.1) + +### Chores + +* **api:** manual updates for ResponseInputAudio ([0db5061](https://github.com/openai/openai-python/commit/0db50619663656ba97bba30ab640bbb33683d196)) + +## 1.104.0 (2025-09-02) + +Full Changelog: [v1.103.0...v1.104.0](https://github.com/openai/openai-python/compare/v1.103.0...v1.104.0) + +### Features + +* **types:** replace List[str] with SequenceNotStr in params ([bc00bda](https://github.com/openai/openai-python/commit/bc00bda880a80089be8a1758c016266ca72dab2c)) + + +### Bug Fixes + +* **types:** update more types to use SequenceNotStr ([cff135c](https://github.com/openai/openai-python/commit/cff135cb7059ef1bf8f9b101a83529fc0cee37c4)) +* **types:** update some types to SequenceNotStr ([03f8b88](https://github.com/openai/openai-python/commit/03f8b88a0d428b74a7822e678a60d0ef106ea961)) + + +### Chores + +* remove unused import ([ac7795b](https://github.com/openai/openai-python/commit/ac7795b50d956ec5dc468302e8e3579a0467edcb)) + +## 1.103.0 (2025-09-02) + +Full Changelog: [v1.102.0...v1.103.0](https://github.com/openai/openai-python/compare/v1.102.0...v1.103.0) + +### Features + +* **api:** realtime API updates ([b7c2ddc](https://github.com/openai/openai-python/commit/b7c2ddc5e5dedda01015b3d0e14ea6eb68c282d3)) + + +### Bug Fixes + +* **responses:** add missing params to stream() method ([bfc0673](https://github.com/openai/openai-python/commit/bfc06732ffe3764cb95cef9f23b4b5c0d312826a)) + + +### Chores + +* bump `inline-snapshot` version to 0.28.0 ([#2590](https://github.com/openai/openai-python/issues/2590)) ([a6b0872](https://github.com/openai/openai-python/commit/a6b087226587d4cc4f59f1f09a595921b2823ef2)) +* **client:** format imports ([7ae3020](https://github.com/openai/openai-python/commit/7ae3020b3ca7de21e6e9a0a1c40908e655f6cad5)) +* **internal:** add Sequence related utils ([d3d72b9](https://github.com/openai/openai-python/commit/d3d72b9ce3c0885bf2b6934ac57d9e84f8653208)) +* **internal:** fix formatting ([3ab273f](https://github.com/openai/openai-python/commit/3ab273f21e601f088be7502b7bb5d249fc386d6a)) +* **internal:** minor formatting change ([478a348](https://github.com/openai/openai-python/commit/478a34881c968e9cab9d93ac2cf8da2fcb37c46c)) +* **internal:** update pyright exclude list ([66e440f](https://github.com/openai/openai-python/commit/66e440fac3ca388400392c64211450dedc491c11)) + +## 1.102.0 (2025-08-26) + +Full Changelog: [v1.101.0...v1.102.0](https://github.com/openai/openai-python/compare/v1.101.0...v1.102.0) + +### Features + +* **api:** add web search filters ([1c199a8](https://github.com/openai/openai-python/commit/1c199a8dc85f773ae656fe850fdfb80b91f8f6b1)) + + +### Bug Fixes + +* avoid newer type syntax ([bd0c668](https://github.com/openai/openai-python/commit/bd0c668d754b89c78c2c9ad2e081258c04aaece6)) + + +### Chores + +* **internal:** change ci workflow machines ([3e129d5](https://github.com/openai/openai-python/commit/3e129d5e49f6391dea7497132cb3cfed8e5dd8ee)) +* **internal:** codegen related update ([b6dc170](https://github.com/openai/openai-python/commit/b6dc170832d719fc5028cfe234748c22e6e168aa)) + +## 1.101.0 (2025-08-21) + +Full Changelog: [v1.100.3...v1.101.0](https://github.com/openai/openai-python/compare/v1.100.3...v1.101.0) + +### Features + +* **api:** Add connectors support for MCP tool ([a47f962](https://github.com/openai/openai-python/commit/a47f962daf579c142b8af5579be732772b688a29)) +* **api:** adding support for /v1/conversations to the API ([e30bcbc](https://github.com/openai/openai-python/commit/e30bcbc0cb7c827af779bee6971f976261abfb67)) + + +### Chores + +* update github action ([7333b28](https://github.com/openai/openai-python/commit/7333b282718a5f6977f30e1a2548207b3a089bd4)) + +## 1.100.3 (2025-08-20) + +Full Changelog: [v1.100.2...v1.100.3](https://github.com/openai/openai-python/compare/v1.100.2...v1.100.3) + +### Chores + +* **internal/ci:** setup breaking change detection ([ca2f936](https://github.com/openai/openai-python/commit/ca2f93600238e875f26395faf6afbefaf15b7c97)) + +## 1.100.2 (2025-08-19) + +Full Changelog: [v1.100.1...v1.100.2](https://github.com/openai/openai-python/compare/v1.100.1...v1.100.2) + +### Chores + +* **api:** accurately represent shape for verbosity on Chat Completions ([c39d5fd](https://github.com/openai/openai-python/commit/c39d5fd3f5429c6d41f257669a1dd4c67a477455)) + +## 1.100.1 (2025-08-18) + +Full Changelog: [v1.100.0...v1.100.1](https://github.com/openai/openai-python/compare/v1.100.0...v1.100.1) + +### Bug Fixes + +* **types:** revert response text config deletion ([ac4fb19](https://github.com/openai/openai-python/commit/ac4fb1922ae125c8310c30e402932e8bb2976f58)) + +## 1.100.0 (2025-08-18) + +Full Changelog: [v1.99.9...v1.100.0](https://github.com/openai/openai-python/compare/v1.99.9...v1.100.0) + +### Features + +* **api:** add new text parameters, expiration options ([e3dfa7c](https://github.com/openai/openai-python/commit/e3dfa7c417b8c750ff62d98650e75e72ad9b1477)) + +## 1.99.9 (2025-08-12) + +Full Changelog: [v1.99.8...v1.99.9](https://github.com/openai/openai-python/compare/v1.99.8...v1.99.9) + +### Bug Fixes + +* **types:** actually fix ChatCompletionMessageToolCall type ([20cb0c8](https://github.com/openai/openai-python/commit/20cb0c86d598e196386ff43db992f6497eb756d0)) + +## 1.99.8 (2025-08-11) + +Full Changelog: [v1.99.7...v1.99.8](https://github.com/openai/openai-python/compare/v1.99.7...v1.99.8) + +### Bug Fixes + +* **internal/tests:** correct snapshot update comment ([2784a7a](https://github.com/openai/openai-python/commit/2784a7a7da24ddba74b5717f07d67546864472b9)) +* **types:** revert ChatCompletionMessageToolCallUnion breaking change ([ba54e03](https://github.com/openai/openai-python/commit/ba54e03bc2d21825d891685bf3bad4a9253cbeb0)) + + +### Chores + +* **internal/tests:** add inline snapshot format command ([8107db8](https://github.com/openai/openai-python/commit/8107db8ff738baa65fe4cf2f2d7f1acd29219c78)) +* **internal:** fix formatting ([f03a03d](https://github.com/openai/openai-python/commit/f03a03de8c84740209d021598ff8bf56b6d3c684)) +* **tests:** add responses output_text test ([971347b](https://github.com/openai/openai-python/commit/971347b3a05f79c51abd11c86b382ca73c28cefb)) + + +### Refactors + +* **tests:** share snapshot utils ([791c567](https://github.com/openai/openai-python/commit/791c567cd87fb8d587965773b1da0404c7848c68)) + +## 1.99.7 (2025-08-11) + +Full Changelog: [v1.99.6...v1.99.7](https://github.com/openai/openai-python/compare/v1.99.6...v1.99.7) + +### Bug Fixes + +* **types:** rename ChatCompletionMessageToolCallParam ([48085e2](https://github.com/openai/openai-python/commit/48085e2f473799d079e71d48d2f5612a6fbeb976)) +* **types:** revert ChatCompletionMessageToolCallParam to a TypedDict ([c8e9cec](https://github.com/openai/openai-python/commit/c8e9cec5c93cc022fff546f27161717f769d1f81)) + +## 1.99.6 (2025-08-09) + +Full Changelog: [v1.99.5...v1.99.6](https://github.com/openai/openai-python/compare/v1.99.5...v1.99.6) + +### Bug Fixes + +* **types:** re-export more tool call types ([8fe5741](https://github.com/openai/openai-python/commit/8fe574131cfe8f0453788cc6105d22834e7c102f)) + + +### Chores + +* **internal:** update comment in script ([e407bb5](https://github.com/openai/openai-python/commit/e407bb52112ad73e5eedf929434ee4ff7ac5a5a8)) +* update @stainless-api/prism-cli to v5.15.0 ([a1883fc](https://github.com/openai/openai-python/commit/a1883fcdfa02b81e5129bdb43206597a51f885fa)) + +## 1.99.5 (2025-08-08) + +Full Changelog: [v1.99.4...v1.99.5](https://github.com/openai/openai-python/compare/v1.99.4...v1.99.5) + +### Bug Fixes + +* **client:** fix verbosity parameter location in Responses ([2764ff4](https://github.com/openai/openai-python/commit/2764ff459eb8b309d25b39b40e363b16a5b95019)) + +## 1.99.4 (2025-08-08) + +Full Changelog: [v1.99.3...v1.99.4](https://github.com/openai/openai-python/compare/v1.99.3...v1.99.4) + +### Bug Fixes + +* **types:** rename chat completion tool ([8d3bf88](https://github.com/openai/openai-python/commit/8d3bf88f5bc11cf30b8b050c24b2cc5a3807614f)) +* **types:** revert ChatCompletionToolParam to a TypedDict ([3f4ae72](https://github.com/openai/openai-python/commit/3f4ae725af53e631ddc128c1c6862ecf0b08e073)) + +## 1.99.3 (2025-08-07) + +Full Changelog: [v1.99.2...v1.99.3](https://github.com/openai/openai-python/compare/v1.99.2...v1.99.3) + +### Bug Fixes + +* **responses:** add output_text back ([585a4f1](https://github.com/openai/openai-python/commit/585a4f15e5a088bf8afee745bc4a7803775ac283)) + +## 1.99.2 (2025-08-07) + +Full Changelog: [v1.99.1...v1.99.2](https://github.com/openai/openai-python/compare/v1.99.1...v1.99.2) + +### Features + +* **api:** adds GPT-5 and new API features: platform.openai.com/docs/guides/gpt-5 ([ed370d8](https://github.com/openai/openai-python/commit/ed370d805e4d5d1ec14a136f5b2516751277059f)) + + +### Bug Fixes + +* **types:** correct tool types ([0c57bd7](https://github.com/openai/openai-python/commit/0c57bd7f2183a20b714d04edea380a4df0464a40)) + + +### Chores + +* **tests:** bump inline-snapshot dependency ([e236fde](https://github.com/openai/openai-python/commit/e236fde99a335fcaac9760f324e4807ce2cf7cba)) + +## 1.99.1 (2025-08-05) + +Full Changelog: [v1.99.0...v1.99.1](https://github.com/openai/openai-python/compare/v1.99.0...v1.99.1) + +### Bug Fixes + +* **internal:** correct event imports ([2a6d143](https://github.com/openai/openai-python/commit/2a6d1436288a07f67f6afefe5c0b5d6ae32d7e70)) + +## 1.99.0 (2025-08-05) + +Full Changelog: [v1.98.0...v1.99.0](https://github.com/openai/openai-python/compare/v1.98.0...v1.99.0) + +### Features + +* **api:** manual updates ([d4aa726](https://github.com/openai/openai-python/commit/d4aa72602bf489ef270154b881b3967d497d4220)) +* **client:** support file upload requests ([0772e6e](https://github.com/openai/openai-python/commit/0772e6ed8310e15539610b003dd73f72f474ec0c)) + + +### Bug Fixes + +* add missing prompt_cache_key & prompt_cache_key params ([00b49ae](https://github.com/openai/openai-python/commit/00b49ae8d44ea396ac0536fc3ce4658fc669e2f5)) + + +### Chores + +* **internal:** fix ruff target version ([aa6b252](https://github.com/openai/openai-python/commit/aa6b252ae0f25f195dede15755e05dd2f542f42d)) + +## 1.98.0 (2025-07-30) + +Full Changelog: [v1.97.2...v1.98.0](https://github.com/openai/openai-python/compare/v1.97.2...v1.98.0) + +### Features + +* **api:** manual updates ([88a8036](https://github.com/openai/openai-python/commit/88a8036c5ea186f36c57029ef4501a0833596f56)) + +## 1.97.2 (2025-07-30) + +Full Changelog: [v1.97.1...v1.97.2](https://github.com/openai/openai-python/compare/v1.97.1...v1.97.2) + +### Chores + +* **client:** refactor streaming slightly to better future proof it ([71c0c74](https://github.com/openai/openai-python/commit/71c0c747132221b798e419bc5a37baf67173d34e)) +* **project:** add settings file for vscode ([29c22c9](https://github.com/openai/openai-python/commit/29c22c90fd229983355089f95d0bba9de15efedb)) + +## 1.97.1 (2025-07-22) + +Full Changelog: [v1.97.0...v1.97.1](https://github.com/openai/openai-python/compare/v1.97.0...v1.97.1) + +### Bug Fixes + +* **parsing:** ignore empty metadata ([58c359f](https://github.com/openai/openai-python/commit/58c359ff67fd6103268e4405600fd58844b6f27b)) +* **parsing:** parse extra field types ([d524b7e](https://github.com/openai/openai-python/commit/d524b7e201418ccc9b5c2206da06d1be011808e5)) + + +### Chores + +* **api:** event shapes more accurate ([f3a9a92](https://github.com/openai/openai-python/commit/f3a9a9229280ecb7e0b2779dd44290df6d9824ef)) + +## 1.97.0 (2025-07-16) + +Full Changelog: [v1.96.1...v1.97.0](https://github.com/openai/openai-python/compare/v1.96.1...v1.97.0) + +### Features + +* **api:** manual updates ([ed8e899](https://github.com/openai/openai-python/commit/ed8e89953d11bd5f44fa531422bdbb7a577ab426)) + +## 1.96.1 (2025-07-15) + +Full Changelog: [v1.96.0...v1.96.1](https://github.com/openai/openai-python/compare/v1.96.0...v1.96.1) + +### Chores + +* **api:** update realtime specs ([b68b71b](https://github.com/openai/openai-python/commit/b68b71b178719e0b49ecfe34486b9d9ac0627924)) + +## 1.96.0 (2025-07-15) + +Full Changelog: [v1.95.1...v1.96.0](https://github.com/openai/openai-python/compare/v1.95.1...v1.96.0) + +### Features + +* clean up environment call outs ([87c2e97](https://github.com/openai/openai-python/commit/87c2e979e0ec37347b7f595c2696408acd25fe20)) + + +### Chores + +* **api:** update realtime specs, build config ([bf06d88](https://github.com/openai/openai-python/commit/bf06d88b33f9af82a51d9a8af5b7a38925906f7a)) + +## 1.95.1 (2025-07-11) + +Full Changelog: [v1.95.0...v1.95.1](https://github.com/openai/openai-python/compare/v1.95.0...v1.95.1) + +### Bug Fixes + +* **client:** don't send Content-Type header on GET requests ([182b763](https://github.com/openai/openai-python/commit/182b763065fbaaf68491a7e4a15fcb23cac361de)) + +## 1.95.0 (2025-07-10) + +Full Changelog: [v1.94.0...v1.95.0](https://github.com/openai/openai-python/compare/v1.94.0...v1.95.0) + +### Features + +* **api:** add file_url, fix event ID ([265e216](https://github.com/openai/openai-python/commit/265e216396196d66cdfb5f92c5ef1a2a6ff27b5b)) + + +### Chores + +* **readme:** fix version rendering on pypi ([1eee5ca](https://github.com/openai/openai-python/commit/1eee5cabf2fd93877cd3ba85d0c6ed2ffd5f159f)) + +## 1.94.0 (2025-07-10) + +Full Changelog: [v1.93.3...v1.94.0](https://github.com/openai/openai-python/compare/v1.93.3...v1.94.0) + +### Features + +* **api:** return better error message on missing embedding ([#2369](https://github.com/openai/openai-python/issues/2369)) ([e53464a](https://github.com/openai/openai-python/commit/e53464ae95f6a041f3267762834e6156c5ce1b57)) + +## 1.93.3 (2025-07-09) + +Full Changelog: [v1.93.2...v1.93.3](https://github.com/openai/openai-python/compare/v1.93.2...v1.93.3) + +### Bug Fixes + +* **parsing:** correctly handle nested discriminated unions ([fc8a677](https://github.com/openai/openai-python/commit/fc8a67715d8f1b45d8639b8b6f9f6590fe358734)) + +## 1.93.2 (2025-07-08) + +Full Changelog: [v1.93.1...v1.93.2](https://github.com/openai/openai-python/compare/v1.93.1...v1.93.2) + +### Chores + +* **internal:** bump pinned h11 dep ([4fca6ae](https://github.com/openai/openai-python/commit/4fca6ae2d0d7f27cbac8d06c3917932767c8c6b8)) +* **package:** mark python 3.13 as supported ([2229047](https://github.com/openai/openai-python/commit/2229047b8a549df16c617bddfe3b4521cfd257a5)) + +## 1.93.1 (2025-07-07) + +Full Changelog: [v1.93.0...v1.93.1](https://github.com/openai/openai-python/compare/v1.93.0...v1.93.1) + +### Bug Fixes + +* **ci:** correct conditional ([de6a9ce](https://github.com/openai/openai-python/commit/de6a9ce078731d60b0bdc42a9322548c575f11a3)) +* **responses:** add missing arguments to parse ([05590ec](https://github.com/openai/openai-python/commit/05590ec2a96399afd05baf5a3ee1d9a744f09c40)) +* **vector stores:** add missing arguments to files.create_and_poll ([3152134](https://github.com/openai/openai-python/commit/3152134510532ec7c522d6b50a820deea205b602)) +* **vector stores:** add missing arguments to files.upload_and_poll ([9d4f425](https://github.com/openai/openai-python/commit/9d4f42569d5b59311453b1b11ee1dd2e8a271268)) + + +### Chores + +* **ci:** change upload type ([cd4aa88](https://github.com/openai/openai-python/commit/cd4aa889c50581d861728c9606327992485f0d0d)) +* **ci:** only run for pushes and fork pull requests ([f89c7eb](https://github.com/openai/openai-python/commit/f89c7eb46c6f081254715d75543cbee3ffa83822)) +* **internal:** codegen related update ([bddb8d2](https://github.com/openai/openai-python/commit/bddb8d2091455920e8526068d64f3f8a5cac7ae6)) +* **tests:** ensure parse method is in sync with create ([4f58e18](https://github.com/openai/openai-python/commit/4f58e187c12dc8b2c33e9cca284b0429e5cc4de5)) +* **tests:** ensure vector store files create and poll method is in sync ([0fe75a2](https://github.com/openai/openai-python/commit/0fe75a28f6109b2d25b015dc99472a06693e0e9f)) + +## 1.93.0 (2025-06-27) + +Full Changelog: [v1.92.3...v1.93.0](https://github.com/openai/openai-python/compare/v1.92.3...v1.93.0) + +### Features + +* **cli:** add support for fine_tuning.jobs ([#1224](https://github.com/openai/openai-python/issues/1224)) ([e362bfd](https://github.com/openai/openai-python/commit/e362bfd10dfd04176560b964470ab0c517c601f3)) + +## 1.92.3 (2025-06-27) + +Full Changelog: [v1.92.2...v1.92.3](https://github.com/openai/openai-python/compare/v1.92.2...v1.92.3) + +### Bug Fixes + +* **client:** avoid encoding error with empty API keys ([5a3e64e](https://github.com/openai/openai-python/commit/5a3e64e0cc761dbaa613fb22ec16e7e73c3bcf72)) + + +### Documentation + +* **examples/realtime:** mention macOS requirements ([#2142](https://github.com/openai/openai-python/issues/2142)) ([27bf6b2](https://github.com/openai/openai-python/commit/27bf6b2a933c61d5ec23fd266148af888f69f5c1)) + +## 1.92.2 (2025-06-26) + +Full Changelog: [v1.92.1...v1.92.2](https://github.com/openai/openai-python/compare/v1.92.1...v1.92.2) + +### Chores + +* **api:** remove unsupported property ([ec24408](https://github.com/openai/openai-python/commit/ec2440864e03278144d7f58b97c31d87903e0843)) + +## 1.92.1 (2025-06-26) + +Full Changelog: [v1.92.0...v1.92.1](https://github.com/openai/openai-python/compare/v1.92.0...v1.92.1) + +### Chores + +* **client:** sync stream/parse methods over ([e2536cf](https://github.com/openai/openai-python/commit/e2536cfd74224047cece9c2ad86f0ffe51c0667c)) +* **docs:** update README to include links to docs on Webhooks ([ddbf9f1](https://github.com/openai/openai-python/commit/ddbf9f1dc47a32257716189f2056b45933328c9c)) + +## 1.92.0 (2025-06-26) + +Full Changelog: [v1.91.0...v1.92.0](https://github.com/openai/openai-python/compare/v1.91.0...v1.92.0) + +### Features + +* **api:** webhook and deep research support ([d3bb116](https://github.com/openai/openai-python/commit/d3bb116f34f470502f902b88131deec43a953b12)) +* **client:** move stream and parse out of beta ([0e358ed](https://github.com/openai/openai-python/commit/0e358ed66b317038705fb38958a449d284f3cb88)) + + +### Bug Fixes + +* **ci:** release-doctor — report correct token name ([ff8c556](https://github.com/openai/openai-python/commit/ff8c5561e44e8a0902732b5934c97299d2c98d4e)) + + +### Chores + +* **internal:** add tests for breaking change detection ([710fe8f](https://github.com/openai/openai-python/commit/710fe8fd5f9e33730338341680152d3f2556dfa0)) +* **tests:** skip some failing tests on the latest python versions ([93ccc38](https://github.com/openai/openai-python/commit/93ccc38a8ef1575d77d33d031666d07d10e4af72)) + +## 1.91.0 (2025-06-23) + +Full Changelog: [v1.90.0...v1.91.0](https://github.com/openai/openai-python/compare/v1.90.0...v1.91.0) + +### Features + +* **api:** update api shapes for usage and code interpreter ([060d566](https://github.com/openai/openai-python/commit/060d5661e4a1fcdb953c52facd3e668ee80f9295)) + +## 1.90.0 (2025-06-20) + +Full Changelog: [v1.89.0...v1.90.0](https://github.com/openai/openai-python/compare/v1.89.0...v1.90.0) + +### Features + +* **api:** make model and inputs not required to create response ([11bd62e](https://github.com/openai/openai-python/commit/11bd62eb7e46eec748edaf2e0cecf253ffc1202c)) + +## 1.89.0 (2025-06-20) + +Full Changelog: [v1.88.0...v1.89.0](https://github.com/openai/openai-python/compare/v1.88.0...v1.89.0) + +### Features + +* **client:** add support for aiohttp ([9218b07](https://github.com/openai/openai-python/commit/9218b07727bf6f6eb00953df66de6ab061fecddb)) + + +### Bug Fixes + +* **tests:** fix: tests which call HTTP endpoints directly with the example parameters ([35bcc4b](https://github.com/openai/openai-python/commit/35bcc4b80bdbaa31108650f2a515902e83794e5a)) + + +### Chores + +* **readme:** update badges ([68044ee](https://github.com/openai/openai-python/commit/68044ee85d1bf324b17d3f60c914df4725d47fc8)) + +## 1.88.0 (2025-06-17) + +Full Changelog: [v1.87.0...v1.88.0](https://github.com/openai/openai-python/compare/v1.87.0...v1.88.0) + +### Features + +* **api:** manual updates ([5d18a84](https://github.com/openai/openai-python/commit/5d18a8448ecbe31597e98ec7f64d7050c831901e)) + + +### Chores + +* **ci:** enable for pull requests ([542b0ce](https://github.com/openai/openai-python/commit/542b0ce98f14ccff4f9e1bcbd3a9ea5e4f846638)) +* **internal:** minor formatting ([29d723d](https://github.com/openai/openai-python/commit/29d723d1f1baf2a5843293c8647dc7baa16d56d1)) + +## 1.87.0 (2025-06-16) + +Full Changelog: [v1.86.0...v1.87.0](https://github.com/openai/openai-python/compare/v1.86.0...v1.87.0) + +### Features + +* **api:** add reusable prompt IDs ([36bfe6e](https://github.com/openai/openai-python/commit/36bfe6e8ae12a31624ba1a360d9260f0aeec448a)) + + +### Bug Fixes + +* **client:** update service_tier on `client.beta.chat.completions` ([aa488d5](https://github.com/openai/openai-python/commit/aa488d5cf210d8640f87216538d4ff79d7181f2a)) + + +### Chores + +* **internal:** codegen related update ([b1a31e5](https://github.com/openai/openai-python/commit/b1a31e5ef4387d9f82cf33f9461371651788d381)) +* **internal:** update conftest.py ([bba0213](https://github.com/openai/openai-python/commit/bba0213842a4c161f2235e526d50901a336eecef)) +* **tests:** add tests for httpx client instantiation & proxies ([bc93712](https://github.com/openai/openai-python/commit/bc9371204f457aee9ed9b6ec1b61c2084f32faf1)) + +## 1.86.0 (2025-06-10) + +Full Changelog: [v1.85.0...v1.86.0](https://github.com/openai/openai-python/compare/v1.85.0...v1.86.0) + +### Features + +* **api:** Add o3-pro model IDs ([d8dd80b](https://github.com/openai/openai-python/commit/d8dd80b1b4e6c73687d7acb6c3f62f0bf4b8282c)) + +## 1.85.0 (2025-06-09) + +Full Changelog: [v1.84.0...v1.85.0](https://github.com/openai/openai-python/compare/v1.84.0...v1.85.0) + +### Features + +* **api:** Add tools and structured outputs to evals ([002cc7b](https://github.com/openai/openai-python/commit/002cc7bb3c315d95b81c2e497f55d21be7fd26f8)) + + +### Bug Fixes + +* **responses:** support raw responses for `parse()` ([d459943](https://github.com/openai/openai-python/commit/d459943cc1c81cf9ce5c426edd3ef9112fdf6723)) + +## 1.84.0 (2025-06-03) + +Full Changelog: [v1.83.0...v1.84.0](https://github.com/openai/openai-python/compare/v1.83.0...v1.84.0) + +### Features + +* **api:** add new realtime and audio models, realtime session options ([0acd0da](https://github.com/openai/openai-python/commit/0acd0da6bc0468c6c857711bc5e77d0bc6d31be6)) + + +### Chores + +* **api:** update type names ([1924559](https://github.com/openai/openai-python/commit/192455913b38bf0323ddd0e2b1499b114e2111a1)) + +## 1.83.0 (2025-06-02) + +Full Changelog: [v1.82.1...v1.83.0](https://github.com/openai/openai-python/compare/v1.82.1...v1.83.0) + +### Features + +* **api:** Config update for pakrym-stream-param ([88bcf3a](https://github.com/openai/openai-python/commit/88bcf3af9ce8ffa8347547d4d30aacac1ceba939)) +* **client:** add follow_redirects request option ([26d715f](https://github.com/openai/openai-python/commit/26d715f4e9b0f2b19e2ac16acc796a949338e1e1)) + + +### Bug Fixes + +* **api:** Fix evals and code interpreter interfaces ([2650159](https://github.com/openai/openai-python/commit/2650159f6d01f6eb481cf8c7942142e4fd21ce44)) +* **client:** return binary content from `get /containers/{container_id}/files/{file_id}/content` ([f7c80c4](https://github.com/openai/openai-python/commit/f7c80c4368434bd0be7436375076ba33a62f63b5)) + + +### Chores + +* **api:** mark some methods as deprecated ([3e2ca57](https://github.com/openai/openai-python/commit/3e2ca571cb6cdd9e15596590605b2f98a4c5a42e)) +* deprecate Assistants API ([9d166d7](https://github.com/openai/openai-python/commit/9d166d795e03dea49af680ec9597e9497522187c)) +* **docs:** remove reference to rye shell ([c7978e9](https://github.com/openai/openai-python/commit/c7978e9f1640c311022988fcd716cbb5c865daa8)) + +## 1.82.1 (2025-05-29) + +Full Changelog: [v1.82.0...v1.82.1](https://github.com/openai/openai-python/compare/v1.82.0...v1.82.1) + +### Bug Fixes + +* **responses:** don't include `parsed_arguments` when re-serialising ([6d04193](https://github.com/openai/openai-python/commit/6d041937963ce452affcfb3553146ee51acfeb7a)) + + +### Chores + +* **internal:** fix release workflows ([361a909](https://github.com/openai/openai-python/commit/361a909a0cc83e5029ea425fd72202ffa8d1a46a)) + +## 1.82.0 (2025-05-22) + +Full Changelog: [v1.81.0...v1.82.0](https://github.com/openai/openai-python/compare/v1.81.0...v1.82.0) + +### Features + +* **api:** new streaming helpers for background responses ([2a65d4d](https://github.com/openai/openai-python/commit/2a65d4de0aaba7801edd0df10f225530fd4969bd)) + + +### Bug Fixes + +* **azure:** mark images/edits as a deployment endpoint [#2371](https://github.com/openai/openai-python/issues/2371) ([5d1d5b4](https://github.com/openai/openai-python/commit/5d1d5b4b6072afe9fd7909b1a36014c8c11c1ad6)) + + +### Documentation + +* **readme:** another async example fix ([9ec8289](https://github.com/openai/openai-python/commit/9ec8289041f395805c67efd97847480f84eb9dac)) +* **readme:** fix async example ([37d0b25](https://github.com/openai/openai-python/commit/37d0b25b6e82cd381e5d1aa6e28f1a1311d02353)) + +## 1.81.0 (2025-05-21) + +Full Changelog: [v1.80.0...v1.81.0](https://github.com/openai/openai-python/compare/v1.80.0...v1.81.0) + +### Features + +* **api:** add container endpoint ([054a210](https://github.com/openai/openai-python/commit/054a210289d7e0db22d2d2a61bbe4d4d9cc0cb47)) + +## 1.80.0 (2025-05-21) + +Full Changelog: [v1.79.0...v1.80.0](https://github.com/openai/openai-python/compare/v1.79.0...v1.80.0) + +### Features + +* **api:** new API tools ([d36ae52](https://github.com/openai/openai-python/commit/d36ae528d55fe87067c4b8c6b2c947cbad5e5002)) + + +### Chores + +* **docs:** grammar improvements ([e746145](https://github.com/openai/openai-python/commit/e746145a12b5335d8841aff95c91bbbde8bae8e3)) + +## 1.79.0 (2025-05-16) + +Full Changelog: [v1.78.1...v1.79.0](https://github.com/openai/openai-python/compare/v1.78.1...v1.79.0) + +### Features + +* **api:** further updates for evals API ([32c99a6](https://github.com/openai/openai-python/commit/32c99a6f5885d4bf3511a7f06b70000edd274301)) +* **api:** manual updates ([25245e5](https://github.com/openai/openai-python/commit/25245e5e3d0713abfb65b760aee1f12bc61deb41)) +* **api:** responses x eval api ([fd586cb](https://github.com/openai/openai-python/commit/fd586cbdf889c9a5c6b9be177ff02fbfffa3eba5)) +* **api:** Updating Assistants and Evals API schemas ([98ba7d3](https://github.com/openai/openai-python/commit/98ba7d355551213a13803f68d5642eecbb4ffd39)) + + +### Bug Fixes + +* fix create audio transcription endpoint ([e9a89ab](https://github.com/openai/openai-python/commit/e9a89ab7b6387610e433550207a23973b7edda3a)) + + +### Chores + +* **ci:** fix installation instructions ([f26c5fc](https://github.com/openai/openai-python/commit/f26c5fc85d98d700b68cb55c8be5d15983a9aeaf)) +* **ci:** upload sdks to package manager ([861f105](https://github.com/openai/openai-python/commit/861f1055768168ab04987a42efcd32a07bc93542)) + +## 1.78.1 (2025-05-12) + +Full Changelog: [v1.78.0...v1.78.1](https://github.com/openai/openai-python/compare/v1.78.0...v1.78.1) + +### Bug Fixes + +* **internal:** fix linting due to broken __test__ annotation ([5a7d7a0](https://github.com/openai/openai-python/commit/5a7d7a081138c6473bff44e60d439812ecb85cdf)) +* **package:** support direct resource imports ([2293fc0](https://github.com/openai/openai-python/commit/2293fc0dd23a9c756067cdc22b39c18448f35feb)) + +## 1.78.0 (2025-05-08) + +Full Changelog: [v1.77.0...v1.78.0](https://github.com/openai/openai-python/compare/v1.77.0...v1.78.0) + +### Features + +* **api:** Add reinforcement fine-tuning api support ([bebe361](https://github.com/openai/openai-python/commit/bebe36104bd3062d09ab9bbfb4bacfc99e737cb2)) + + +### Bug Fixes + +* ignore errors in isinstance() calls on LazyProxy subclasses ([#2343](https://github.com/openai/openai-python/issues/2343)) ([52cbbdf](https://github.com/openai/openai-python/commit/52cbbdf2207567741f16d18f1ea1b0d13d667375)), closes [#2056](https://github.com/openai/openai-python/issues/2056) + + +### Chores + +* **internal:** update proxy tests ([b8e848d](https://github.com/openai/openai-python/commit/b8e848d5fb58472cbfa27fb3ed01efc25a05d944)) +* use lazy imports for module level client ([4d0f409](https://github.com/openai/openai-python/commit/4d0f409e79a18cce9855fe076f5a50e52b8bafd8)) +* use lazy imports for resources ([834813c](https://github.com/openai/openai-python/commit/834813c5cb1a84effc34e5eabed760393e1de806)) + +## 1.77.0 (2025-05-02) + +Full Changelog: [v1.76.2...v1.77.0](https://github.com/openai/openai-python/compare/v1.76.2...v1.77.0) + +### Features + +* **api:** add image sizes, reasoning encryption ([473469a](https://github.com/openai/openai-python/commit/473469afa1a5f0a03f727bdcdadb9fd57872f9c5)) + + +### Bug Fixes + +* **parsing:** handle whitespace only strings ([#2007](https://github.com/openai/openai-python/issues/2007)) ([246bc5b](https://github.com/openai/openai-python/commit/246bc5b7559887840717667a0dad465caef66c3b)) + + +### Chores + +* only strip leading whitespace ([8467d66](https://github.com/openai/openai-python/commit/8467d666e0ddf1a9f81b8769a5c8a2fef1de20c1)) + +## 1.76.2 (2025-04-29) + +Full Changelog: [v1.76.1...v1.76.2](https://github.com/openai/openai-python/compare/v1.76.1...v1.76.2) + +### Chores + +* **api:** API spec cleanup ([0a4d3e2](https://github.com/openai/openai-python/commit/0a4d3e2b495d22dd42ce1773b870554c64f9b3b2)) + +## 1.76.1 (2025-04-29) + +Full Changelog: [v1.76.0...v1.76.1](https://github.com/openai/openai-python/compare/v1.76.0...v1.76.1) + +### Chores + +* broadly detect json family of content-type headers ([b4b1b08](https://github.com/openai/openai-python/commit/b4b1b086b512eecc0ada7fc1efa45eb506982f13)) +* **ci:** only use depot for staging repos ([35312d8](https://github.com/openai/openai-python/commit/35312d80e6bbc1a61d06ad253af9a713b5ef040c)) +* **ci:** run on more branches and use depot runners ([a6a45d4](https://github.com/openai/openai-python/commit/a6a45d4af8a4d904b37573a9b223d56106b4887d)) + +## 1.76.0 (2025-04-23) + +Full Changelog: [v1.75.0...v1.76.0](https://github.com/openai/openai-python/compare/v1.75.0...v1.76.0) + +### Features + +* **api:** adding new image model support ([74d7692](https://github.com/openai/openai-python/commit/74d7692e94c9dca96db8793809d75631c22dbb87)) + + +### Bug Fixes + +* **pydantic v1:** more robust `ModelField.annotation` check ([#2163](https://github.com/openai/openai-python/issues/2163)) ([7351b12](https://github.com/openai/openai-python/commit/7351b12bc981f56632b92342d9ef26f6fb28d540)) +* **pydantic v1:** more robust ModelField.annotation check ([eba7856](https://github.com/openai/openai-python/commit/eba7856db55afb8cb44376a0248587549f7bc65f)) + + +### Chores + +* **ci:** add timeout thresholds for CI jobs ([0997211](https://github.com/openai/openai-python/commit/09972119df5dd4c7c8db137c721364787e22d4c6)) +* **internal:** fix list file params ([da2113c](https://github.com/openai/openai-python/commit/da2113c60b50b4438459325fcd38d55df3f63d8e)) +* **internal:** import reformatting ([b425fb9](https://github.com/openai/openai-python/commit/b425fb906f62550c3669b09b9d8575f3d4d8496b)) +* **internal:** minor formatting changes ([aed1d76](https://github.com/openai/openai-python/commit/aed1d767898324cf90328db329e04e89a77579c3)) +* **internal:** refactor retries to not use recursion ([8cb8cfa](https://github.com/openai/openai-python/commit/8cb8cfab48a4fed70a756ce50036e7e56e1f9f87)) +* **internal:** update models test ([870ad4e](https://github.com/openai/openai-python/commit/870ad4ed3a284d75f44b825503750129284c7906)) +* update completion parse signature ([a44016c](https://github.com/openai/openai-python/commit/a44016c64cdefe404e97592808ed3c25411ab27b)) + +## 1.75.0 (2025-04-16) + +Full Changelog: [v1.74.1...v1.75.0](https://github.com/openai/openai-python/compare/v1.74.1...v1.75.0) + +### Features + +* **api:** add o3 and o4-mini model IDs ([4bacbd5](https://github.com/openai/openai-python/commit/4bacbd5503137e266c127dc643ebae496cb4f158)) + +## 1.74.1 (2025-04-16) + +Full Changelog: [v1.74.0...v1.74.1](https://github.com/openai/openai-python/compare/v1.74.0...v1.74.1) + +### Chores + +* **internal:** base client updates ([06303b5](https://github.com/openai/openai-python/commit/06303b501f8c17040c495971a4ee79ae340f6f4a)) +* **internal:** bump pyright version ([9fd1c77](https://github.com/openai/openai-python/commit/9fd1c778c3231616bf1331cb1daa86fdfca4cb7f)) + +## 1.74.0 (2025-04-14) + +Full Changelog: [v1.73.0...v1.74.0](https://github.com/openai/openai-python/compare/v1.73.0...v1.74.0) + +### Features + +* **api:** adding gpt-4.1 family of model IDs ([d4dae55](https://github.com/openai/openai-python/commit/d4dae5553ff3a2879b9ab79a6423661b212421f9)) + + +### Bug Fixes + +* **chat:** skip azure async filter events ([#2255](https://github.com/openai/openai-python/issues/2255)) ([fd3a38b](https://github.com/openai/openai-python/commit/fd3a38b1ed30af0a9f3302c1cfc6be6b352e65de)) + + +### Chores + +* **client:** minor internal fixes ([6071ae5](https://github.com/openai/openai-python/commit/6071ae5e8b4faa465afc8d07370737e66901900a)) +* **internal:** update pyright settings ([c8f8beb](https://github.com/openai/openai-python/commit/c8f8bebf852380a224701bc36826291d6387c53d)) + +## 1.73.0 (2025-04-12) + +Full Changelog: [v1.72.0...v1.73.0](https://github.com/openai/openai-python/compare/v1.72.0...v1.73.0) + +### Features + +* **api:** manual updates ([a3253dd](https://github.com/openai/openai-python/commit/a3253dd798c1eccd9810d4fc593e8c2a568bcf4f)) + + +### Bug Fixes + +* **perf:** optimize some hot paths ([f79d39f](https://github.com/openai/openai-python/commit/f79d39fbcaea8f366a9e48c06fb1696bab3e607d)) +* **perf:** skip traversing types for NotGiven values ([28d220d](https://github.com/openai/openai-python/commit/28d220de3b4a09d80450d0bcc9b347bbf68f81ec)) + + +### Chores + +* **internal:** expand CI branch coverage ([#2295](https://github.com/openai/openai-python/issues/2295)) ([0ae783b](https://github.com/openai/openai-python/commit/0ae783b99122975be521365de0b6d2bce46056c9)) +* **internal:** reduce CI branch coverage ([2fb7d42](https://github.com/openai/openai-python/commit/2fb7d425cda679a54aa3262090479fd747363bb4)) +* slight wording improvement in README ([#2291](https://github.com/openai/openai-python/issues/2291)) ([e020759](https://github.com/openai/openai-python/commit/e0207598d16a2a9cb3cb3a8e8e97fa9cfdccd5e8)) +* workaround build errors ([4e10c96](https://github.com/openai/openai-python/commit/4e10c96a483db28dedc2d8c2908765fb7317e049)) + +## 1.72.0 (2025-04-08) + +Full Changelog: [v1.71.0...v1.72.0](https://github.com/openai/openai-python/compare/v1.71.0...v1.72.0) + +### Features + +* **api:** Add evalapi to sdk ([#2287](https://github.com/openai/openai-python/issues/2287)) ([35262fc](https://github.com/openai/openai-python/commit/35262fcef6ccb7d1f75c9abdfdc68c3dcf87ef53)) + + +### Chores + +* **internal:** fix examples ([#2288](https://github.com/openai/openai-python/issues/2288)) ([39defd6](https://github.com/openai/openai-python/commit/39defd61e81ea0ec6b898be12e9fb7e621c0e532)) +* **internal:** skip broken test ([#2289](https://github.com/openai/openai-python/issues/2289)) ([e2c9bce](https://github.com/openai/openai-python/commit/e2c9bce1f59686ee053b495d06ea118b4a89e09e)) +* **internal:** slight transform perf improvement ([#2284](https://github.com/openai/openai-python/issues/2284)) ([746174f](https://github.com/openai/openai-python/commit/746174fae7a018ece5dab54fb0b5a15fcdd18f2f)) +* **tests:** improve enum examples ([#2286](https://github.com/openai/openai-python/issues/2286)) ([c9dd81c](https://github.com/openai/openai-python/commit/c9dd81ce0277e8b1f5db5e0a39c4c2bcd9004bcc)) + +## 1.71.0 (2025-04-07) + +Full Changelog: [v1.70.0...v1.71.0](https://github.com/openai/openai-python/compare/v1.70.0...v1.71.0) + +### Features + +* **api:** manual updates ([bf8b4b6](https://github.com/openai/openai-python/commit/bf8b4b69906bfaea622c9c644270e985d92e2df2)) +* **api:** manual updates ([3e37aa3](https://github.com/openai/openai-python/commit/3e37aa3e151d9738625a1daf75d6243d6fdbe8f2)) +* **api:** manual updates ([dba9b65](https://github.com/openai/openai-python/commit/dba9b656fa5955b6eba8f6910da836a34de8d59d)) +* **api:** manual updates ([f0c463b](https://github.com/openai/openai-python/commit/f0c463b47836666d091b5f616871f1b94646d346)) + + +### Chores + +* **deps:** allow websockets v15 ([#2281](https://github.com/openai/openai-python/issues/2281)) ([19c619e](https://github.com/openai/openai-python/commit/19c619ea95839129a86c19d5b60133e1ed9f2746)) +* **internal:** only run examples workflow in main repo ([#2282](https://github.com/openai/openai-python/issues/2282)) ([c3e0927](https://github.com/openai/openai-python/commit/c3e0927d3fbbb9f753ba12adfa682a4235ba530a)) +* **internal:** remove trailing character ([#2277](https://github.com/openai/openai-python/issues/2277)) ([5a21a2d](https://github.com/openai/openai-python/commit/5a21a2d7994e39bb0c86271eeb807983a9ae874a)) +* Remove deprecated/unused remote spec feature ([23f76eb](https://github.com/openai/openai-python/commit/23f76eb0b9ddf12bcb04a6ad3f3ec5e956d2863f)) + +## 1.70.0 (2025-03-31) + +Full Changelog: [v1.69.0...v1.70.0](https://github.com/openai/openai-python/compare/v1.69.0...v1.70.0) + +### Features + +* **api:** add `get /responses/{response_id}/input_items` endpoint ([4c6a35d](https://github.com/openai/openai-python/commit/4c6a35dec65362a6a738c3387dae57bf8cbfcbb2)) + +## 1.69.0 (2025-03-27) + +Full Changelog: [v1.68.2...v1.69.0](https://github.com/openai/openai-python/compare/v1.68.2...v1.69.0) + +### Features + +* **api:** add `get /chat/completions` endpoint ([e6b8a42](https://github.com/openai/openai-python/commit/e6b8a42fc4286656cc86c2acd83692b170e77b68)) + + +### Bug Fixes + +* **audio:** correctly parse transcription stream events ([16a3a19](https://github.com/openai/openai-python/commit/16a3a195ff31f099fbe46043a12d2380c2c01f83)) + + +### Chores + +* add hash of OpenAPI spec/config inputs to .stats.yml ([515e1cd](https://github.com/openai/openai-python/commit/515e1cdd4a3109e5b29618df813656e17f22b52a)) +* **api:** updates to supported Voice IDs ([#2261](https://github.com/openai/openai-python/issues/2261)) ([64956f9](https://github.com/openai/openai-python/commit/64956f9d9889b04380c7f5eb926509d1efd523e6)) +* fix typos ([#2259](https://github.com/openai/openai-python/issues/2259)) ([6160de3](https://github.com/openai/openai-python/commit/6160de3e099f09c2d6ee5eeee4cbcc55b67a8f87)) + +## 1.68.2 (2025-03-21) + +Full Changelog: [v1.68.1...v1.68.2](https://github.com/openai/openai-python/compare/v1.68.1...v1.68.2) + +### Refactors + +* **package:** rename audio extra to voice_helpers ([2dd6cb8](https://github.com/openai/openai-python/commit/2dd6cb87489fe12c5e45128f44d985c3f49aba1d)) + +## 1.68.1 (2025-03-21) + +Full Changelog: [v1.68.0...v1.68.1](https://github.com/openai/openai-python/compare/v1.68.0...v1.68.1) + +### Bug Fixes + +* **client:** remove duplicate types ([#2235](https://github.com/openai/openai-python/issues/2235)) ([063f7d0](https://github.com/openai/openai-python/commit/063f7d0684c350ca9d766e2cb150233a22a623c8)) +* **helpers/audio:** remove duplicative module ([f253d04](https://github.com/openai/openai-python/commit/f253d0415145f2c4904ea2e7b389d31d94e45a54)) +* **package:** make sounddevice and numpy optional dependencies ([8b04453](https://github.com/openai/openai-python/commit/8b04453f0483736c13f0209a9f8f3618bc0e86c9)) + + +### Chores + +* **ci:** run workflows on next too ([67f89d4](https://github.com/openai/openai-python/commit/67f89d478aab780d1481c9bf6682c6633e431137)) + +## 1.68.0 (2025-03-20) + +Full Changelog: [v1.67.0...v1.68.0](https://github.com/openai/openai-python/compare/v1.67.0...v1.68.0) + +### Features + +* add audio helpers ([423655c](https://github.com/openai/openai-python/commit/423655ca9077cfd258f1e52f6eb386fc8307fa5f)) +* **api:** new models for TTS, STT, + new audio features for Realtime ([#2232](https://github.com/openai/openai-python/issues/2232)) ([ab5192d](https://github.com/openai/openai-python/commit/ab5192d0a7b417ade622ec94dd48f86beb90692c)) + +## 1.67.0 (2025-03-19) + +Full Changelog: [v1.66.5...v1.67.0](https://github.com/openai/openai-python/compare/v1.66.5...v1.67.0) + +### Features + +* **api:** o1-pro now available through the API ([#2228](https://github.com/openai/openai-python/issues/2228)) ([40a19d8](https://github.com/openai/openai-python/commit/40a19d8592c1767d6318230fc93e37c360d1bcd1)) + +## 1.66.5 (2025-03-18) + +Full Changelog: [v1.66.4...v1.66.5](https://github.com/openai/openai-python/compare/v1.66.4...v1.66.5) + +### Bug Fixes + +* **types:** improve responses type names ([#2224](https://github.com/openai/openai-python/issues/2224)) ([5f7beb8](https://github.com/openai/openai-python/commit/5f7beb873af5ccef2551f34ab3ef098e099ce9c6)) + + +### Chores + +* **internal:** add back releases workflow ([c71d4c9](https://github.com/openai/openai-python/commit/c71d4c918eab3532b36ea944b0c4069db6ac2d38)) +* **internal:** codegen related update ([#2222](https://github.com/openai/openai-python/issues/2222)) ([f570d91](https://github.com/openai/openai-python/commit/f570d914a16cb5092533e32dfd863027d378c0b5)) + +## 1.66.4 (2025-03-17) + +Full Changelog: [v1.66.3...v1.66.4](https://github.com/openai/openai-python/compare/v1.66.3...v1.66.4) + +### Bug Fixes + +* **ci:** ensure pip is always available ([#2207](https://github.com/openai/openai-python/issues/2207)) ([3f08e56](https://github.com/openai/openai-python/commit/3f08e56a48a04c2b7f03a4ad63f38228e25810e6)) +* **ci:** remove publishing patch ([#2208](https://github.com/openai/openai-python/issues/2208)) ([dd2dab7](https://github.com/openai/openai-python/commit/dd2dab7faf2a003da3e6af66780bd250be6e7f3f)) +* **types:** handle more discriminated union shapes ([#2206](https://github.com/openai/openai-python/issues/2206)) ([f85a9c6](https://github.com/openai/openai-python/commit/f85a9c633dcb9b64c0eb47d20151894742bbef22)) + + +### Chores + +* **internal:** bump rye to 0.44.0 ([#2200](https://github.com/openai/openai-python/issues/2200)) ([2dd3139](https://github.com/openai/openai-python/commit/2dd3139df6e7fe6307f9847e6527073e355e5047)) +* **internal:** remove CI condition ([#2203](https://github.com/openai/openai-python/issues/2203)) ([9620fdc](https://github.com/openai/openai-python/commit/9620fdcf4f2d01b6753ecc0abc16e5239c2b41e1)) +* **internal:** remove extra empty newlines ([#2195](https://github.com/openai/openai-python/issues/2195)) ([a1016a7](https://github.com/openai/openai-python/commit/a1016a78fe551e0f0e2562a0e81d1cb724d195da)) +* **internal:** update release workflows ([e2def44](https://github.com/openai/openai-python/commit/e2def4453323aa1cf8077df447fd55eb4c626393)) + +## 1.66.3 (2025-03-12) + +Full Changelog: [v1.66.2...v1.66.3](https://github.com/openai/openai-python/compare/v1.66.2...v1.66.3) + +### Bug Fixes + +* update module level client ([#2185](https://github.com/openai/openai-python/issues/2185)) ([456f324](https://github.com/openai/openai-python/commit/456f3240a0c33e71521c6b73c32e8adc1b8cd3bc)) + +## 1.66.2 (2025-03-11) + +Full Changelog: [v1.66.1...v1.66.2](https://github.com/openai/openai-python/compare/v1.66.1...v1.66.2) + +### Bug Fixes + +* **responses:** correct reasoning output type ([#2181](https://github.com/openai/openai-python/issues/2181)) ([8cb1129](https://github.com/openai/openai-python/commit/8cb11299acc40c80061af275691cd09a2bf30c65)) + +## 1.66.1 (2025-03-11) + +Full Changelog: [v1.66.0...v1.66.1](https://github.com/openai/openai-python/compare/v1.66.0...v1.66.1) + +### Bug Fixes + +* **responses:** correct computer use enum value ([#2180](https://github.com/openai/openai-python/issues/2180)) ([48f4628](https://github.com/openai/openai-python/commit/48f4628c5fb18ddd7d71e8730184f3ac50c4ffea)) + + +### Chores + +* **internal:** temporary commit ([afabec1](https://github.com/openai/openai-python/commit/afabec1b5b18b41ac870970d06e6c2f152ef7bbe)) + +## 1.66.0 (2025-03-11) + +Full Changelog: [v1.65.5...v1.66.0](https://github.com/openai/openai-python/compare/v1.65.5...v1.66.0) + +### Features + +* **api:** add /v1/responses and built-in tools ([854df97](https://github.com/openai/openai-python/commit/854df97884736244d46060fd3d5a92916826ec8f)) + + +### Chores + +* export more types ([#2176](https://github.com/openai/openai-python/issues/2176)) ([a730f0e](https://github.com/openai/openai-python/commit/a730f0efedd228f96a49467f17fb19b6a219246c)) + +## 1.65.5 (2025-03-09) + +Full Changelog: [v1.65.4...v1.65.5](https://github.com/openai/openai-python/compare/v1.65.4...v1.65.5) + +### Chores + +* move ChatModel type to shared ([#2167](https://github.com/openai/openai-python/issues/2167)) ([104f02a](https://github.com/openai/openai-python/commit/104f02af371076d5d2498e48ae14d2eacc7df8bd)) + +## 1.65.4 (2025-03-05) + +Full Changelog: [v1.65.3...v1.65.4](https://github.com/openai/openai-python/compare/v1.65.3...v1.65.4) + +### Bug Fixes + +* **api:** add missing file rank enum + more metadata ([#2164](https://github.com/openai/openai-python/issues/2164)) ([0387e48](https://github.com/openai/openai-python/commit/0387e48e0880e496eb74b60eec9ed76a3171f14d)) + +## 1.65.3 (2025-03-04) + +Full Changelog: [v1.65.2...v1.65.3](https://github.com/openai/openai-python/compare/v1.65.2...v1.65.3) + +### Chores + +* **internal:** remove unused http client options forwarding ([#2158](https://github.com/openai/openai-python/issues/2158)) ([76ec464](https://github.com/openai/openai-python/commit/76ec464cfe3db3fa59a766259d6d6ee5bb889f86)) +* **internal:** run example files in CI ([#2160](https://github.com/openai/openai-python/issues/2160)) ([9979345](https://github.com/openai/openai-python/commit/9979345038594440eec2f500c0c7cc5417cc7c08)) + +## 1.65.2 (2025-03-01) + +Full Changelog: [v1.65.1...v1.65.2](https://github.com/openai/openai-python/compare/v1.65.1...v1.65.2) + +### Bug Fixes + +* **azure:** azure_deployment use with realtime + non-deployment-based APIs ([#2154](https://github.com/openai/openai-python/issues/2154)) ([5846b55](https://github.com/openai/openai-python/commit/5846b552877f3d278689c521f9a26ce31167e1ea)) + + +### Chores + +* **docs:** update client docstring ([#2152](https://github.com/openai/openai-python/issues/2152)) ([0518c34](https://github.com/openai/openai-python/commit/0518c341ee0e19941c6b1d9d60e2552e1aa17f26)) + +## 1.65.1 (2025-02-27) + +Full Changelog: [v1.65.0...v1.65.1](https://github.com/openai/openai-python/compare/v1.65.0...v1.65.1) + +### Documentation + +* update URLs from stainlessapi.com to stainless.com ([#2150](https://github.com/openai/openai-python/issues/2150)) ([dee4298](https://github.com/openai/openai-python/commit/dee42986eff46dd23ba25b3e2a5bb7357aca39d9)) + +## 1.65.0 (2025-02-27) + +Full Changelog: [v1.64.0...v1.65.0](https://github.com/openai/openai-python/compare/v1.64.0...v1.65.0) + +### Features + +* **api:** add gpt-4.5-preview ([#2149](https://github.com/openai/openai-python/issues/2149)) ([4cee52e](https://github.com/openai/openai-python/commit/4cee52e8d191b0532f28d86446da79b43a58b907)) + + +### Chores + +* **internal:** properly set __pydantic_private__ ([#2144](https://github.com/openai/openai-python/issues/2144)) ([2b1bd16](https://github.com/openai/openai-python/commit/2b1bd1604a038ded67367742a0b1c9d92e29dfc8)) + +## 1.64.0 (2025-02-22) + +Full Changelog: [v1.63.2...v1.64.0](https://github.com/openai/openai-python/compare/v1.63.2...v1.64.0) + +### Features + +* **client:** allow passing `NotGiven` for body ([#2135](https://github.com/openai/openai-python/issues/2135)) ([4451f56](https://github.com/openai/openai-python/commit/4451f5677f9eaad9b8fee74f71c2e5fe6785c420)) + + +### Bug Fixes + +* **client:** mark some request bodies as optional ([4451f56](https://github.com/openai/openai-python/commit/4451f5677f9eaad9b8fee74f71c2e5fe6785c420)) + + +### Chores + +* **internal:** fix devcontainers setup ([#2137](https://github.com/openai/openai-python/issues/2137)) ([4d88402](https://github.com/openai/openai-python/commit/4d884020cbeb1ca6093dd5317e3e5812551f7a46)) + +## 1.63.2 (2025-02-17) + +Full Changelog: [v1.63.1...v1.63.2](https://github.com/openai/openai-python/compare/v1.63.1...v1.63.2) + +### Chores + +* **internal:** revert temporary commit ([#2121](https://github.com/openai/openai-python/issues/2121)) ([72458ab](https://github.com/openai/openai-python/commit/72458abeed3dd95db8aabed94a33bb12a916f8b7)) + +## 1.63.1 (2025-02-17) + +Full Changelog: [v1.63.0...v1.63.1](https://github.com/openai/openai-python/compare/v1.63.0...v1.63.1) + +### Chores + +* **internal:** temporary commit ([#2121](https://github.com/openai/openai-python/issues/2121)) ([f7f8361](https://github.com/openai/openai-python/commit/f7f83614c8da84c6725d60936f08f9f1a65f0a9e)) + +## 1.63.0 (2025-02-13) + +Full Changelog: [v1.62.0...v1.63.0](https://github.com/openai/openai-python/compare/v1.62.0...v1.63.0) + +### Features + +* **api:** add support for storing chat completions ([#2117](https://github.com/openai/openai-python/issues/2117)) ([2357a8f](https://github.com/openai/openai-python/commit/2357a8f97246a3fe17c6ac1fb0d7a67d6f1ffc1d)) + +## 1.62.0 (2025-02-12) + +Full Changelog: [v1.61.1...v1.62.0](https://github.com/openai/openai-python/compare/v1.61.1...v1.62.0) + +### Features + +* **client:** send `X-Stainless-Read-Timeout` header ([#2094](https://github.com/openai/openai-python/issues/2094)) ([0288213](https://github.com/openai/openai-python/commit/0288213fbfa935c9bf9d56416619ea929ae1cf63)) +* **embeddings:** use stdlib array type for improved performance ([#2060](https://github.com/openai/openai-python/issues/2060)) ([9a95db9](https://github.com/openai/openai-python/commit/9a95db9154ac98678970e7f1652a7cacfd2f7fdb)) +* **pagination:** avoid fetching when has_more: false ([#2098](https://github.com/openai/openai-python/issues/2098)) ([1882483](https://github.com/openai/openai-python/commit/18824832d3a676ae49206cd2b5e09d4796fdf033)) + + +### Bug Fixes + +* **api:** add missing reasoning effort + model enums ([#2096](https://github.com/openai/openai-python/issues/2096)) ([e0ca9f0](https://github.com/openai/openai-python/commit/e0ca9f0f6fae40230f8cab97573914ed632920b6)) +* **parsing:** don't default to an empty array ([#2106](https://github.com/openai/openai-python/issues/2106)) ([8e748bb](https://github.com/openai/openai-python/commit/8e748bb08d9c0d1f7e8a1af31452e25eb7154f55)) + + +### Chores + +* **internal:** fix type traversing dictionary params ([#2097](https://github.com/openai/openai-python/issues/2097)) ([4e5b368](https://github.com/openai/openai-python/commit/4e5b368bf576f38d0f125778edde74ed6d101d7d)) +* **internal:** minor type handling changes ([#2099](https://github.com/openai/openai-python/issues/2099)) ([a2c6da0](https://github.com/openai/openai-python/commit/a2c6da0fbc610ee80a2e044a0b20fc1cc2376962)) + +## 1.61.1 (2025-02-05) + +Full Changelog: [v1.61.0...v1.61.1](https://github.com/openai/openai-python/compare/v1.61.0...v1.61.1) + +### Bug Fixes + +* **api/types:** correct audio duration & role types ([#2091](https://github.com/openai/openai-python/issues/2091)) ([afcea48](https://github.com/openai/openai-python/commit/afcea4891ff85de165ccc2b5497ccf9a90520e9e)) +* **cli/chat:** only send params when set ([#2077](https://github.com/openai/openai-python/issues/2077)) ([688b223](https://github.com/openai/openai-python/commit/688b223d9a733d241d50e5d7df62f346592c537c)) + + +### Chores + +* **internal:** bummp ruff dependency ([#2080](https://github.com/openai/openai-python/issues/2080)) ([b7a80b1](https://github.com/openai/openai-python/commit/b7a80b1994ab86e81485b88531e4aea63b3da594)) +* **internal:** change default timeout to an int ([#2079](https://github.com/openai/openai-python/issues/2079)) ([d3df1c6](https://github.com/openai/openai-python/commit/d3df1c6ca090598701e38fd376a9796aadba88f1)) + +## 1.61.0 (2025-01-31) + +Full Changelog: [v1.60.2...v1.61.0](https://github.com/openai/openai-python/compare/v1.60.2...v1.61.0) + +### Features + +* **api:** add o3-mini ([#2067](https://github.com/openai/openai-python/issues/2067)) ([12b87a4](https://github.com/openai/openai-python/commit/12b87a4a1e6cb071a6b063d089585dec56a5d534)) + + +### Bug Fixes + +* **types:** correct metadata type + other fixes ([12b87a4](https://github.com/openai/openai-python/commit/12b87a4a1e6cb071a6b063d089585dec56a5d534)) + + +### Chores + +* **helpers:** section links ([ef8d3cc](https://github.com/openai/openai-python/commit/ef8d3cce40022d3482d341455be604e5f1afbd70)) +* **types:** fix Metadata types ([82d3156](https://github.com/openai/openai-python/commit/82d3156e74ed2f95edd10cd7ebea53d2b5562794)) +* update api.md ([#2063](https://github.com/openai/openai-python/issues/2063)) ([21964f0](https://github.com/openai/openai-python/commit/21964f00fb104011c4c357544114702052b74548)) + + +### Documentation + +* **readme:** current section links ([#2055](https://github.com/openai/openai-python/issues/2055)) ([ef8d3cc](https://github.com/openai/openai-python/commit/ef8d3cce40022d3482d341455be604e5f1afbd70)) + +## 1.60.2 (2025-01-27) + +Full Changelog: [v1.60.1...v1.60.2](https://github.com/openai/openai-python/compare/v1.60.1...v1.60.2) + +### Bug Fixes + +* **parsing:** don't validate input tools in the asynchronous `.parse()` method ([6fcfe73](https://github.com/openai/openai-python/commit/6fcfe73cd335853c7dd2cd3151a0d5d1785cfc9c)) + +## 1.60.1 (2025-01-24) + +Full Changelog: [v1.60.0...v1.60.1](https://github.com/openai/openai-python/compare/v1.60.0...v1.60.1) + +### Chores + +* **internal:** minor formatting changes ([#2050](https://github.com/openai/openai-python/issues/2050)) ([9c44192](https://github.com/openai/openai-python/commit/9c44192be5776d9252d36dc027a33c60b33d81b2)) + + +### Documentation + +* **examples/azure:** add async snippet ([#1787](https://github.com/openai/openai-python/issues/1787)) ([f60eda1](https://github.com/openai/openai-python/commit/f60eda1c1e8caf0ec2274b18b3fb2252304196db)) + +## 1.60.0 (2025-01-22) + +Full Changelog: [v1.59.9...v1.60.0](https://github.com/openai/openai-python/compare/v1.59.9...v1.60.0) + +### Features + +* **api:** update enum values, comments, and examples ([#2045](https://github.com/openai/openai-python/issues/2045)) ([e8205fd](https://github.com/openai/openai-python/commit/e8205fd58f0d677f476c577a8d9afb90f5710506)) + + +### Chores + +* **internal:** minor style changes ([#2043](https://github.com/openai/openai-python/issues/2043)) ([89a9dd8](https://github.com/openai/openai-python/commit/89a9dd821eaf5300ad11b0270b61fdfa4fd6e9b6)) + + +### Documentation + +* **readme:** mention failed requests in request IDs ([5f7c30b](https://github.com/openai/openai-python/commit/5f7c30bc006ffb666c324011a68aae357cb33e35)) + +## 1.59.9 (2025-01-20) + +Full Changelog: [v1.59.8...v1.59.9](https://github.com/openai/openai-python/compare/v1.59.8...v1.59.9) + +### Bug Fixes + +* **tests:** make test_get_platform less flaky ([#2040](https://github.com/openai/openai-python/issues/2040)) ([72ea05c](https://github.com/openai/openai-python/commit/72ea05cf18caaa7a5e6fe7e2251ab93fa0ba3140)) + + +### Chores + +* **internal:** avoid pytest-asyncio deprecation warning ([#2041](https://github.com/openai/openai-python/issues/2041)) ([b901046](https://github.com/openai/openai-python/commit/b901046ddda9c79b7f019e2263c02d126a3b2ee2)) +* **internal:** update websockets dep ([#2036](https://github.com/openai/openai-python/issues/2036)) ([642cd11](https://github.com/openai/openai-python/commit/642cd119482c6fbca925ba702ad2579f9dc47bf9)) + + +### Documentation + +* fix typo ([#2031](https://github.com/openai/openai-python/issues/2031)) ([02fcf15](https://github.com/openai/openai-python/commit/02fcf15611953089826a74725cb96201d94658bb)) +* **raw responses:** fix duplicate `the` ([#2039](https://github.com/openai/openai-python/issues/2039)) ([9b8eab9](https://github.com/openai/openai-python/commit/9b8eab99fdc6a581a1f5cc421c6f74b0e2b30415)) + +## 1.59.8 (2025-01-17) + +Full Changelog: [v1.59.7...v1.59.8](https://github.com/openai/openai-python/compare/v1.59.7...v1.59.8) + +### Bug Fixes + +* streaming ([c16f58e](https://github.com/openai/openai-python/commit/c16f58ead0bc85055b164182689ba74b7e939dfa)) +* **structured outputs:** avoid parsing empty empty content ([#2023](https://github.com/openai/openai-python/issues/2023)) ([6d3513c](https://github.com/openai/openai-python/commit/6d3513c86f6e5800f8f73a45e089b7a205327121)) +* **structured outputs:** correct schema coercion for inline ref expansion ([#2025](https://github.com/openai/openai-python/issues/2025)) ([2f4f0b3](https://github.com/openai/openai-python/commit/2f4f0b374207f162060c328b71ec995049dc42e8)) +* **types:** correct type for vector store chunking strategy ([#2017](https://github.com/openai/openai-python/issues/2017)) ([e389279](https://github.com/openai/openai-python/commit/e38927950a5cdad99065853fe7b72aad6bb322e9)) + + +### Chores + +* **examples:** update realtime model ([f26746c](https://github.com/openai/openai-python/commit/f26746cbcd893d66cf8a3fd68a7c3779dc8c833c)), closes [#2020](https://github.com/openai/openai-python/issues/2020) +* **internal:** bump pyright dependency ([#2021](https://github.com/openai/openai-python/issues/2021)) ([0a9a0f5](https://github.com/openai/openai-python/commit/0a9a0f5d8b9d5457643798287f893305006dd518)) +* **internal:** streaming refactors ([#2012](https://github.com/openai/openai-python/issues/2012)) ([d76a748](https://github.com/openai/openai-python/commit/d76a748f606743407f94dfc26758095560e2082a)) +* **internal:** update deps ([#2015](https://github.com/openai/openai-python/issues/2015)) ([514e0e4](https://github.com/openai/openai-python/commit/514e0e415f87ab4510262d29ed6125384e017b84)) + + +### Documentation + +* **examples/azure:** example script with realtime API ([#1967](https://github.com/openai/openai-python/issues/1967)) ([84f2f9c](https://github.com/openai/openai-python/commit/84f2f9c0439229a7db7136fe78419292d34d1f81)) + +## 1.59.7 (2025-01-13) + +Full Changelog: [v1.59.6...v1.59.7](https://github.com/openai/openai-python/compare/v1.59.6...v1.59.7) + +### Chores + +* export HttpxBinaryResponseContent class ([7191b71](https://github.com/openai/openai-python/commit/7191b71f3dcbbfcb2f2bec855c3bba93c956384e)) + +## 1.59.6 (2025-01-09) + +Full Changelog: [v1.59.5...v1.59.6](https://github.com/openai/openai-python/compare/v1.59.5...v1.59.6) + +### Bug Fixes + +* correctly handle deserialising `cls` fields ([#2002](https://github.com/openai/openai-python/issues/2002)) ([089c820](https://github.com/openai/openai-python/commit/089c820c8a5d20e9db6a171f0a4f11b481fe8465)) + + +### Chores + +* **internal:** spec update ([#2000](https://github.com/openai/openai-python/issues/2000)) ([36548f8](https://github.com/openai/openai-python/commit/36548f871763fdd7b5ce44903d186bc916331549)) + +## 1.59.5 (2025-01-08) + +Full Changelog: [v1.59.4...v1.59.5](https://github.com/openai/openai-python/compare/v1.59.4...v1.59.5) + +### Bug Fixes + +* **client:** only call .close() when needed ([#1992](https://github.com/openai/openai-python/issues/1992)) ([bdfd699](https://github.com/openai/openai-python/commit/bdfd699b99522e83f7610b5f98e36fe43ddf8338)) + + +### Documentation + +* fix typos ([#1995](https://github.com/openai/openai-python/issues/1995)) ([be694a0](https://github.com/openai/openai-python/commit/be694a097d6cf2668f08ecf94c882773b2ee1f84)) +* fix typos ([#1996](https://github.com/openai/openai-python/issues/1996)) ([714aed9](https://github.com/openai/openai-python/commit/714aed9d7eb74a19f6e502fb6d4fe83399f82851)) +* more typo fixes ([#1998](https://github.com/openai/openai-python/issues/1998)) ([7bd92f0](https://github.com/openai/openai-python/commit/7bd92f06a75f11f6afc2d1223d2426e186cc74cb)) +* **readme:** moved period to inside parentheses ([#1980](https://github.com/openai/openai-python/issues/1980)) ([e7fae94](https://github.com/openai/openai-python/commit/e7fae948f2ba8db23461e4374308417570196847)) + +## 1.59.4 (2025-01-07) + +Full Changelog: [v1.59.3...v1.59.4](https://github.com/openai/openai-python/compare/v1.59.3...v1.59.4) + +### Chores + +* add missing isclass check ([#1988](https://github.com/openai/openai-python/issues/1988)) ([61d9072](https://github.com/openai/openai-python/commit/61d9072fbace58d64910ec7378c3686ac555972e)) +* add missing isclass check for structured outputs ([bcbf013](https://github.com/openai/openai-python/commit/bcbf013e8d825b8b5f88172313dfb6e0313ca34c)) +* **internal:** bump httpx dependency ([#1990](https://github.com/openai/openai-python/issues/1990)) ([288c2c3](https://github.com/openai/openai-python/commit/288c2c30dc405cbaa89924f9243442300e95e100)) + + +### Documentation + +* **realtime:** fix event reference link ([9b6885d](https://github.com/openai/openai-python/commit/9b6885d50f8d65ba5009642046727d291e0f14fa)) + +## 1.59.3 (2025-01-03) + +Full Changelog: [v1.59.2...v1.59.3](https://github.com/openai/openai-python/compare/v1.59.2...v1.59.3) + +### Chores + +* **api:** bump spec version ([#1985](https://github.com/openai/openai-python/issues/1985)) ([c6f1b35](https://github.com/openai/openai-python/commit/c6f1b357fcf669065f4ed6819d47a528b0787128)) + +## 1.59.2 (2025-01-03) + +Full Changelog: [v1.59.1...v1.59.2](https://github.com/openai/openai-python/compare/v1.59.1...v1.59.2) + +### Chores + +* **ci:** fix publish workflow ([0be1f5d](https://github.com/openai/openai-python/commit/0be1f5de0daf807cece564abf061c8bb188bb9aa)) +* **internal:** empty commit ([fe8dc2e](https://github.com/openai/openai-python/commit/fe8dc2e97fc430ea2433ed28cfaa79425af223ec)) + +## 1.59.1 (2025-01-02) + +Full Changelog: [v1.59.0...v1.59.1](https://github.com/openai/openai-python/compare/v1.59.0...v1.59.1) + +### Chores + +* bump license year ([#1981](https://github.com/openai/openai-python/issues/1981)) ([f29011a](https://github.com/openai/openai-python/commit/f29011a6426d3fa4844ecd723ee20561ee60c665)) + +## 1.59.0 (2024-12-21) + +Full Changelog: [v1.58.1...v1.59.0](https://github.com/openai/openai-python/compare/v1.58.1...v1.59.0) + +### Features + +* **azure:** support for the Realtime API ([#1963](https://github.com/openai/openai-python/issues/1963)) ([9fda141](https://github.com/openai/openai-python/commit/9fda14172abdb66fe240aa7b4dc7cfae4faf1d73)) + + +### Chores + +* **realtime:** update docstrings ([#1964](https://github.com/openai/openai-python/issues/1964)) ([3dee863](https://github.com/openai/openai-python/commit/3dee863554d28272103e90a6a199ac196e92ff05)) + +## 1.58.1 (2024-12-17) + +Full Changelog: [v1.58.0...v1.58.1](https://github.com/openai/openai-python/compare/v1.58.0...v1.58.1) + +### Documentation + +* **readme:** fix example script link ([23ba877](https://github.com/openai/openai-python/commit/23ba8778fd55e0f54f36685e9c5950b452d8e10c)) + +## 1.58.0 (2024-12-17) + +Full Changelog: [v1.57.4...v1.58.0](https://github.com/openai/openai-python/compare/v1.57.4...v1.58.0) + +### Features + +* add Realtime API support ([#1958](https://github.com/openai/openai-python/issues/1958)) ([97d73cf](https://github.com/openai/openai-python/commit/97d73cf89935ca6098bb889a92f0ec2cdff16989)) +* **api:** new o1 and GPT-4o models + preference fine-tuning ([#1956](https://github.com/openai/openai-python/issues/1956)) ([ec22ffb](https://github.com/openai/openai-python/commit/ec22ffb129c524525caa33b088405d27c271e631)) + + +### Bug Fixes + +* add reasoning_effort to all methods ([8829c32](https://github.com/openai/openai-python/commit/8829c3202dbe790ca3646476c802ec55ed47d864)) +* **assistants:** correctly send `include` query param ([9a4c69c](https://github.com/openai/openai-python/commit/9a4c69c383bc6719b6521a485f2c7e62a9c036a9)) +* **cli/migrate:** change grit binaries prefix ([#1951](https://github.com/openai/openai-python/issues/1951)) ([1c396c9](https://github.com/openai/openai-python/commit/1c396c95b040fb3d1a2523b09eaad4ff62d96846)) + + +### Chores + +* **internal:** fix some typos ([#1955](https://github.com/openai/openai-python/issues/1955)) ([628dead](https://github.com/openai/openai-python/commit/628dead660c00435bf46e09081c7b90b7bbe4a8a)) + + +### Documentation + +* add examples + guidance on Realtime API support ([1cb00f8](https://github.com/openai/openai-python/commit/1cb00f8fed78052aacbb9e0fac997b6ba0d44d2a)) +* **readme:** example snippet for client context manager ([#1953](https://github.com/openai/openai-python/issues/1953)) ([ad80255](https://github.com/openai/openai-python/commit/ad802551d8aaf4e6eff711118676ec4e64392638)) + +## 1.57.4 (2024-12-13) + +Full Changelog: [v1.57.3...v1.57.4](https://github.com/openai/openai-python/compare/v1.57.3...v1.57.4) + +### Chores + +* **internal:** remove some duplicated imports ([#1946](https://github.com/openai/openai-python/issues/1946)) ([f94fddd](https://github.com/openai/openai-python/commit/f94fddd377015764b3c82919fdf956f619447b77)) +* **internal:** updated imports ([#1948](https://github.com/openai/openai-python/issues/1948)) ([13971fc](https://github.com/openai/openai-python/commit/13971fc450106746c0ae02ab931e68b770ee105e)) + +## 1.57.3 (2024-12-12) + +Full Changelog: [v1.57.2...v1.57.3](https://github.com/openai/openai-python/compare/v1.57.2...v1.57.3) + +### Chores + +* **internal:** add support for TypeAliasType ([#1942](https://github.com/openai/openai-python/issues/1942)) ([d3442ff](https://github.com/openai/openai-python/commit/d3442ff28f2394200e14122f683d1f94686e8231)) +* **internal:** bump pyright ([#1939](https://github.com/openai/openai-python/issues/1939)) ([190d1a8](https://github.com/openai/openai-python/commit/190d1a805dee7c37fb8f9dcb93b1715caa06cf95)) + +## 1.57.2 (2024-12-10) + +Full Changelog: [v1.57.1...v1.57.2](https://github.com/openai/openai-python/compare/v1.57.1...v1.57.2) + +### Bug Fixes + +* **azure:** handle trailing slash in `azure_endpoint` ([#1935](https://github.com/openai/openai-python/issues/1935)) ([69b73c5](https://github.com/openai/openai-python/commit/69b73c553b1982277c2f1b9d110ed951ddca689e)) + + +### Documentation + +* **readme:** fix http client proxies example ([#1932](https://github.com/openai/openai-python/issues/1932)) ([7a83e0f](https://github.com/openai/openai-python/commit/7a83e0fe4cc29e484ae417448b002c997745e4a3)) + +## 1.57.1 (2024-12-09) + +Full Changelog: [v1.57.0...v1.57.1](https://github.com/openai/openai-python/compare/v1.57.0...v1.57.1) + +### Chores + +* **internal:** bump pydantic dependency ([#1929](https://github.com/openai/openai-python/issues/1929)) ([5227c95](https://github.com/openai/openai-python/commit/5227c95eff9c7b1395e6d8f14b94652a91ed2ee2)) + +## 1.57.0 (2024-12-05) + +Full Changelog: [v1.56.2...v1.57.0](https://github.com/openai/openai-python/compare/v1.56.2...v1.57.0) + +### Features + +* **api:** updates ([#1924](https://github.com/openai/openai-python/issues/1924)) ([82ba614](https://github.com/openai/openai-python/commit/82ba6144682b0a6b3a22d4f764231c0c6afdcf6e)) + + +### Chores + +* bump openapi url ([#1922](https://github.com/openai/openai-python/issues/1922)) ([a472a8f](https://github.com/openai/openai-python/commit/a472a8fd0ba36b6897dcd02b6005fcf23f98f056)) + +## 1.56.2 (2024-12-04) + +Full Changelog: [v1.56.1...v1.56.2](https://github.com/openai/openai-python/compare/v1.56.1...v1.56.2) + +### Chores + +* make the `Omit` type public ([#1919](https://github.com/openai/openai-python/issues/1919)) ([4fb8a1c](https://github.com/openai/openai-python/commit/4fb8a1cf1f8df37ce8c027bbaaac85a648bae02a)) + +## 1.56.1 (2024-12-03) + +Full Changelog: [v1.56.0...v1.56.1](https://github.com/openai/openai-python/compare/v1.56.0...v1.56.1) + +### Bug Fixes + +* **cli:** remove usage of httpx proxies ([0e9fc3d](https://github.com/openai/openai-python/commit/0e9fc3dfbc7dec5b8c8f84dea9d87aad9f3d9cf6)) + + +### Chores + +* **internal:** bump pyright ([#1917](https://github.com/openai/openai-python/issues/1917)) ([0e87346](https://github.com/openai/openai-python/commit/0e8734637666ab22bc27fe4ec2cf7c39fddb5d08)) + +## 1.56.0 (2024-12-02) + +Full Changelog: [v1.55.3...v1.56.0](https://github.com/openai/openai-python/compare/v1.55.3...v1.56.0) + +### Features + +* **client:** make ChatCompletionStreamState public ([#1898](https://github.com/openai/openai-python/issues/1898)) ([dc7f6cb](https://github.com/openai/openai-python/commit/dc7f6cb2618686ff04bfdca228913cda3d320884)) + +## 1.55.3 (2024-11-28) + +Full Changelog: [v1.55.2...v1.55.3](https://github.com/openai/openai-python/compare/v1.55.2...v1.55.3) + +### Bug Fixes + +* **client:** compat with new httpx 0.28.0 release ([#1904](https://github.com/openai/openai-python/issues/1904)) ([72b6c63](https://github.com/openai/openai-python/commit/72b6c636c526885ef873580a07eff1c18e76bc10)) + +## 1.55.2 (2024-11-27) + +Full Changelog: [v1.55.1...v1.55.2](https://github.com/openai/openai-python/compare/v1.55.1...v1.55.2) + +### Chores + +* **internal:** exclude mypy from running on tests ([#1899](https://github.com/openai/openai-python/issues/1899)) ([e2496f1](https://github.com/openai/openai-python/commit/e2496f1d274126bdaa46a8256b3dd384b4ae244b)) + + +### Documentation + +* **assistants:** correct on_text_delta example ([#1896](https://github.com/openai/openai-python/issues/1896)) ([460b663](https://github.com/openai/openai-python/commit/460b663567ed1031467a8d69eb13fd3b3da38827)) + +## 1.55.1 (2024-11-25) + +Full Changelog: [v1.55.0...v1.55.1](https://github.com/openai/openai-python/compare/v1.55.0...v1.55.1) + +### Bug Fixes + +* **pydantic-v1:** avoid runtime error for assistants streaming ([#1885](https://github.com/openai/openai-python/issues/1885)) ([197c94b](https://github.com/openai/openai-python/commit/197c94b9e2620da8902aeed6959d2f871bb70461)) + + +### Chores + +* remove now unused `cached-property` dep ([#1867](https://github.com/openai/openai-python/issues/1867)) ([df5fac1](https://github.com/openai/openai-python/commit/df5fac1e557f79ed8d0935c48ca7f3f0bf77fa98)) +* remove now unused `cached-property` dep ([#1891](https://github.com/openai/openai-python/issues/1891)) ([feebaae](https://github.com/openai/openai-python/commit/feebaae85d76960cb8f1c58dd9b5180136c47962)) + + +### Documentation + +* add info log level to readme ([#1887](https://github.com/openai/openai-python/issues/1887)) ([358255d](https://github.com/openai/openai-python/commit/358255d15ed220f8c80a3c0861b98e61e909a7ae)) + +## 1.55.0 (2024-11-20) + +Full Changelog: [v1.54.5...v1.55.0](https://github.com/openai/openai-python/compare/v1.54.5...v1.55.0) + +### Features + +* **api:** add gpt-4o-2024-11-20 model ([#1877](https://github.com/openai/openai-python/issues/1877)) ([ff64c2a](https://github.com/openai/openai-python/commit/ff64c2a0733854ed8cc1d7dd959a8287b2ec8120)) + +## 1.54.5 (2024-11-19) + +Full Changelog: [v1.54.4...v1.54.5](https://github.com/openai/openai-python/compare/v1.54.4...v1.54.5) + +### Bug Fixes + +* **asyncify:** avoid hanging process under certain conditions ([#1853](https://github.com/openai/openai-python/issues/1853)) ([3d23437](https://github.com/openai/openai-python/commit/3d234377e7c9cd19db5186688612eb18e68cec8f)) + + +### Chores + +* **internal:** minor test changes ([#1874](https://github.com/openai/openai-python/issues/1874)) ([189339d](https://github.com/openai/openai-python/commit/189339d2a09d23ea1883286972f366e19b397f91)) +* **internal:** spec update ([#1873](https://github.com/openai/openai-python/issues/1873)) ([24c81f7](https://github.com/openai/openai-python/commit/24c81f729ae09ba3cec5542e5cc955c8b05b0f88)) +* **tests:** limit array example length ([#1870](https://github.com/openai/openai-python/issues/1870)) ([1e550df](https://github.com/openai/openai-python/commit/1e550df708fc3b5d903b7adfa2180058a216b676)) + +## 1.54.4 (2024-11-12) + +Full Changelog: [v1.54.3...v1.54.4](https://github.com/openai/openai-python/compare/v1.54.3...v1.54.4) + +### Bug Fixes + +* don't use dicts as iterables in transform ([#1865](https://github.com/openai/openai-python/issues/1865)) ([76a51b1](https://github.com/openai/openai-python/commit/76a51b11efae50659a562197b1e18c6343964b56)) + + +### Documentation + +* bump models in example snippets to gpt-4o ([#1861](https://github.com/openai/openai-python/issues/1861)) ([adafe08](https://github.com/openai/openai-python/commit/adafe0859178d406fa93b38f3547f3d262651331)) +* move comments in example snippets ([#1860](https://github.com/openai/openai-python/issues/1860)) ([362cf74](https://github.com/openai/openai-python/commit/362cf74d6c34506f98f6c4fb2304357be21f7691)) +* **readme:** add missing asyncio import ([#1858](https://github.com/openai/openai-python/issues/1858)) ([dec9d0c](https://github.com/openai/openai-python/commit/dec9d0c97b702b6bcf9c71f5bdd6172bb5718354)) + +## 1.54.3 (2024-11-06) + +Full Changelog: [v1.54.2...v1.54.3](https://github.com/openai/openai-python/compare/v1.54.2...v1.54.3) + +### Bug Fixes + +* **logs:** redact sensitive headers ([#1850](https://github.com/openai/openai-python/issues/1850)) ([466608f](https://github.com/openai/openai-python/commit/466608fa56b7a9939c08a4c78be2f6fe4a05111b)) + +## 1.54.2 (2024-11-06) + +Full Changelog: [v1.54.1...v1.54.2](https://github.com/openai/openai-python/compare/v1.54.1...v1.54.2) + +### Chores + +* **tests:** adjust retry timeout values ([#1851](https://github.com/openai/openai-python/issues/1851)) ([cc8009c](https://github.com/openai/openai-python/commit/cc8009c9de56fe80f2689f69e7b891ff4ed297a3)) + +## 1.54.1 (2024-11-05) + +Full Changelog: [v1.54.0...v1.54.1](https://github.com/openai/openai-python/compare/v1.54.0...v1.54.1) + +### Bug Fixes + +* add new prediction param to all methods ([6aa424d](https://github.com/openai/openai-python/commit/6aa424d076098312801febd938bd4b5e8baf4851)) + +## 1.54.0 (2024-11-04) + +Full Changelog: [v1.53.1...v1.54.0](https://github.com/openai/openai-python/compare/v1.53.1...v1.54.0) + +### Features + +* **api:** add support for predicted outputs ([#1847](https://github.com/openai/openai-python/issues/1847)) ([42a4103](https://github.com/openai/openai-python/commit/42a410379a1b5f72424cc2e96dc6ddff22fd00be)) +* **project:** drop support for Python 3.7 ([#1845](https://github.com/openai/openai-python/issues/1845)) ([0ed5b1a](https://github.com/openai/openai-python/commit/0ed5b1a9302ccf2f40c3c751cd777740a4749cda)) + +## 1.53.1 (2024-11-04) + +Full Changelog: [v1.53.0...v1.53.1](https://github.com/openai/openai-python/compare/v1.53.0...v1.53.1) + +### Bug Fixes + +* don't use dicts as iterables in transform ([#1842](https://github.com/openai/openai-python/issues/1842)) ([258f265](https://github.com/openai/openai-python/commit/258f26535744ab3b2f0746991fd29eae72ebd667)) +* support json safe serialization for basemodel subclasses ([#1844](https://github.com/openai/openai-python/issues/1844)) ([2b80c90](https://github.com/openai/openai-python/commit/2b80c90c21d3b2468dfa3bf40c08c5b0e0eebffa)) + + +### Chores + +* **internal:** bump mypy ([#1839](https://github.com/openai/openai-python/issues/1839)) ([d92f959](https://github.com/openai/openai-python/commit/d92f959aa6f49be56574b4d1d1ac5ac48689dd46)) + +## 1.53.0 (2024-10-30) + +Full Changelog: [v1.52.2...v1.53.0](https://github.com/openai/openai-python/compare/v1.52.2...v1.53.0) + +### Features + +* **api:** add new, expressive voices for Realtime and Audio in Chat Completions ([7cf0a49](https://github.com/openai/openai-python/commit/7cf0a4958e4c985bef4d18bb919fa3948f389a82)) + + +### Chores + +* **internal:** bump pytest to v8 & pydantic ([#1829](https://github.com/openai/openai-python/issues/1829)) ([0e67a8a](https://github.com/openai/openai-python/commit/0e67a8af5daf9da029d2bd4bdf341cc8a494254a)) + +## 1.52.2 (2024-10-23) + +Full Changelog: [v1.52.1...v1.52.2](https://github.com/openai/openai-python/compare/v1.52.1...v1.52.2) + +### Chores + +* **internal:** update spec version ([#1816](https://github.com/openai/openai-python/issues/1816)) ([c23282a](https://github.com/openai/openai-python/commit/c23282a328c48af90a88673ff5f6cc7a866f8758)) + +## 1.52.1 (2024-10-22) + +Full Changelog: [v1.52.0...v1.52.1](https://github.com/openai/openai-python/compare/v1.52.0...v1.52.1) + +### Bug Fixes + +* **client/async:** correctly retry in all cases ([#1803](https://github.com/openai/openai-python/issues/1803)) ([9fe3f3f](https://github.com/openai/openai-python/commit/9fe3f3f925e06769b7ef6abbf1314a5e82749b4a)) + + +### Chores + +* **internal:** bump ruff dependency ([#1801](https://github.com/openai/openai-python/issues/1801)) ([859c672](https://github.com/openai/openai-python/commit/859c6725865f1b3285698f68693f9491d511f7ea)) +* **internal:** remove unused black config ([#1807](https://github.com/openai/openai-python/issues/1807)) ([112dab0](https://github.com/openai/openai-python/commit/112dab0290342654265db612c37d327d652251bb)) +* **internal:** update spec version ([#1810](https://github.com/openai/openai-python/issues/1810)) ([aa25b7b](https://github.com/openai/openai-python/commit/aa25b7b88823836b418a63da59491f5f3842773c)) +* **internal:** update test syntax ([#1798](https://github.com/openai/openai-python/issues/1798)) ([d3098dd](https://github.com/openai/openai-python/commit/d3098dd0b9fbe627c21a8ad39c119d125b7cdb54)) +* **tests:** add more retry tests ([#1806](https://github.com/openai/openai-python/issues/1806)) ([5525a1b](https://github.com/openai/openai-python/commit/5525a1ba536058ecc13411e1f98e88f7ec4bf8b9)) + +## 1.52.0 (2024-10-17) + +Full Changelog: [v1.51.2...v1.52.0](https://github.com/openai/openai-python/compare/v1.51.2...v1.52.0) + +### Features + +* **api:** add gpt-4o-audio-preview model for chat completions ([#1796](https://github.com/openai/openai-python/issues/1796)) ([fbf1e0c](https://github.com/openai/openai-python/commit/fbf1e0c25c4d163f06b61a43d1a94ce001033a7b)) + +## 1.51.2 (2024-10-08) + +Full Changelog: [v1.51.1...v1.51.2](https://github.com/openai/openai-python/compare/v1.51.1...v1.51.2) + +### Chores + +* add repr to PageInfo class ([#1780](https://github.com/openai/openai-python/issues/1780)) ([63118ee](https://github.com/openai/openai-python/commit/63118ee3c2481d217682e8a31337bdcc16893127)) + +## 1.51.1 (2024-10-07) + +Full Changelog: [v1.51.0...v1.51.1](https://github.com/openai/openai-python/compare/v1.51.0...v1.51.1) + +### Bug Fixes + +* **client:** avoid OverflowError with very large retry counts ([#1779](https://github.com/openai/openai-python/issues/1779)) ([fb1dacf](https://github.com/openai/openai-python/commit/fb1dacfa4d9447d123c38ab3d3d433d900d32ec5)) + + +### Chores + +* **internal:** add support for parsing bool response content ([#1774](https://github.com/openai/openai-python/issues/1774)) ([aa2e25f](https://github.com/openai/openai-python/commit/aa2e25f9a4a632357051397ea34d269eafba026d)) + + +### Documentation + +* fix typo in fenced code block language ([#1769](https://github.com/openai/openai-python/issues/1769)) ([57bbc15](https://github.com/openai/openai-python/commit/57bbc155210cc439a36f4e5cbd082e94c3349d78)) +* improve and reference contributing documentation ([#1767](https://github.com/openai/openai-python/issues/1767)) ([a985a8b](https://github.com/openai/openai-python/commit/a985a8b8ab8d0b364bd3c26b6423a7c49ae7b1ce)) + +## 1.51.0 (2024-10-01) + +Full Changelog: [v1.50.2...v1.51.0](https://github.com/openai/openai-python/compare/v1.50.2...v1.51.0) + +### Features + +* **api:** support storing chat completions, enabling evals and model distillation in the dashboard ([2840c6d](https://github.com/openai/openai-python/commit/2840c6df94afb44cfd80efabe0405898331ee267)) + + +### Chores + +* **docs:** fix maxium typo ([#1762](https://github.com/openai/openai-python/issues/1762)) ([de94553](https://github.com/openai/openai-python/commit/de94553f93d71fc6c8187c8d3fbe924a71cc46dd)) +* **internal:** remove ds store ([47a3968](https://github.com/openai/openai-python/commit/47a3968f9b318eb02d5602f5b10e7d9e69c3ae84)) + + +### Documentation + +* **helpers:** fix method name typo ([#1764](https://github.com/openai/openai-python/issues/1764)) ([e1bcfe8](https://github.com/openai/openai-python/commit/e1bcfe86554017ac63055060153c4fd72e65c0cf)) + +## 1.50.2 (2024-09-27) + +Full Changelog: [v1.50.1...v1.50.2](https://github.com/openai/openai-python/compare/v1.50.1...v1.50.2) + +### Bug Fixes + +* **audio:** correct types for transcriptions / translations ([#1755](https://github.com/openai/openai-python/issues/1755)) ([76c1f3f](https://github.com/openai/openai-python/commit/76c1f3f318b68003aae124c02efc4547a398a864)) + +## 1.50.1 (2024-09-27) + +Full Changelog: [v1.50.0...v1.50.1](https://github.com/openai/openai-python/compare/v1.50.0...v1.50.1) + +### Documentation + +* **helpers:** fix chat completion anchor ([#1753](https://github.com/openai/openai-python/issues/1753)) ([956d4e8](https://github.com/openai/openai-python/commit/956d4e8e32507fbce399f4619e06daa9d37a0532)) + +## 1.50.0 (2024-09-26) + +Full Changelog: [v1.49.0...v1.50.0](https://github.com/openai/openai-python/compare/v1.49.0...v1.50.0) + +### Features + +* **structured outputs:** add support for accessing raw responses ([#1748](https://github.com/openai/openai-python/issues/1748)) ([0189e28](https://github.com/openai/openai-python/commit/0189e28b0b062a28b16343da0460a4f0f4e17a9a)) + + +### Chores + +* **pydantic v1:** exclude specific properties when rich printing ([#1751](https://github.com/openai/openai-python/issues/1751)) ([af535ce](https://github.com/openai/openai-python/commit/af535ce6a523eca39438f117a3e55f16064567a9)) + +## 1.49.0 (2024-09-26) + +Full Changelog: [v1.48.0...v1.49.0](https://github.com/openai/openai-python/compare/v1.48.0...v1.49.0) + +### Features + +* **api:** add omni-moderation model ([#1750](https://github.com/openai/openai-python/issues/1750)) ([05b50da](https://github.com/openai/openai-python/commit/05b50da5428d5c7b5ea09626bcd88f8423762bf8)) + + +### Chores + +* **internal:** update test snapshots ([#1749](https://github.com/openai/openai-python/issues/1749)) ([42f054e](https://github.com/openai/openai-python/commit/42f054ee7afa8ce8316c2ecd90608a0f7e13bfdd)) + +## 1.48.0 (2024-09-25) + +Full Changelog: [v1.47.1...v1.48.0](https://github.com/openai/openai-python/compare/v1.47.1...v1.48.0) + +### Features + +* **client:** allow overriding retry count header ([#1745](https://github.com/openai/openai-python/issues/1745)) ([9f07d4d](https://github.com/openai/openai-python/commit/9f07d4dbd6f24108a1f5e0309037318858f5a229)) + + +### Bug Fixes + +* **audio:** correct response_format translations type ([#1743](https://github.com/openai/openai-python/issues/1743)) ([b912108](https://github.com/openai/openai-python/commit/b9121089c696bc943323e2e75d4706401d809aaa)) + + +### Chores + +* **internal:** use `typing_extensions.overload` instead of `typing` ([#1740](https://github.com/openai/openai-python/issues/1740)) ([2522bd5](https://github.com/openai/openai-python/commit/2522bd59f7b5e903e4fc856a4c5dbdbe66bba37f)) + +## 1.47.1 (2024-09-23) + +Full Changelog: [v1.47.0...v1.47.1](https://github.com/openai/openai-python/compare/v1.47.0...v1.47.1) + +### Bug Fixes + +* **pydantic v1:** avoid warnings error ([1e8e7d1](https://github.com/openai/openai-python/commit/1e8e7d1f01a4ab4153085bc20484a19613d993b3)) + +## 1.47.0 (2024-09-20) + +Full Changelog: [v1.46.1...v1.47.0](https://github.com/openai/openai-python/compare/v1.46.1...v1.47.0) + +### Features + +* **client:** send retry count header ([21b0c00](https://github.com/openai/openai-python/commit/21b0c0043406d81971f87455e5a48b17935dc346)) + + +### Chores + +* **types:** improve type name for embedding models ([#1730](https://github.com/openai/openai-python/issues/1730)) ([4b4eb2b](https://github.com/openai/openai-python/commit/4b4eb2b37877728d2124ad5651ceebf615c0ab28)) + +## 1.46.1 (2024-09-19) + +Full Changelog: [v1.46.0...v1.46.1](https://github.com/openai/openai-python/compare/v1.46.0...v1.46.1) + +### Bug Fixes + +* **client:** handle domains with underscores ([#1726](https://github.com/openai/openai-python/issues/1726)) ([cd194df](https://github.com/openai/openai-python/commit/cd194dfdc418a84589bd903357cba349e9ad3e78)) + + +### Chores + +* **streaming:** silence pydantic model_dump warnings ([#1722](https://github.com/openai/openai-python/issues/1722)) ([30f84b9](https://github.com/openai/openai-python/commit/30f84b96081ac37f60e40a75d765dbbf563b61b3)) + +## 1.46.0 (2024-09-17) + +Full Changelog: [v1.45.1...v1.46.0](https://github.com/openai/openai-python/compare/v1.45.1...v1.46.0) + +### Features + +* **client:** add ._request_id property to object responses ([#1707](https://github.com/openai/openai-python/issues/1707)) ([8b3da05](https://github.com/openai/openai-python/commit/8b3da05a35b33245aec98693a0540ace6218a61b)) + + +### Documentation + +* **readme:** add examples for chat with image content ([#1703](https://github.com/openai/openai-python/issues/1703)) ([192b8f2](https://github.com/openai/openai-python/commit/192b8f2b6a49f462e48c1442858931875524ab49)) + ## 1.45.1 (2024-09-16) Full Changelog: [v1.45.0...v1.45.1](https://github.com/openai/openai-python/compare/v1.45.0...v1.45.1) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5a6639b8fc..f92377d459 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,13 @@ ### With Rye -We use [Rye](https://rye-up.com/) to manage dependencies so we highly recommend [installing it](https://rye-up.com/guide/installation/) as it will automatically provision a Python environment with the expected Python version. +We use [Rye](https://rye.astral.sh/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run: -After installing Rye, you'll just have to run this command: +```sh +$ ./scripts/bootstrap +``` + +Or [install Rye manually](https://rye.astral.sh/guide/installation/) and run: ```sh $ rye sync --all-features @@ -13,8 +17,7 @@ $ rye sync --all-features You can then run scripts using `rye run python script.py` or by activating the virtual environment: ```sh -$ rye shell -# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work +# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work $ source .venv/bin/activate # now you can omit the `rye run` prefix @@ -39,17 +42,17 @@ modify the contents of the `src/openai/lib/` and `examples/` directories. All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. -```bash +```py # add an example to examples/.py #!/usr/bin/env -S rye run python … ``` -``` -chmod +x examples/.py +```sh +$ chmod +x examples/.py # run the example against your api -./examples/.py +$ ./examples/.py ``` ## Using the repository from source @@ -58,8 +61,8 @@ If you’d like to use the repository from source, you can either install from g To install via git: -```bash -pip install git+ssh://git@github.com/openai/openai-python.git +```sh +$ pip install git+ssh://git@github.com/openai/openai-python.git ``` Alternatively, you can build from source and install the wheel file: @@ -68,29 +71,28 @@ Building this package will create two files in the `dist/` directory, a `.tar.gz To create a distributable version of the library, all you have to do is run this command: -```bash -rye build +```sh +$ rye build # or -python -m build +$ python -m build ``` Then to install: ```sh -pip install ./path-to-wheel-file.whl +$ pip install ./path-to-wheel-file.whl ``` ## Running tests -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. +Most tests require you to [set up a mock server](https://github.com/dgellow/steady) against the OpenAPI spec to run the tests. -```bash -# you will need npm installed -npx prism mock path/to/your/openapi.yml +```sh +$ ./scripts/mock ``` -```bash -rye run pytest +```sh +$ ./scripts/test ``` ## Linting and formatting @@ -100,14 +102,14 @@ This repository uses [ruff](https://github.com/astral-sh/ruff) and To lint: -```bash -rye run lint +```sh +$ ./scripts/lint ``` To format and fix all ruff issues automatically: -```bash -rye run format +```sh +$ ./scripts/format ``` ## Publishing and releases @@ -117,9 +119,8 @@ the changes aren't made through the automated pipeline, you may want to make rel ### Publish with a GitHub workflow -You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/openai/openai-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. +You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/openai/openai-python/actions/workflows/publish-pypi.yml). PyPI publishing uses Trusted Publishing, so the PyPI project must trust this repository's GitHub Actions workflow and the `publish` environment. ### Publish manually -If you need to manually release a package, you can run the `bin/publish-pypi` script with a `PYPI_TOKEN` set on -the environment. +If you need to retry a PyPI release, use the `Publish PyPI` GitHub action. Local manual publishing is not the standard release path because the GitHub workflow uses OIDC instead of a long-lived PyPI token. diff --git a/LICENSE b/LICENSE index 621a6becfb..cbb5bb26e4 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2024 OpenAI + Copyright 2026 OpenAI Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 9e9628ff83..6f87246470 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # OpenAI Python API library -[![PyPI version](https://img.shields.io/pypi/v/openai.svg)](https://pypi.org/project/openai/) + +[![PyPI version](https://img.shields.io/pypi/v/openai.svg?label=pypi%20(stable))](https://pypi.org/project/openai/) -The OpenAI Python library provides convenient access to the OpenAI REST API from any Python 3.7+ +The OpenAI Python library provides convenient access to the OpenAI REST API from any Python 3.9+ application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). @@ -10,13 +11,10 @@ It is generated from our [OpenAPI specification](https://github.com/openai/opena ## Documentation -The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs). The full API of this library can be found in [api.md](api.md). +The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs/api-reference). The full API of this library can be found in [api.md](api.md). ## Installation -> [!IMPORTANT] -> The SDK was rewritten in v1, which was released November 6th 2023. See the [v1 migration guide](https://github.com/openai/openai-python/discussions/742), which includes scripts to automatically update your code. - ```sh # install from PyPI pip install openai @@ -26,6 +24,8 @@ pip install openai The full API of this library can be found in [api.md](api.md). +The primary API for interacting with OpenAI models is the [Responses API](https://platform.openai.com/docs/api-reference/responses). You can generate text from the model with the code below. + ```python import os from openai import OpenAI @@ -35,113 +35,185 @@ client = OpenAI( api_key=os.environ.get("OPENAI_API_KEY"), ) -chat_completion = client.chat.completions.create( +response = client.responses.create( + model="gpt-5.5", + instructions="You are a coding assistant that talks like a pirate.", + input="How do I check if a Python object is an instance of a class?", +) + +print(response.output_text) +``` + +The previous standard (supported indefinitely) for generating text is the [Chat Completions API](https://platform.openai.com/docs/api-reference/chat). You can use that API to generate text from the model with the code below. + +```python +from openai import OpenAI + +client = OpenAI() + +completion = client.chat.completions.create( + model="gpt-5.5", messages=[ + {"role": "developer", "content": "Talk like a pirate."}, { "role": "user", - "content": "Say this is a test", - } + "content": "How do I check if a Python object is an instance of a class?", + }, ], - model="gpt-3.5-turbo", ) + +print(completion.choices[0].message.content) ``` While you can provide an `api_key` keyword argument, we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) to add `OPENAI_API_KEY="My API Key"` to your `.env` file -so that your API Key is not stored in source control. +so that your API key is not stored in source control. +[Get an API key here](https://platform.openai.com/settings/organization/api-keys). -### Vision +### Workload Identity Authentication + +For secure, automated environments like cloud-managed Kubernetes, Azure, and Google Cloud Platform, you can use workload identity authentication with short-lived tokens from cloud identity providers instead of long-lived API keys. -With a hosted image: +#### Kubernetes (service account tokens) ```python +from openai import OpenAI +from openai.auth import k8s_service_account_token_provider + +client = OpenAI( + workload_identity={ + "identity_provider_id": "idp-123", + "service_account_id": "sa-456", + "provider": k8s_service_account_token_provider( + "/var/run/secrets/kubernetes.io/serviceaccount/token" + ), + }, +) + response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - { - "role": "user", - "content": [ - {"type": "text", "text": prompt}, - { - "type": "image_url", - "image_url": {"url": f"{img_url}"}, - }, - ], - } - ], + model="gpt-5.5", + messages=[{"role": "user", "content": "Hello!"}], ) ``` -With the image as a base64 encoded string: +#### Azure (managed identity) ```python -response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - { - "role": "user", - "content": [ - {"type": "text", "text": prompt}, - { - "type": "image_url", - "image_url": {"url": f"data:{img_type};base64,{img_b64_str}"}, - }, - ], - } - ], +from openai import OpenAI +from openai.auth import azure_managed_identity_token_provider + +client = OpenAI( + workload_identity={ + "identity_provider_id": "idp-123", + "service_account_id": "sa-456", + "provider": azure_managed_identity_token_provider( + resource="https://management.azure.com/", + ), + }, ) ``` -### Polling Helpers - -When interacting with the API some actions such as starting a Run and adding files to vector stores are asynchronous and take time to complete. The SDK includes -helper functions which will poll the status until it reaches a terminal state and then return the resulting object. -If an API method results in an action that could benefit from polling there will be a corresponding version of the -method ending in '\_and_poll'. - -For instance to create a Run and poll until it reaches a terminal state you can run: +#### Google Cloud Platform (compute engine metadata) ```python -run = client.beta.threads.runs.create_and_poll( - thread_id=thread.id, - assistant_id=assistant.id, +from openai import OpenAI +from openai.auth import gcp_id_token_provider + +client = OpenAI( + workload_identity={ + "identity_provider_id": "idp-123", + "service_account_id": "sa-456", + "provider": gcp_id_token_provider(audience="https://api.openai.com/v1"), + }, ) ``` -More information on the lifecycle of a Run can be found in the [Run Lifecycle Documentation](https://platform.openai.com/docs/assistants/how-it-works/run-lifecycle) +#### Custom subject token provider + +```python +from openai import OpenAI + + +def get_custom_token() -> str: + return "your-jwt-token" -### Bulk Upload Helpers -When creating and interacting with vector stores, you can use polling helpers to monitor the status of operations. -For convenience, we also provide a bulk upload helper to allow you to simultaneously upload several files at once. +client = OpenAI( + workload_identity={ + "identity_provider_id": "idp-123", + "service_account_id": "sa-456", + "provider": { + "token_type": "jwt", + "get_token": get_custom_token, + }, + } +) +``` + +You can also customize the token refresh buffer (default is 1200 seconds (20 minutes) before expiration): ```python -sample_files = [Path("sample-paper.pdf"), ...] +from openai import OpenAI +from openai.auth import k8s_service_account_token_provider -batch = await client.vector_stores.file_batches.upload_and_poll( - store.id, - files=sample_files, +client = OpenAI( + workload_identity={ + "identity_provider_id": "idp-123", + "service_account_id": "sa-456", + "provider": k8s_service_account_token_provider("/var/token"), + "refresh_buffer_seconds": 120.0, + } ) ``` -### Streaming Helpers +### Vision -The SDK also includes helpers to process streams and handle incoming events. +With an image URL: ```python -with client.beta.threads.runs.stream( - thread_id=thread.id, - assistant_id=assistant.id, - instructions="Please address the user as Jane Doe. The user has a premium account.", -) as stream: - for event in stream: - # Print the text from text delta events - if event.type == "thread.message.delta" and event.data.delta.content: - print(event.data.delta.content[0].text) +prompt = "What is in this image?" +img_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/2023_06_08_Raccoon1.jpg/1599px-2023_06_08_Raccoon1.jpg" + +response = client.responses.create( + model="gpt-5.5", + input=[ + { + "role": "user", + "content": [ + {"type": "input_text", "text": prompt}, + {"type": "input_image", "image_url": f"{img_url}"}, + ], + } + ], +) ``` -More information on streaming helpers can be found in the dedicated documentation: [helpers.md](helpers.md) +With the image as a base64 encoded string: + +```python +import base64 +from openai import OpenAI + +client = OpenAI() + +prompt = "What is in this image?" +with open("path/to/image.png", "rb") as image_file: + b64_image = base64.b64encode(image_file.read()).decode("utf-8") + +response = client.responses.create( + model="gpt-5.5", + input=[ + { + "role": "user", + "content": [ + {"type": "input_text", "text": prompt}, + {"type": "input_image", "image_url": f"data:image/png;base64,{b64_image}"}, + ], + } + ], +) +``` ## Async usage @@ -159,15 +231,10 @@ client = AsyncOpenAI( async def main() -> None: - chat_completion = await client.chat.completions.create( - messages=[ - { - "role": "user", - "content": "Say this is a test", - } - ], - model="gpt-3.5-turbo", + response = await client.responses.create( + model="gpt-5.5", input="Explain disestablishmentarianism to a smart five year old." ) + print(response.output_text) asyncio.run(main()) @@ -175,6 +242,45 @@ asyncio.run(main()) Functionality between the synchronous and asynchronous clients is otherwise identical. +### With aiohttp + +By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. + +You can enable this by installing `aiohttp`: + +```sh +# install from PyPI +pip install openai[aiohttp] +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import os +import asyncio +from openai import DefaultAioHttpClient +from openai import AsyncOpenAI + + +async def main() -> None: + async with AsyncOpenAI( + api_key=os.environ.get("OPENAI_API_KEY"), # This is the default and can be omitted + http_client=DefaultAioHttpClient(), + ) as client: + chat_completion = await client.chat.completions.create( + messages=[ + { + "role": "user", + "content": "Say this is a test", + } + ], + model="gpt-5.5", + ) + + +asyncio.run(main()) +``` + ## Streaming responses We provide support for streaming responses using Server Side Events (SSE). @@ -184,75 +290,101 @@ from openai import OpenAI client = OpenAI() -stream = client.chat.completions.create( - model="gpt-4", - messages=[{"role": "user", "content": "Say this is a test"}], +stream = client.responses.create( + model="gpt-5.5", + input="Write a one-sentence bedtime story about a unicorn.", stream=True, ) -for chunk in stream: - print(chunk.choices[0].delta.content or "", end="") + +for event in stream: + print(event) ``` The async client uses the exact same interface. ```python +import asyncio from openai import AsyncOpenAI client = AsyncOpenAI() async def main(): - stream = await client.chat.completions.create( - model="gpt-4", - messages=[{"role": "user", "content": "Say this is a test"}], + stream = await client.responses.create( + model="gpt-5.5", + input="Write a one-sentence bedtime story about a unicorn.", stream=True, ) - async for chunk in stream: - print(chunk.choices[0].delta.content or "", end="") + + async for event in stream: + print(event) asyncio.run(main()) ``` -## Module-level client +## Realtime API -> [!IMPORTANT] -> We highly recommend instantiating client instances instead of relying on the global client. +The Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as [function calling](https://platform.openai.com/docs/guides/function-calling) through a WebSocket connection. -We also expose a global client instance that is accessible in a similar fashion to versions prior to v1. +Under the hood the SDK uses the [`websockets`](https://websockets.readthedocs.io/en/stable/) library to manage connections. + +The Realtime API works through a combination of client-sent events and server-sent events. Clients can send events to do things like update session configuration or send text and audio inputs. Server events confirm when audio responses have completed, or when a text response from the model has been received. A full event reference can be found [here](https://platform.openai.com/docs/api-reference/realtime-client-events) and a guide can be found [here](https://platform.openai.com/docs/guides/realtime). + +Basic text based example: ```py -import openai +import asyncio +from openai import AsyncOpenAI -# optional; defaults to `os.environ['OPENAI_API_KEY']` -openai.api_key = '...' +async def main(): + client = AsyncOpenAI() -# all client options can be configured just like the `OpenAI` instantiation counterpart -openai.base_url = "https://..." -openai.default_headers = {"x-foo": "true"} + async with client.realtime.connect(model="gpt-realtime-2") as connection: + await connection.session.update( + session={"type": "realtime", "output_modalities": ["text"]} + ) -completion = openai.chat.completions.create( - model="gpt-4", - messages=[ - { - "role": "user", - "content": "How do I output all files in a directory using Python?", - }, - ], -) -print(completion.choices[0].message.content) + await connection.conversation.item.create( + item={ + "type": "message", + "role": "user", + "content": [{"type": "input_text", "text": "Say hello!"}], + } + ) + await connection.response.create() + + async for event in connection: + if event.type == "response.output_text.delta": + print(event.delta, flush=True, end="") + + elif event.type == "response.output_text.done": + print() + + elif event.type == "response.done": + break + +asyncio.run(main()) ``` -The API is the exact same as the standard client instance-based API. +However the real magic of the Realtime API is handling audio inputs / outputs, see this example [TUI script](https://github.com/openai/openai-python/blob/main/examples/realtime/push_to_talk_app.py) for a fully fledged example. -This is intended to be used within REPLs or notebooks for faster iteration, **not** in application code. +### Realtime error handling -We recommend that you always instantiate a client (e.g., with `client = OpenAI()`) in application code because: +Whenever an error occurs, the Realtime API will send an [`error` event](https://platform.openai.com/docs/guides/realtime-model-capabilities#error-handling) and the connection will stay open and remain usable. This means you need to handle it yourself, as _no errors are raised directly_ by the SDK when an `error` event comes in. -- It can be difficult to reason about where client options are configured -- It's not possible to change certain client options without potentially causing race conditions -- It's harder to mock for testing purposes -- It's not possible to control cleanup of network connections +```py +client = AsyncOpenAI() + +async with client.realtime.connect(model="gpt-realtime-2") as connection: + ... + async for event in connection: + if event.type == 'error': + print(event.error.type) + print(event.error.code) + print(event.error.event_id) + print(event.error.message) +``` ## Using types @@ -343,21 +475,21 @@ from openai import OpenAI client = OpenAI() -completion = client.chat.completions.create( - messages=[ +response = client.responses.create( + input=[ { "role": "user", - "content": "Can you generate an example json object describing a fruit?", + "content": "How much ?", } ], - model="gpt-3.5-turbo-1106", - response_format={"type": "json_object"}, + model="gpt-5.5", + text={"format": {"type": "json_object"}}, ) ``` ## File uploads -Request parameters that correspond to file uploads can be passed as `bytes`, a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. +Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. ```python from pathlib import Path @@ -373,6 +505,86 @@ client.files.create( The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. +## Webhook Verification + +Verifying webhook signatures is _optional but encouraged_. + +For more information about webhooks, see [the API docs](https://platform.openai.com/docs/guides/webhooks). + +### Parsing webhook payloads + +For most use cases, you will likely want to verify the webhook and parse the payload at the same time. To achieve this, we provide the method `client.webhooks.unwrap()`, which parses a webhook request and verifies that it was sent by OpenAI. This method will raise an error if the signature is invalid. + +Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). The `.unwrap()` method will parse this JSON for you into an event object after verifying the webhook was sent from OpenAI. + +```python +from openai import OpenAI +from flask import Flask, request + +app = Flask(__name__) +client = OpenAI() # OPENAI_WEBHOOK_SECRET environment variable is used by default + + +@app.route("/webhook", methods=["POST"]) +def webhook(): + request_body = request.get_data(as_text=True) + + try: + event = client.webhooks.unwrap(request_body, request.headers) + + if event.type == "response.completed": + print("Response completed:", event.data) + elif event.type == "response.failed": + print("Response failed:", event.data) + else: + print("Unhandled event type:", event.type) + + return "ok" + except Exception as e: + print("Invalid signature:", e) + return "Invalid signature", 400 + + +if __name__ == "__main__": + app.run(port=8000) +``` + +### Verifying webhook payloads directly + +In some cases, you may want to verify the webhook separately from parsing the payload. If you prefer to handle these steps separately, we provide the method `client.webhooks.verify_signature()` to _only verify_ the signature of a webhook request. Like `.unwrap()`, this method will raise an error if the signature is invalid. + +Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). You will then need to parse the body after verifying the signature. + +```python +import json +from openai import OpenAI +from flask import Flask, request + +app = Flask(__name__) +client = OpenAI() # OPENAI_WEBHOOK_SECRET environment variable is used by default + + +@app.route("/webhook", methods=["POST"]) +def webhook(): + request_body = request.get_data(as_text=True) + + try: + client.webhooks.verify_signature(request_body, request.headers) + + # Parse the body after verification + event = json.loads(request_body) + print("Verified event:", event) + + return "ok" + except Exception as e: + print("Invalid signature:", e) + return "Invalid signature", 400 + + +if __name__ == "__main__": + app.run(port=8000) +``` + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `openai.APIConnectionError` is raised. @@ -390,7 +602,7 @@ client = OpenAI() try: client.fine_tuning.jobs.create( - model="gpt-3.5-turbo", + model="gpt-4o", training_file="file-abc123", ) except openai.APIConnectionError as e: @@ -404,7 +616,7 @@ except openai.APIStatusError as e: print(e.response) ``` -Error codes are as followed: +Error codes are as follows: | Status Code | Error Type | | ----------- | -------------------------- | @@ -417,7 +629,40 @@ Error codes are as followed: | >=500 | `InternalServerError` | | N/A | `APIConnectionError` | -### Retries +## Request IDs + +> For more information on debugging requests, see [these docs](https://platform.openai.com/docs/api-reference/debugging-requests) + +All object responses in the SDK provide a `_request_id` property which is added from the `x-request-id` response header so that you can quickly log failing requests and report them back to OpenAI. + +```python +response = await client.responses.create( + model="gpt-5.5", + input="Say 'this is a test'.", +) +print(response._request_id) # req_123 +``` + +Note that unlike other properties that use an `_` prefix, the `_request_id` property +_is_ public. Unless documented otherwise, _all_ other `_` prefix properties, +methods and modules are _private_. + +> [!IMPORTANT] +> If you need to access request IDs for failed requests you must catch the `APIStatusError` exception + +```python +import openai + +try: + completion = await client.chat.completions.create( + messages=[{"role": "user", "content": "Say this is a test"}], model="gpt-5.5" + ) +except openai.APIStatusError as exc: + print(exc.request_id) # req_123 + raise exc +``` + +## Retries Certain errors are automatically retried 2 times by default, with a short exponential backoff. Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, @@ -439,17 +684,17 @@ client.with_options(max_retries=5).chat.completions.create( messages=[ { "role": "user", - "content": "How can I get the name of the current day in Node.js?", + "content": "How can I get the name of the current day in JavaScript?", } ], - model="gpt-3.5-turbo", + model="gpt-5.5", ) ``` -### Timeouts +## Timeouts By default requests time out after 10 minutes. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: +which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: ```python from openai import OpenAI @@ -473,7 +718,7 @@ client.with_options(timeout=5.0).chat.completions.create( "content": "How can I list all files in a directory using Python?", } ], - model="gpt-3.5-turbo", + model="gpt-5.5", ) ``` @@ -487,12 +732,14 @@ Note that requests that time out are [retried twice by default](#retries). We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module. -You can enable logging by setting the environment variable `OPENAI_LOG` to `debug`. +You can enable logging by setting the environment variable `OPENAI_LOG` to `info`. ```shell -$ export OPENAI_LOG=debug +$ export OPENAI_LOG=info ``` +Or to `debug` for more verbose logging. + ### How to tell whether `None` means `null` or missing In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`: @@ -518,7 +765,7 @@ response = client.chat.completions.with_raw_response.create( "role": "user", "content": "Say this is a test", }], - model="gpt-3.5-turbo", + model="gpt-5.5", ) print(response.headers.get('X-My-Header')) @@ -526,7 +773,7 @@ completion = response.parse() # get the object that `chat.completions.create()` print(completion) ``` -These methods return an [`LegacyAPIResponse`](https://github.com/openai/openai-python/tree/main/src/openai/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. +These methods return a [`LegacyAPIResponse`](https://github.com/openai/openai-python/tree/main/src/openai/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. For the sync client this will mostly be the same with the exception of `content` & `text` will be methods instead of properties. In the @@ -551,7 +798,7 @@ with client.chat.completions.with_streaming_response.create( "content": "Say this is a test", } ], - model="gpt-3.5-turbo", + model="gpt-5.5", ) as response: print(response.headers.get("X-My-Header")) @@ -570,8 +817,7 @@ If you need to access undocumented endpoints, params, or response properties, th #### Undocumented endpoints To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other -http verbs. Options on the client will be respected (such as retries) will be respected when making this -request. +http verbs. Options on the client will be respected (such as retries) when making this request. ```py import httpx @@ -600,18 +846,19 @@ can also get all the extra fields on the Pydantic model as a dict with You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including: -- Support for proxies -- Custom transports +- Support for [proxies](https://www.python-httpx.org/advanced/proxies/) +- Custom [transports](https://www.python-httpx.org/advanced/transports/) - Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality ```python +import httpx from openai import OpenAI, DefaultHttpxClient client = OpenAI( # Or use the `OPENAI_BASE_URL` env var base_url="http://my.test.server.example.com:8083/v1", http_client=DefaultHttpxClient( - proxies="http://my.test.proxy.example.com", + proxy="http://my.test.proxy.example.com", transport=httpx.HTTPTransport(local_address="0.0.0.0"), ), ) @@ -627,6 +874,16 @@ client.with_options(http_client=DefaultHttpxClient(...)) By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting. +```py +from openai import OpenAI + +with OpenAI() as client: + # make requests here + ... + +# HTTP client is now closed +``` + ## Microsoft Azure OpenAI To use this library with [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/overview), use the `AzureOpenAI` @@ -669,12 +926,43 @@ In addition to the options provided in the base `OpenAI` client, the following o An example of using the client with Microsoft Entra ID (formerly known as Azure Active Directory) can be found [here](https://github.com/openai/openai-python/blob/main/examples/azure_ad.py). +## Amazon Bedrock + +To use this library with [Amazon Bedrock's OpenAI-compatible API](https://docs.aws.amazon.com/bedrock/latest/userguide/models-api-compatibility.html), use the `BedrockOpenAI` class instead of the `OpenAI` class. + +```py +from openai import BedrockOpenAI + +# gets the bearer token from AWS_BEARER_TOKEN_BEDROCK and the region from AWS_REGION/AWS_DEFAULT_REGION +client = BedrockOpenAI() + +response = client.responses.create( + model="openai.gpt-5.4", + input="Say hello!", +) + +print(response.output_text) +``` + +`BedrockOpenAI` configures AWS bearer auth and the Bedrock Mantle endpoint, then uses the normal SDK resources. AWS controls which endpoints and features are supported; unsupported calls surface the provider's normal HTTP errors through the SDK. + +Pass `base_url` or set `AWS_BEDROCK_BASE_URL` to override the derived `https://bedrock-mantle..api.aws/openai/v1` endpoint. The legacy module client supports `openai.api_type = "amazon-bedrock"` or `OPENAI_API_TYPE=amazon-bedrock`. + +Set `AWS_BEARER_TOKEN_BEDROCK` to an [Amazon Bedrock API key](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html). To refresh tokens yourself, pass a provider instead of `api_key`: + +```py +client = BedrockOpenAI( + aws_region="us-west-2", + bedrock_token_provider=lambda: refresh_bedrock_token(), +) +``` + ## Versioning This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: 1. Changes that only affect static types, without breaking runtime behavior. -2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals)_. +2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ 3. Changes that we do not expect to impact the vast majority of users in practice. We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. @@ -694,4 +982,8 @@ print(openai.__version__) ## Requirements -Python 3.7 or higher. +Python 3.9 or higher. + +## Contributing + +See [the contributing documentation](./CONTRIBUTING.md). diff --git a/SECURITY.md b/SECURITY.md index c54acaf331..4adb0c54f1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,9 +2,9 @@ ## Reporting Security Issues -This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. -To report a security issue, please contact the Stainless team at security@stainlessapi.com. +To report a security issue, please contact the Stainless team at security@stainless.com. ## Responsible Disclosure @@ -16,13 +16,13 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by OpenAI please follow the respective company's security reporting guidelines. +or products provided by OpenAI, please follow the respective company's security reporting guidelines. ### OpenAI Terms and Policies Our Security Policy can be found at [Security Policy URL](https://openai.com/policies/coordinated-vulnerability-disclosure-policy). -Please contact disclosure@openai.com for any questions or concerns regarding security of our services. +Please contact disclosure@openai.com for any questions or concerns regarding the security of our services. --- diff --git a/api.md b/api.md index 32c0fb9efc..10a729d995 100644 --- a/api.md +++ b/api.md @@ -2,12 +2,24 @@ ```python from openai.types import ( + AllModels, + ChatModel, + ComparisonFilter, + CompoundFilter, + CustomToolInputFormat, ErrorObject, FunctionDefinition, FunctionParameters, + Metadata, + OAuthErrorCode, + Reasoning, + ReasoningEffort, ResponseFormatJSONObject, ResponseFormatJSONSchema, ResponseFormatText, + ResponseFormatTextGrammar, + ResponseFormatTextPython, + ResponsesModel, ) ``` @@ -38,39 +50,65 @@ Types: ```python from openai.types.chat import ( ChatCompletion, + ChatCompletionAllowedToolChoice, ChatCompletionAssistantMessageParam, + ChatCompletionAudio, + ChatCompletionAudioParam, ChatCompletionChunk, ChatCompletionContentPart, ChatCompletionContentPartImage, + ChatCompletionContentPartInputAudio, ChatCompletionContentPartRefusal, ChatCompletionContentPartText, + ChatCompletionCustomTool, + ChatCompletionDeleted, + ChatCompletionDeveloperMessageParam, ChatCompletionFunctionCallOption, ChatCompletionFunctionMessageParam, + ChatCompletionFunctionTool, ChatCompletionMessage, + ChatCompletionMessageCustomToolCall, + ChatCompletionMessageFunctionToolCall, ChatCompletionMessageParam, - ChatCompletionMessageToolCall, + ChatCompletionMessageToolCallUnion, + ChatCompletionModality, ChatCompletionNamedToolChoice, + ChatCompletionNamedToolChoiceCustom, + ChatCompletionPredictionContent, ChatCompletionRole, + ChatCompletionStoreMessage, ChatCompletionStreamOptions, ChatCompletionSystemMessageParam, ChatCompletionTokenLogprob, - ChatCompletionTool, + ChatCompletionToolUnion, ChatCompletionToolChoiceOption, ChatCompletionToolMessageParam, ChatCompletionUserMessageParam, + ChatCompletionAllowedTools, + ChatCompletionReasoningEffort, ) ``` Methods: -- client.chat.completions.create(\*\*params) -> ChatCompletion +- client.chat.completions.create(\*\*params) -> ChatCompletion +- client.chat.completions.retrieve(completion_id) -> ChatCompletion +- client.chat.completions.update(completion_id, \*\*params) -> ChatCompletion +- client.chat.completions.list(\*\*params) -> SyncCursorPage[ChatCompletion] +- client.chat.completions.delete(completion_id) -> ChatCompletionDeleted + +### Messages + +Methods: + +- client.chat.completions.messages.list(completion_id, \*\*params) -> SyncCursorPage[ChatCompletionStoreMessage] # Embeddings Types: ```python -from openai.types import CreateEmbeddingResponse, Embedding +from openai.types import CreateEmbeddingResponse, Embedding, EmbeddingModel ``` Methods: @@ -89,10 +127,10 @@ Methods: - client.files.create(\*\*params) -> FileObject - client.files.retrieve(file_id) -> FileObject -- client.files.list(\*\*params) -> SyncPage[FileObject] +- client.files.list(\*\*params) -> SyncCursorPage[FileObject] - client.files.delete(file_id) -> FileDeleted - client.files.content(file_id) -> HttpxBinaryResponseContent -- client.files.retrieve_content(file_id) -> str +- client.files.retrieve_content(file_id) -> str - client.files.wait_for_processing(\*args) -> FileObject # Images @@ -100,7 +138,17 @@ Methods: Types: ```python -from openai.types import Image, ImageModel, ImagesResponse +from openai.types import ( + Image, + ImageEditCompletedEvent, + ImageEditPartialImageEvent, + ImageEditStreamEvent, + ImageGenCompletedEvent, + ImageGenPartialImageEvent, + ImageGenStreamEvent, + ImageModel, + ImagesResponse, +) ``` Methods: @@ -114,7 +162,7 @@ Methods: Types: ```python -from openai.types import AudioModel +from openai.types import AudioModel, AudioResponseFormat ``` ## Transcriptions @@ -122,24 +170,37 @@ from openai.types import AudioModel Types: ```python -from openai.types.audio import Transcription +from openai.types.audio import ( + Transcription, + TranscriptionDiarized, + TranscriptionDiarizedSegment, + TranscriptionInclude, + TranscriptionSegment, + TranscriptionStreamEvent, + TranscriptionTextDeltaEvent, + TranscriptionTextDoneEvent, + TranscriptionTextSegmentEvent, + TranscriptionVerbose, + TranscriptionWord, + TranscriptionCreateResponse, +) ``` Methods: -- client.audio.transcriptions.create(\*\*params) -> Transcription +- client.audio.transcriptions.create(\*\*params) -> TranscriptionCreateResponse ## Translations Types: ```python -from openai.types.audio import Translation +from openai.types.audio import Translation, TranslationVerbose, TranslationCreateResponse ``` Methods: -- client.audio.translations.create(\*\*params) -> Translation +- client.audio.translations.create(\*\*params) -> TranslationCreateResponse ## Speech @@ -158,7 +219,14 @@ Methods: Types: ```python -from openai.types import Moderation, ModerationModel, ModerationCreateResponse +from openai.types import ( + Moderation, + ModerationImageURLInput, + ModerationModel, + ModerationMultiModalInput, + ModerationTextInput, + ModerationCreateResponse, +) ``` Methods: @@ -181,6 +249,21 @@ Methods: # FineTuning +## Methods + +Types: + +```python +from openai.types.fine_tuning import ( + DpoHyperparameters, + DpoMethod, + ReinforcementHyperparameters, + ReinforcementMethod, + SupervisedHyperparameters, + SupervisedMethod, +) +``` + ## Jobs Types: @@ -189,9 +272,9 @@ Types: from openai.types.fine_tuning import ( FineTuningJob, FineTuningJobEvent, - FineTuningJobIntegration, FineTuningJobWandbIntegration, FineTuningJobWandbIntegrationObject, + FineTuningJobIntegration, ) ``` @@ -202,6 +285,8 @@ Methods: - client.fine_tuning.jobs.list(\*\*params) -> SyncCursorPage[FineTuningJob] - client.fine_tuning.jobs.cancel(fine_tuning_job_id) -> FineTuningJob - client.fine_tuning.jobs.list_events(fine_tuning_job_id, \*\*params) -> SyncCursorPage[FineTuningJobEvent] +- client.fine_tuning.jobs.pause(fine_tuning_job_id) -> FineTuningJob +- client.fine_tuning.jobs.resume(fine_tuning_job_id) -> FineTuningJob ### Checkpoints @@ -215,70 +300,218 @@ Methods: - client.fine_tuning.jobs.checkpoints.list(fine_tuning_job_id, \*\*params) -> SyncCursorPage[FineTuningJobCheckpoint] -# Beta +## Checkpoints -## VectorStores +### Permissions Types: ```python -from openai.types.beta import ( +from openai.types.fine_tuning.checkpoints import ( + PermissionCreateResponse, + PermissionRetrieveResponse, + PermissionListResponse, + PermissionDeleteResponse, +) +``` + +Methods: + +- client.fine_tuning.checkpoints.permissions.create(fine_tuned_model_checkpoint, \*\*params) -> SyncPage[PermissionCreateResponse] +- client.fine_tuning.checkpoints.permissions.retrieve(fine_tuned_model_checkpoint, \*\*params) -> PermissionRetrieveResponse +- client.fine_tuning.checkpoints.permissions.list(fine_tuned_model_checkpoint, \*\*params) -> SyncConversationCursorPage[PermissionListResponse] +- client.fine_tuning.checkpoints.permissions.delete(permission_id, \*, fine_tuned_model_checkpoint) -> PermissionDeleteResponse + +## Alpha + +### Graders + +Types: + +```python +from openai.types.fine_tuning.alpha import GraderRunResponse, GraderValidateResponse +``` + +Methods: + +- client.fine_tuning.alpha.graders.run(\*\*params) -> GraderRunResponse +- client.fine_tuning.alpha.graders.validate(\*\*params) -> GraderValidateResponse + +# Graders + +## GraderModels + +Types: + +```python +from openai.types.graders import ( + GraderInputs, + LabelModelGrader, + MultiGrader, + PythonGrader, + ScoreModelGrader, + StringCheckGrader, + TextSimilarityGrader, +) +``` + +# VectorStores + +Types: + +```python +from openai.types import ( AutoFileChunkingStrategyParam, FileChunkingStrategy, FileChunkingStrategyParam, OtherFileChunkingStrategyObject, StaticFileChunkingStrategy, StaticFileChunkingStrategyObject, - StaticFileChunkingStrategyParam, + StaticFileChunkingStrategyObjectParam, VectorStore, VectorStoreDeleted, + VectorStoreSearchResponse, ) ``` Methods: -- client.beta.vector_stores.create(\*\*params) -> VectorStore -- client.beta.vector_stores.retrieve(vector_store_id) -> VectorStore -- client.beta.vector_stores.update(vector_store_id, \*\*params) -> VectorStore -- client.beta.vector_stores.list(\*\*params) -> SyncCursorPage[VectorStore] -- client.beta.vector_stores.delete(vector_store_id) -> VectorStoreDeleted +- client.vector_stores.create(\*\*params) -> VectorStore +- client.vector_stores.retrieve(vector_store_id) -> VectorStore +- client.vector_stores.update(vector_store_id, \*\*params) -> VectorStore +- client.vector_stores.list(\*\*params) -> SyncCursorPage[VectorStore] +- client.vector_stores.delete(vector_store_id) -> VectorStoreDeleted +- client.vector_stores.search(vector_store_id, \*\*params) -> SyncPage[VectorStoreSearchResponse] + +## Files + +Types: + +```python +from openai.types.vector_stores import VectorStoreFile, VectorStoreFileDeleted, FileContentResponse +``` + +Methods: + +- client.vector_stores.files.create(vector_store_id, \*\*params) -> VectorStoreFile +- client.vector_stores.files.retrieve(file_id, \*, vector_store_id) -> VectorStoreFile +- client.vector_stores.files.update(file_id, \*, vector_store_id, \*\*params) -> VectorStoreFile +- client.vector_stores.files.list(vector_store_id, \*\*params) -> SyncCursorPage[VectorStoreFile] +- client.vector_stores.files.delete(file_id, \*, vector_store_id) -> VectorStoreFileDeleted +- client.vector_stores.files.content(file_id, \*, vector_store_id) -> SyncPage[FileContentResponse] +- client.vector_stores.files.create_and_poll(\*args) -> VectorStoreFile +- client.vector_stores.files.poll(\*args) -> VectorStoreFile +- client.vector_stores.files.upload(\*args) -> VectorStoreFile +- client.vector_stores.files.upload_and_poll(\*args) -> VectorStoreFile + +## FileBatches + +Types: + +```python +from openai.types.vector_stores import VectorStoreFileBatch +``` + +Methods: + +- client.vector_stores.file_batches.create(vector_store_id, \*\*params) -> VectorStoreFileBatch +- client.vector_stores.file_batches.retrieve(batch_id, \*, vector_store_id) -> VectorStoreFileBatch +- client.vector_stores.file_batches.cancel(batch_id, \*, vector_store_id) -> VectorStoreFileBatch +- client.vector_stores.file_batches.list_files(batch_id, \*, vector_store_id, \*\*params) -> SyncCursorPage[VectorStoreFile] +- client.vector_stores.file_batches.create_and_poll(\*args) -> VectorStoreFileBatch +- client.vector_stores.file_batches.poll(\*args) -> VectorStoreFileBatch +- client.vector_stores.file_batches.upload_and_poll(\*args) -> VectorStoreFileBatch + +# [Webhooks](src/openai/resources/webhooks/api.md) + +Methods: + +- client.webhooks.unwrap(payload, headers, \*, secret) -> UnwrapWebhookEvent +- client.webhooks.verify_signature(payload, headers, \*, secret, tolerance) -> None + +# Beta + +## Realtime + +Types: + +```python +from openai.types.beta.realtime import ( + ConversationCreatedEvent, + ConversationItem, + ConversationItemContent, + ConversationItemCreateEvent, + ConversationItemCreatedEvent, + ConversationItemDeleteEvent, + ConversationItemDeletedEvent, + ConversationItemInputAudioTranscriptionCompletedEvent, + ConversationItemInputAudioTranscriptionDeltaEvent, + ConversationItemInputAudioTranscriptionFailedEvent, + ConversationItemRetrieveEvent, + ConversationItemTruncateEvent, + ConversationItemTruncatedEvent, + ConversationItemWithReference, + ErrorEvent, + InputAudioBufferAppendEvent, + InputAudioBufferClearEvent, + InputAudioBufferClearedEvent, + InputAudioBufferCommitEvent, + InputAudioBufferCommittedEvent, + InputAudioBufferSpeechStartedEvent, + InputAudioBufferSpeechStoppedEvent, + RateLimitsUpdatedEvent, + RealtimeClientEvent, + RealtimeResponse, + RealtimeResponseStatus, + RealtimeResponseUsage, + RealtimeServerEvent, + ResponseAudioDeltaEvent, + ResponseAudioDoneEvent, + ResponseAudioTranscriptDeltaEvent, + ResponseAudioTranscriptDoneEvent, + ResponseCancelEvent, + ResponseContentPartAddedEvent, + ResponseContentPartDoneEvent, + ResponseCreateEvent, + ResponseCreatedEvent, + ResponseDoneEvent, + ResponseFunctionCallArgumentsDeltaEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseOutputItemAddedEvent, + ResponseOutputItemDoneEvent, + ResponseTextDeltaEvent, + ResponseTextDoneEvent, + SessionCreatedEvent, + SessionUpdateEvent, + SessionUpdatedEvent, + TranscriptionSessionUpdate, + TranscriptionSessionUpdatedEvent, +) +``` -### Files +### Sessions Types: ```python -from openai.types.beta.vector_stores import VectorStoreFile, VectorStoreFileDeleted +from openai.types.beta.realtime import Session, SessionCreateResponse ``` Methods: -- client.beta.vector_stores.files.create(vector_store_id, \*\*params) -> VectorStoreFile -- client.beta.vector_stores.files.retrieve(file_id, \*, vector_store_id) -> VectorStoreFile -- client.beta.vector_stores.files.list(vector_store_id, \*\*params) -> SyncCursorPage[VectorStoreFile] -- client.beta.vector_stores.files.delete(file_id, \*, vector_store_id) -> VectorStoreFileDeleted -- client.beta.vector_stores.files.create_and_poll(\*args) -> VectorStoreFile -- client.beta.vector_stores.files.poll(\*args) -> VectorStoreFile -- client.beta.vector_stores.files.upload(\*args) -> VectorStoreFile -- client.beta.vector_stores.files.upload_and_poll(\*args) -> VectorStoreFile +- client.beta.realtime.sessions.create(\*\*params) -> SessionCreateResponse -### FileBatches +### TranscriptionSessions Types: ```python -from openai.types.beta.vector_stores import VectorStoreFileBatch +from openai.types.beta.realtime import TranscriptionSession ``` Methods: -- client.beta.vector_stores.file_batches.create(vector_store_id, \*\*params) -> VectorStoreFileBatch -- client.beta.vector_stores.file_batches.retrieve(batch_id, \*, vector_store_id) -> VectorStoreFileBatch -- client.beta.vector_stores.file_batches.cancel(batch_id, \*, vector_store_id) -> VectorStoreFileBatch -- client.beta.vector_stores.file_batches.list_files(batch_id, \*, vector_store_id, \*\*params) -> SyncCursorPage[VectorStoreFile] -- client.beta.vector_stores.file_batches.create_and_poll(\*args) -> VectorStoreFileBatch -- client.beta.vector_stores.file_batches.poll(\*args) -> VectorStoreFileBatch -- client.beta.vector_stores.file_batches.upload_and_poll(\*args) -> VectorStoreFileBatch +- client.beta.realtime.transcription_sessions.create(\*\*params) -> TranscriptionSession ## Assistants @@ -438,7 +671,7 @@ Methods: Types: ```python -from openai.types import Batch, BatchError, BatchRequestCounts +from openai.types import Batch, BatchError, BatchRequestCounts, BatchUsage ``` Methods: @@ -473,3 +706,652 @@ from openai.types.uploads import UploadPart Methods: - client.uploads.parts.create(upload_id, \*\*params) -> UploadPart + +# Admin + +## Organization + +### AuditLogs + +Types: + +```python +from openai.types.admin.organization import AuditLogListResponse +``` + +Methods: + +- client.admin.organization.audit_logs.list(\*\*params) -> SyncConversationCursorPage[AuditLogListResponse] + +### AdminAPIKeys + +Types: + +```python +from openai.types.admin.organization import ( + AdminAPIKey, + AdminAPIKeyCreateResponse, + AdminAPIKeyDeleteResponse, +) +``` + +Methods: + +- client.admin.organization.admin_api_keys.create(\*\*params) -> AdminAPIKeyCreateResponse +- client.admin.organization.admin_api_keys.retrieve(key_id) -> AdminAPIKey +- client.admin.organization.admin_api_keys.list(\*\*params) -> SyncCursorPage[AdminAPIKey] +- client.admin.organization.admin_api_keys.delete(key_id) -> AdminAPIKeyDeleteResponse + +### Usage + +Types: + +```python +from openai.types.admin.organization import ( + UsageAudioSpeechesResponse, + UsageAudioTranscriptionsResponse, + UsageCodeInterpreterSessionsResponse, + UsageCompletionsResponse, + UsageCostsResponse, + UsageEmbeddingsResponse, + UsageFileSearchCallsResponse, + UsageImagesResponse, + UsageModerationsResponse, + UsageVectorStoresResponse, + UsageWebSearchCallsResponse, +) +``` + +Methods: + +- client.admin.organization.usage.audio_speeches(\*\*params) -> UsageAudioSpeechesResponse +- client.admin.organization.usage.audio_transcriptions(\*\*params) -> UsageAudioTranscriptionsResponse +- client.admin.organization.usage.code_interpreter_sessions(\*\*params) -> UsageCodeInterpreterSessionsResponse +- client.admin.organization.usage.completions(\*\*params) -> UsageCompletionsResponse +- client.admin.organization.usage.costs(\*\*params) -> UsageCostsResponse +- client.admin.organization.usage.embeddings(\*\*params) -> UsageEmbeddingsResponse +- client.admin.organization.usage.file_search_calls(\*\*params) -> UsageFileSearchCallsResponse +- client.admin.organization.usage.images(\*\*params) -> UsageImagesResponse +- client.admin.organization.usage.moderations(\*\*params) -> UsageModerationsResponse +- client.admin.organization.usage.vector_stores(\*\*params) -> UsageVectorStoresResponse +- client.admin.organization.usage.web_search_calls(\*\*params) -> UsageWebSearchCallsResponse + +### Invites + +Types: + +```python +from openai.types.admin.organization import Invite, InviteDeleteResponse +``` + +Methods: + +- client.admin.organization.invites.create(\*\*params) -> Invite +- client.admin.organization.invites.retrieve(invite_id) -> Invite +- client.admin.organization.invites.list(\*\*params) -> SyncConversationCursorPage[Invite] +- client.admin.organization.invites.delete(invite_id) -> InviteDeleteResponse + +### Users + +Types: + +```python +from openai.types.admin.organization import OrganizationUser, UserDeleteResponse +``` + +Methods: + +- client.admin.organization.users.retrieve(user_id) -> OrganizationUser +- client.admin.organization.users.update(user_id, \*\*params) -> OrganizationUser +- client.admin.organization.users.list(\*\*params) -> SyncConversationCursorPage[OrganizationUser] +- client.admin.organization.users.delete(user_id) -> UserDeleteResponse + +#### Roles + +Types: + +```python +from openai.types.admin.organization.users import ( + RoleCreateResponse, + RoleRetrieveResponse, + RoleListResponse, + RoleDeleteResponse, +) +``` + +Methods: + +- client.admin.organization.users.roles.create(user_id, \*\*params) -> RoleCreateResponse +- client.admin.organization.users.roles.retrieve(role_id, \*, user_id) -> RoleRetrieveResponse +- client.admin.organization.users.roles.list(user_id, \*\*params) -> SyncNextCursorPage[RoleListResponse] +- client.admin.organization.users.roles.delete(role_id, \*, user_id) -> RoleDeleteResponse + +### Groups + +Types: + +```python +from openai.types.admin.organization import Group, GroupUpdateResponse, GroupDeleteResponse +``` + +Methods: + +- client.admin.organization.groups.create(\*\*params) -> Group +- client.admin.organization.groups.retrieve(group_id) -> Group +- client.admin.organization.groups.update(group_id, \*\*params) -> GroupUpdateResponse +- client.admin.organization.groups.list(\*\*params) -> SyncNextCursorPage[Group] +- client.admin.organization.groups.delete(group_id) -> GroupDeleteResponse + +#### Users + +Types: + +```python +from openai.types.admin.organization.groups import ( + OrganizationGroupUser, + UserCreateResponse, + UserRetrieveResponse, + UserDeleteResponse, +) +``` + +Methods: + +- client.admin.organization.groups.users.create(group_id, \*\*params) -> UserCreateResponse +- client.admin.organization.groups.users.retrieve(user_id, \*, group_id) -> UserRetrieveResponse +- client.admin.organization.groups.users.list(group_id, \*\*params) -> SyncNextCursorPage[OrganizationGroupUser] +- client.admin.organization.groups.users.delete(user_id, \*, group_id) -> UserDeleteResponse + +#### Roles + +Types: + +```python +from openai.types.admin.organization.groups import ( + RoleCreateResponse, + RoleRetrieveResponse, + RoleListResponse, + RoleDeleteResponse, +) +``` + +Methods: + +- client.admin.organization.groups.roles.create(group_id, \*\*params) -> RoleCreateResponse +- client.admin.organization.groups.roles.retrieve(role_id, \*, group_id) -> RoleRetrieveResponse +- client.admin.organization.groups.roles.list(group_id, \*\*params) -> SyncNextCursorPage[RoleListResponse] +- client.admin.organization.groups.roles.delete(role_id, \*, group_id) -> RoleDeleteResponse + +### Roles + +Types: + +```python +from openai.types.admin.organization import Role, RoleDeleteResponse +``` + +Methods: + +- client.admin.organization.roles.create(\*\*params) -> Role +- client.admin.organization.roles.retrieve(role_id) -> Role +- client.admin.organization.roles.update(role_id, \*\*params) -> Role +- client.admin.organization.roles.list(\*\*params) -> SyncNextCursorPage[Role] +- client.admin.organization.roles.delete(role_id) -> RoleDeleteResponse + +### DataRetention + +Types: + +```python +from openai.types.admin.organization import OrganizationDataRetention +``` + +Methods: + +- client.admin.organization.data_retention.retrieve() -> OrganizationDataRetention +- client.admin.organization.data_retention.update(\*\*params) -> OrganizationDataRetention + +### SpendAlerts + +Types: + +```python +from openai.types.admin.organization import OrganizationSpendAlert, OrganizationSpendAlertDeleted +``` + +Methods: + +- client.admin.organization.spend_alerts.create(\*\*params) -> OrganizationSpendAlert +- client.admin.organization.spend_alerts.update(alert_id, \*\*params) -> OrganizationSpendAlert +- client.admin.organization.spend_alerts.list(\*\*params) -> SyncConversationCursorPage[OrganizationSpendAlert] +- client.admin.organization.spend_alerts.delete(alert_id) -> OrganizationSpendAlertDeleted + +### Certificates + +Types: + +```python +from openai.types.admin.organization import ( + Certificate, + CertificateListResponse, + CertificateDeleteResponse, + CertificateActivateResponse, + CertificateDeactivateResponse, +) +``` + +Methods: + +- client.admin.organization.certificates.create(\*\*params) -> Certificate +- client.admin.organization.certificates.retrieve(certificate_id, \*\*params) -> Certificate +- client.admin.organization.certificates.update(certificate_id, \*\*params) -> Certificate +- client.admin.organization.certificates.list(\*\*params) -> SyncConversationCursorPage[CertificateListResponse] +- client.admin.organization.certificates.delete(certificate_id) -> CertificateDeleteResponse +- client.admin.organization.certificates.activate(\*\*params) -> SyncPage[CertificateActivateResponse] +- client.admin.organization.certificates.deactivate(\*\*params) -> SyncPage[CertificateDeactivateResponse] + +### Projects + +Types: + +```python +from openai.types.admin.organization import Project +``` + +Methods: + +- client.admin.organization.projects.create(\*\*params) -> Project +- client.admin.organization.projects.retrieve(project_id) -> Project +- client.admin.organization.projects.update(project_id, \*\*params) -> Project +- client.admin.organization.projects.list(\*\*params) -> SyncConversationCursorPage[Project] +- client.admin.organization.projects.archive(project_id) -> Project + +#### Users + +Types: + +```python +from openai.types.admin.organization.projects import ProjectUser, UserDeleteResponse +``` + +Methods: + +- client.admin.organization.projects.users.create(project_id, \*\*params) -> ProjectUser +- client.admin.organization.projects.users.retrieve(user_id, \*, project_id) -> ProjectUser +- client.admin.organization.projects.users.update(user_id, \*, project_id, \*\*params) -> ProjectUser +- client.admin.organization.projects.users.list(project_id, \*\*params) -> SyncConversationCursorPage[ProjectUser] +- client.admin.organization.projects.users.delete(user_id, \*, project_id) -> UserDeleteResponse + +##### Roles + +Types: + +```python +from openai.types.admin.organization.projects.users import ( + RoleCreateResponse, + RoleRetrieveResponse, + RoleListResponse, + RoleDeleteResponse, +) +``` + +Methods: + +- client.admin.organization.projects.users.roles.create(user_id, \*, project_id, \*\*params) -> RoleCreateResponse +- client.admin.organization.projects.users.roles.retrieve(role_id, \*, project_id, user_id) -> RoleRetrieveResponse +- client.admin.organization.projects.users.roles.list(user_id, \*, project_id, \*\*params) -> SyncNextCursorPage[RoleListResponse] +- client.admin.organization.projects.users.roles.delete(role_id, \*, project_id, user_id) -> RoleDeleteResponse + +#### ServiceAccounts + +Types: + +```python +from openai.types.admin.organization.projects import ( + ProjectServiceAccount, + ServiceAccountCreateResponse, + ServiceAccountDeleteResponse, +) +``` + +Methods: + +- client.admin.organization.projects.service_accounts.create(project_id, \*\*params) -> ServiceAccountCreateResponse +- client.admin.organization.projects.service_accounts.retrieve(service_account_id, \*, project_id) -> ProjectServiceAccount +- client.admin.organization.projects.service_accounts.update(service_account_id, \*, project_id, \*\*params) -> ProjectServiceAccount +- client.admin.organization.projects.service_accounts.list(project_id, \*\*params) -> SyncConversationCursorPage[ProjectServiceAccount] +- client.admin.organization.projects.service_accounts.delete(service_account_id, \*, project_id) -> ServiceAccountDeleteResponse + +#### APIKeys + +Types: + +```python +from openai.types.admin.organization.projects import ProjectAPIKey, APIKeyDeleteResponse +``` + +Methods: + +- client.admin.organization.projects.api_keys.retrieve(api_key_id, \*, project_id) -> ProjectAPIKey +- client.admin.organization.projects.api_keys.list(project_id, \*\*params) -> SyncConversationCursorPage[ProjectAPIKey] +- client.admin.organization.projects.api_keys.delete(api_key_id, \*, project_id) -> APIKeyDeleteResponse + +#### RateLimits + +Types: + +```python +from openai.types.admin.organization.projects import ProjectRateLimit +``` + +Methods: + +- client.admin.organization.projects.rate_limits.list_rate_limits(project_id, \*\*params) -> SyncConversationCursorPage[ProjectRateLimit] +- client.admin.organization.projects.rate_limits.update_rate_limit(rate_limit_id, \*, project_id, \*\*params) -> ProjectRateLimit + +#### ModelPermissions + +Types: + +```python +from openai.types.admin.organization.projects import ( + ProjectModelPermissions, + ProjectModelPermissionsDeleted, +) +``` + +Methods: + +- client.admin.organization.projects.model_permissions.retrieve(project_id) -> ProjectModelPermissions +- client.admin.organization.projects.model_permissions.update(project_id, \*\*params) -> ProjectModelPermissions +- client.admin.organization.projects.model_permissions.delete(project_id) -> ProjectModelPermissionsDeleted + +#### HostedToolPermissions + +Types: + +```python +from openai.types.admin.organization.projects import ProjectHostedToolPermissions +``` + +Methods: + +- client.admin.organization.projects.hosted_tool_permissions.retrieve(project_id) -> ProjectHostedToolPermissions +- client.admin.organization.projects.hosted_tool_permissions.update(project_id, \*\*params) -> ProjectHostedToolPermissions + +#### Groups + +Types: + +```python +from openai.types.admin.organization.projects import ProjectGroup, GroupDeleteResponse +``` + +Methods: + +- client.admin.organization.projects.groups.create(project_id, \*\*params) -> ProjectGroup +- client.admin.organization.projects.groups.retrieve(group_id, \*, project_id, \*\*params) -> ProjectGroup +- client.admin.organization.projects.groups.list(project_id, \*\*params) -> SyncNextCursorPage[ProjectGroup] +- client.admin.organization.projects.groups.delete(group_id, \*, project_id) -> GroupDeleteResponse + +##### Roles + +Types: + +```python +from openai.types.admin.organization.projects.groups import ( + RoleCreateResponse, + RoleRetrieveResponse, + RoleListResponse, + RoleDeleteResponse, +) +``` + +Methods: + +- client.admin.organization.projects.groups.roles.create(group_id, \*, project_id, \*\*params) -> RoleCreateResponse +- client.admin.organization.projects.groups.roles.retrieve(role_id, \*, project_id, group_id) -> RoleRetrieveResponse +- client.admin.organization.projects.groups.roles.list(group_id, \*, project_id, \*\*params) -> SyncNextCursorPage[RoleListResponse] +- client.admin.organization.projects.groups.roles.delete(role_id, \*, project_id, group_id) -> RoleDeleteResponse + +#### Roles + +Types: + +```python +from openai.types.admin.organization.projects import RoleDeleteResponse +``` + +Methods: + +- client.admin.organization.projects.roles.create(project_id, \*\*params) -> Role +- client.admin.organization.projects.roles.retrieve(role_id, \*, project_id) -> Role +- client.admin.organization.projects.roles.update(role_id, \*, project_id, \*\*params) -> Role +- client.admin.organization.projects.roles.list(project_id, \*\*params) -> SyncNextCursorPage[Role] +- client.admin.organization.projects.roles.delete(role_id, \*, project_id) -> RoleDeleteResponse + +#### DataRetention + +Types: + +```python +from openai.types.admin.organization.projects import ProjectDataRetention +``` + +Methods: + +- client.admin.organization.projects.data_retention.retrieve(project_id) -> ProjectDataRetention +- client.admin.organization.projects.data_retention.update(project_id, \*\*params) -> ProjectDataRetention + +#### SpendAlerts + +Types: + +```python +from openai.types.admin.organization.projects import ProjectSpendAlert, ProjectSpendAlertDeleted +``` + +Methods: + +- client.admin.organization.projects.spend_alerts.create(project_id, \*\*params) -> ProjectSpendAlert +- client.admin.organization.projects.spend_alerts.update(alert_id, \*, project_id, \*\*params) -> ProjectSpendAlert +- client.admin.organization.projects.spend_alerts.list(project_id, \*\*params) -> SyncConversationCursorPage[ProjectSpendAlert] +- client.admin.organization.projects.spend_alerts.delete(alert_id, \*, project_id) -> ProjectSpendAlertDeleted + +#### Certificates + +Types: + +```python +from openai.types.admin.organization.projects import ( + CertificateListResponse, + CertificateActivateResponse, + CertificateDeactivateResponse, +) +``` + +Methods: + +- client.admin.organization.projects.certificates.list(project_id, \*\*params) -> SyncConversationCursorPage[CertificateListResponse] +- client.admin.organization.projects.certificates.activate(project_id, \*\*params) -> SyncPage[CertificateActivateResponse] +- client.admin.organization.projects.certificates.deactivate(project_id, \*\*params) -> SyncPage[CertificateDeactivateResponse] + +# [Responses](src/openai/resources/responses/api.md) + +# [Realtime](src/openai/resources/realtime/api.md) + +# [Conversations](src/openai/resources/conversations/api.md) + +# Evals + +Types: + +```python +from openai.types import ( + EvalCustomDataSourceConfig, + EvalStoredCompletionsDataSourceConfig, + EvalCreateResponse, + EvalRetrieveResponse, + EvalUpdateResponse, + EvalListResponse, + EvalDeleteResponse, +) +``` + +Methods: + +- client.evals.create(\*\*params) -> EvalCreateResponse +- client.evals.retrieve(eval_id) -> EvalRetrieveResponse +- client.evals.update(eval_id, \*\*params) -> EvalUpdateResponse +- client.evals.list(\*\*params) -> SyncCursorPage[EvalListResponse] +- client.evals.delete(eval_id) -> EvalDeleteResponse + +## Runs + +Types: + +```python +from openai.types.evals import ( + CreateEvalCompletionsRunDataSource, + CreateEvalJSONLRunDataSource, + EvalAPIError, + RunCreateResponse, + RunRetrieveResponse, + RunListResponse, + RunDeleteResponse, + RunCancelResponse, +) +``` + +Methods: + +- client.evals.runs.create(eval_id, \*\*params) -> RunCreateResponse +- client.evals.runs.retrieve(run_id, \*, eval_id) -> RunRetrieveResponse +- client.evals.runs.list(eval_id, \*\*params) -> SyncCursorPage[RunListResponse] +- client.evals.runs.delete(run_id, \*, eval_id) -> RunDeleteResponse +- client.evals.runs.cancel(run_id, \*, eval_id) -> RunCancelResponse + +### OutputItems + +Types: + +```python +from openai.types.evals.runs import OutputItemRetrieveResponse, OutputItemListResponse +``` + +Methods: + +- client.evals.runs.output_items.retrieve(output_item_id, \*, eval_id, run_id) -> OutputItemRetrieveResponse +- client.evals.runs.output_items.list(run_id, \*, eval_id, \*\*params) -> SyncCursorPage[OutputItemListResponse] + +# Containers + +Types: + +```python +from openai.types import ContainerCreateResponse, ContainerRetrieveResponse, ContainerListResponse +``` + +Methods: + +- client.containers.create(\*\*params) -> ContainerCreateResponse +- client.containers.retrieve(container_id) -> ContainerRetrieveResponse +- client.containers.list(\*\*params) -> SyncCursorPage[ContainerListResponse] +- client.containers.delete(container_id) -> None + +## Files + +Types: + +```python +from openai.types.containers import FileCreateResponse, FileRetrieveResponse, FileListResponse +``` + +Methods: + +- client.containers.files.create(container_id, \*\*params) -> FileCreateResponse +- client.containers.files.retrieve(file_id, \*, container_id) -> FileRetrieveResponse +- client.containers.files.list(container_id, \*\*params) -> SyncCursorPage[FileListResponse] +- client.containers.files.delete(file_id, \*, container_id) -> None + +### Content + +Methods: + +- client.containers.files.content.retrieve(file_id, \*, container_id) -> HttpxBinaryResponseContent + +# Skills + +Types: + +```python +from openai.types import DeletedSkill, Skill, SkillList +``` + +Methods: + +- client.skills.create(\*\*params) -> Skill +- client.skills.retrieve(skill_id) -> Skill +- client.skills.update(skill_id, \*\*params) -> Skill +- client.skills.list(\*\*params) -> SyncCursorPage[Skill] +- client.skills.delete(skill_id) -> DeletedSkill + +## Content + +Methods: + +- client.skills.content.retrieve(skill_id) -> HttpxBinaryResponseContent + +## Versions + +Types: + +```python +from openai.types.skills import DeletedSkillVersion, SkillVersion, SkillVersionList +``` + +Methods: + +- client.skills.versions.create(skill_id, \*\*params) -> SkillVersion +- client.skills.versions.retrieve(version, \*, skill_id) -> SkillVersion +- client.skills.versions.list(skill_id, \*\*params) -> SyncCursorPage[SkillVersion] +- client.skills.versions.delete(version, \*, skill_id) -> DeletedSkillVersion + +### Content + +Methods: + +- client.skills.versions.content.retrieve(version, \*, skill_id) -> HttpxBinaryResponseContent + +# Videos + +Types: + +```python +from openai.types import ( + ImageInputReferenceParam, + Video, + VideoCreateError, + VideoModel, + VideoSeconds, + VideoSize, + VideoDeleteResponse, + VideoCreateCharacterResponse, + VideoGetCharacterResponse, +) +``` + +Methods: + +- client.videos.create(\*\*params) -> Video +- client.videos.retrieve(video_id) -> Video +- client.videos.list(\*\*params) -> SyncConversationCursorPage[Video] +- client.videos.delete(video_id) -> VideoDeleteResponse +- client.videos.create_character(\*\*params) -> VideoCreateCharacterResponse +- client.videos.download_content(video_id, \*\*params) -> HttpxBinaryResponseContent +- client.videos.edit(\*\*params) -> Video +- client.videos.extend(\*\*params) -> Video +- client.videos.get_character(character_id) -> VideoGetCharacterResponse +- client.videos.remix(video_id, \*\*params) -> Video +- client.videos.create_and_poll(\*args) -> Video +- client.videos.poll(\*args) -> Video diff --git a/bin/check-release-environment b/bin/check-release-environment deleted file mode 100644 index 2cc5ad6352..0000000000 --- a/bin/check-release-environment +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -errors=() - -if [ -z "${STAINLESS_API_KEY}" ]; then - errors+=("The STAINLESS_API_KEY secret has not been set. Please contact Stainless for an API key & set it in your organization secrets on GitHub.") -fi - -if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The OPENAI_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") -fi - -lenErrors=${#errors[@]} - -if [[ lenErrors -gt 0 ]]; then - echo -e "Found the following errors in the release environment:\n" - - for error in "${errors[@]}"; do - echo -e "- $error\n" - done - - exit 1 -fi - -echo "The environment is ready to push releases!" diff --git a/bin/publish-pypi b/bin/publish-pypi deleted file mode 100644 index 05bfccbb71..0000000000 --- a/bin/publish-pypi +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -set -eux -mkdir -p dist -rye build --clean -# Patching importlib-metadata version until upstream library version is updated -# https://github.com/pypa/twine/issues/977#issuecomment-2189800841 -"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1' -rye publish --yes --token=$PYPI_TOKEN diff --git a/examples/assistant.py b/examples/assistant.py deleted file mode 100644 index f6924a0c7d..0000000000 --- a/examples/assistant.py +++ /dev/null @@ -1,37 +0,0 @@ -import openai - -# gets API Key from environment variable OPENAI_API_KEY -client = openai.OpenAI() - -assistant = client.beta.assistants.create( - name="Math Tutor", - instructions="You are a personal math tutor. Write and run code to answer math questions.", - tools=[{"type": "code_interpreter"}], - model="gpt-4-1106-preview", -) - -thread = client.beta.threads.create() - -message = client.beta.threads.messages.create( - thread_id=thread.id, - role="user", - content="I need to solve the equation `3x + 11 = 14`. Can you help me?", -) - -run = client.beta.threads.runs.create_and_poll( - thread_id=thread.id, - assistant_id=assistant.id, - instructions="Please address the user as Jane Doe. The user has a premium account.", -) - -print("Run completed with status: " + run.status) - -if run.status == "completed": - messages = client.beta.threads.messages.list(thread_id=thread.id) - - print("messages: ") - for message in messages: - assert message.content[0].type == "text" - print({"role": message.role, "message": message.content[0].text.value}) - - client.beta.assistants.delete(assistant.id) diff --git a/examples/assistant_stream.py b/examples/assistant_stream.py deleted file mode 100644 index 0465d3930f..0000000000 --- a/examples/assistant_stream.py +++ /dev/null @@ -1,33 +0,0 @@ -import openai - -# gets API Key from environment variable OPENAI_API_KEY -client = openai.OpenAI() - -assistant = client.beta.assistants.create( - name="Math Tutor", - instructions="You are a personal math tutor. Write and run code to answer math questions.", - tools=[{"type": "code_interpreter"}], - model="gpt-4-1106-preview", -) - -thread = client.beta.threads.create() - -message = client.beta.threads.messages.create( - thread_id=thread.id, - role="user", - content="I need to solve the equation `3x + 11 = 14`. Can you help me?", -) - -print("starting run stream") - -stream = client.beta.threads.runs.create( - thread_id=thread.id, - assistant_id=assistant.id, - instructions="Please address the user as Jane Doe. The user has a premium account.", - stream=True, -) - -for event in stream: - print(event.model_dump_json(indent=2, exclude_unset=True)) - -client.beta.assistants.delete(assistant.id) diff --git a/examples/assistant_stream_helpers.py b/examples/assistant_stream_helpers.py deleted file mode 100644 index 7baec77c72..0000000000 --- a/examples/assistant_stream_helpers.py +++ /dev/null @@ -1,78 +0,0 @@ -from __future__ import annotations - -from typing_extensions import override - -import openai -from openai import AssistantEventHandler -from openai.types.beta import AssistantStreamEvent -from openai.types.beta.threads import Text, TextDelta -from openai.types.beta.threads.runs import RunStep, RunStepDelta - - -class EventHandler(AssistantEventHandler): - @override - def on_event(self, event: AssistantStreamEvent) -> None: - if event.event == "thread.run.step.created": - details = event.data.step_details - if details.type == "tool_calls": - print("Generating code to interpret:\n\n```py") - elif event.event == "thread.message.created": - print("\nResponse:\n") - - @override - def on_text_delta(self, delta: TextDelta, snapshot: Text) -> None: - print(delta.value, end="", flush=True) - - @override - def on_run_step_done(self, run_step: RunStep) -> None: - details = run_step.step_details - if details.type == "tool_calls": - for tool in details.tool_calls: - if tool.type == "code_interpreter": - print("\n```\nExecuting code...") - - @override - def on_run_step_delta(self, delta: RunStepDelta, snapshot: RunStep) -> None: - details = delta.step_details - if details is not None and details.type == "tool_calls": - for tool in details.tool_calls or []: - if tool.type == "code_interpreter" and tool.code_interpreter and tool.code_interpreter.input: - print(tool.code_interpreter.input, end="", flush=True) - - -def main() -> None: - client = openai.OpenAI() - - assistant = client.beta.assistants.create( - name="Math Tutor", - instructions="You are a personal math tutor. Write and run code to answer math questions.", - tools=[{"type": "code_interpreter"}], - model="gpt-4-1106-preview", - ) - - try: - question = "I need to solve the equation `3x + 11 = 14`. Can you help me?" - - thread = client.beta.threads.create( - messages=[ - { - "role": "user", - "content": question, - }, - ] - ) - print(f"Question: {question}\n") - - with client.beta.threads.runs.stream( - thread_id=thread.id, - assistant_id=assistant.id, - instructions="Please address the user as Jane Doe. The user has a premium account.", - event_handler=EventHandler(), - ) as stream: - stream.until_done() - print() - finally: - client.beta.assistants.delete(assistant.id) - - -main() diff --git a/examples/audio.py b/examples/audio.py index 85f47bfb06..af41fe601b 100755 --- a/examples/audio.py +++ b/examples/audio.py @@ -1,6 +1,5 @@ #!/usr/bin/env rye run python -import time from pathlib import Path from openai import OpenAI @@ -12,8 +11,6 @@ def main() -> None: - stream_to_speakers() - # Create text-to-speech audio file with openai.audio.speech.with_streaming_response.create( model="tts-1", @@ -37,28 +34,5 @@ def main() -> None: print(translation.text) -def stream_to_speakers() -> None: - import pyaudio - - player_stream = pyaudio.PyAudio().open(format=pyaudio.paInt16, channels=1, rate=24000, output=True) - - start_time = time.time() - - with openai.audio.speech.with_streaming_response.create( - model="tts-1", - voice="alloy", - response_format="pcm", # similar to WAV, but without a header chunk at the start. - input="""I see skies of blue and clouds of white - The bright blessed days, the dark sacred nights - And I think to myself - What a wonderful world""", - ) as response: - print(f"Time to first byte: {int((time.time() - start_time) * 1000)}ms") - for chunk in response.iter_bytes(chunk_size=1024): - player_stream.write(chunk) - - print(f"Done in {int((time.time() - start_time) * 1000)}ms.") - - if __name__ == "__main__": main() diff --git a/examples/azure_ad.py b/examples/azure_ad.py index 1b0d81863d..67e2f23713 100755 --- a/examples/azure_ad.py +++ b/examples/azure_ad.py @@ -1,30 +1,67 @@ -from azure.identity import DefaultAzureCredential, get_bearer_token_provider +import asyncio -from openai import AzureOpenAI +from openai.lib.azure import AzureOpenAI, AsyncAzureOpenAI, AzureADTokenProvider, AsyncAzureADTokenProvider -token_provider = get_bearer_token_provider(DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default") +scopes = "https://cognitiveservices.azure.com/.default" - -# may change in the future +# May change in the future # https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning api_version = "2023-07-01-preview" # https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource endpoint = "https://my-resource.openai.azure.com" -client = AzureOpenAI( - api_version=api_version, - azure_endpoint=endpoint, - azure_ad_token_provider=token_provider, -) - -completion = client.chat.completions.create( - model="deployment-name", # e.g. gpt-35-instant - messages=[ - { - "role": "user", - "content": "How do I output all files in a directory using Python?", - }, - ], -) -print(completion.to_json()) +deployment_name = "deployment-name" # e.g. gpt-35-instant + + +def sync_main() -> None: + from azure.identity import DefaultAzureCredential, get_bearer_token_provider + + token_provider: AzureADTokenProvider = get_bearer_token_provider(DefaultAzureCredential(), scopes) + + client = AzureOpenAI( + api_version=api_version, + azure_endpoint=endpoint, + azure_ad_token_provider=token_provider, + ) + + completion = client.chat.completions.create( + model=deployment_name, + messages=[ + { + "role": "user", + "content": "How do I output all files in a directory using Python?", + } + ], + ) + + print(completion.to_json()) + + +async def async_main() -> None: + from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider + + token_provider: AsyncAzureADTokenProvider = get_bearer_token_provider(DefaultAzureCredential(), scopes) + + client = AsyncAzureOpenAI( + api_version=api_version, + azure_endpoint=endpoint, + azure_ad_token_provider=token_provider, + ) + + completion = await client.chat.completions.create( + model=deployment_name, + messages=[ + { + "role": "user", + "content": "How do I output all files in a directory using Python?", + } + ], + ) + + print(completion.to_json()) + + +sync_main() + +asyncio.run(async_main()) diff --git a/examples/bedrock.py b/examples/bedrock.py new file mode 100644 index 0000000000..24dafb5b80 --- /dev/null +++ b/examples/bedrock.py @@ -0,0 +1,13 @@ +from openai import BedrockOpenAI + +client = BedrockOpenAI() + +# For refreshed Bedrock bearer tokens: +# client = BedrockOpenAI(aws_region="us-west-2", bedrock_token_provider=get_bedrock_token) + +response = client.responses.create( + model="openai.gpt-5.4", + input="Say hello!", +) + +print(response.output_text) diff --git a/examples/image_stream.py b/examples/image_stream.py new file mode 100644 index 0000000000..eab5932534 --- /dev/null +++ b/examples/image_stream.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +import base64 +from pathlib import Path + +from openai import OpenAI + +client = OpenAI() + + +def main() -> None: + """Example of OpenAI image streaming with partial images.""" + stream = client.images.generate( + model="gpt-image-1", + prompt="A cute baby sea otter", + n=1, + size="1024x1024", + stream=True, + partial_images=3, + ) + + for event in stream: + if event.type == "image_generation.partial_image": + print(f" Partial image {event.partial_image_index + 1}/3 received") + print(f" Size: {len(event.b64_json)} characters (base64)") + + # Save partial image to file + filename = f"partial_{event.partial_image_index + 1}.png" + image_data = base64.b64decode(event.b64_json) + with open(filename, "wb") as f: + f.write(image_data) + print(f" 💾 Saved to: {Path(filename).resolve()}") + + elif event.type == "image_generation.completed": + print(f"\n✅ Final image completed!") + print(f" Size: {len(event.b64_json)} characters (base64)") + + # Save final image to file + filename = "final_image.png" + image_data = base64.b64decode(event.b64_json) + with open(filename, "wb") as f: + f.write(image_data) + print(f" 💾 Saved to: {Path(filename).resolve()}") + + else: + print(f"❓ Unknown event: {event}") # type: ignore[unreachable] + + +if __name__ == "__main__": + try: + main() + except Exception as error: + print(f"Error generating image: {error}") diff --git a/examples/parsing.py b/examples/parsing.py index 17e5db52ec..906ce974c1 100644 --- a/examples/parsing.py +++ b/examples/parsing.py @@ -18,7 +18,7 @@ class MathResponse(BaseModel): client = OpenAI() -completion = client.beta.chat.completions.parse( +completion = client.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ {"role": "system", "content": "You are a helpful math tutor."}, diff --git a/examples/parsing_stream.py b/examples/parsing_stream.py index 6c6f078f77..1be7853098 100644 --- a/examples/parsing_stream.py +++ b/examples/parsing_stream.py @@ -18,7 +18,7 @@ class MathResponse(BaseModel): client = OpenAI() -with client.beta.chat.completions.stream( +with client.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ {"role": "system", "content": "You are a helpful math tutor."}, diff --git a/examples/parsing_tools.py b/examples/parsing_tools.py index c6065eeb7a..26921b1df6 100644 --- a/examples/parsing_tools.py +++ b/examples/parsing_tools.py @@ -57,7 +57,7 @@ class Query(BaseModel): client = OpenAI() -completion = client.beta.chat.completions.parse( +completion = client.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { diff --git a/examples/parsing_tools_stream.py b/examples/parsing_tools_stream.py index eea6f6a43a..b7dcd3d230 100644 --- a/examples/parsing_tools_stream.py +++ b/examples/parsing_tools_stream.py @@ -15,7 +15,7 @@ class GetWeather(BaseModel): client = OpenAI() -with client.beta.chat.completions.stream( +with client.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { diff --git a/examples/realtime/audio_util.py b/examples/realtime/audio_util.py new file mode 100644 index 0000000000..954a508675 --- /dev/null +++ b/examples/realtime/audio_util.py @@ -0,0 +1,142 @@ +from __future__ import annotations + +import io +import base64 +import asyncio +import threading +from typing import Callable, Awaitable + +import numpy as np +import pyaudio +import sounddevice as sd +from pydub import AudioSegment + +from openai.resources.realtime.realtime import AsyncRealtimeConnection + +CHUNK_LENGTH_S = 0.05 # 100ms +SAMPLE_RATE = 24000 +FORMAT = pyaudio.paInt16 +CHANNELS = 1 + +# pyright: reportUnknownMemberType=false, reportUnknownVariableType=false, reportUnknownArgumentType=false + + +def audio_to_pcm16_base64(audio_bytes: bytes) -> bytes: + # load the audio file from the byte stream + audio = AudioSegment.from_file(io.BytesIO(audio_bytes)) + print(f"Loaded audio: {audio.frame_rate=} {audio.channels=} {audio.sample_width=} {audio.frame_width=}") + # resample to 24kHz mono pcm16 + pcm_audio = audio.set_frame_rate(SAMPLE_RATE).set_channels(CHANNELS).set_sample_width(2).raw_data + return pcm_audio + + +class AudioPlayerAsync: + def __init__(self): + self.queue = [] + self.lock = threading.Lock() + self.stream = sd.OutputStream( + callback=self.callback, + samplerate=SAMPLE_RATE, + channels=CHANNELS, + dtype=np.int16, + blocksize=int(CHUNK_LENGTH_S * SAMPLE_RATE), + ) + self.playing = False + self._frame_count = 0 + + def callback(self, outdata, frames, time, status): # noqa + with self.lock: + data = np.empty(0, dtype=np.int16) + + # get next item from queue if there is still space in the buffer + while len(data) < frames and len(self.queue) > 0: + item = self.queue.pop(0) + frames_needed = frames - len(data) + data = np.concatenate((data, item[:frames_needed])) + if len(item) > frames_needed: + self.queue.insert(0, item[frames_needed:]) + + self._frame_count += len(data) + + # fill the rest of the frames with zeros if there is no more data + if len(data) < frames: + data = np.concatenate((data, np.zeros(frames - len(data), dtype=np.int16))) + + outdata[:] = data.reshape(-1, 1) + + def reset_frame_count(self): + self._frame_count = 0 + + def get_frame_count(self): + return self._frame_count + + def add_data(self, data: bytes): + with self.lock: + # bytes is pcm16 single channel audio data, convert to numpy array + np_data = np.frombuffer(data, dtype=np.int16) + self.queue.append(np_data) + if not self.playing: + self.start() + + def start(self): + self.playing = True + self.stream.start() + + def stop(self): + self.playing = False + self.stream.stop() + with self.lock: + self.queue = [] + + def terminate(self): + self.stream.close() + + +async def send_audio_worker_sounddevice( + connection: AsyncRealtimeConnection, + should_send: Callable[[], bool] | None = None, + start_send: Callable[[], Awaitable[None]] | None = None, +): + sent_audio = False + + device_info = sd.query_devices() + print(device_info) + + read_size = int(SAMPLE_RATE * 0.02) + + stream = sd.InputStream( + channels=CHANNELS, + samplerate=SAMPLE_RATE, + dtype="int16", + ) + stream.start() + + try: + while True: + if stream.read_available < read_size: + await asyncio.sleep(0) + continue + + data, _ = stream.read(read_size) + + if should_send() if should_send else True: + if not sent_audio and start_send: + await start_send() + await connection.send( + {"type": "input_audio_buffer.append", "audio": base64.b64encode(data).decode("utf-8")} + ) + sent_audio = True + + elif sent_audio: + print("Done, triggering inference") + await connection.send({"type": "input_audio_buffer.commit"}) + await connection.send({"type": "response.create", "response": {}}) + sent_audio = False + + await asyncio.sleep(0) + + except KeyboardInterrupt: + pass + finally: + stream.stop() + stream.close() diff --git a/examples/realtime/azure_realtime.py b/examples/realtime/azure_realtime.py new file mode 100644 index 0000000000..a8bcf4c536 --- /dev/null +++ b/examples/realtime/azure_realtime.py @@ -0,0 +1,79 @@ +import os +import asyncio + +from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider + +from openai import AsyncOpenAI + +# Azure OpenAI Realtime Docs + +# How-to: https://learn.microsoft.com/azure/ai-services/openai/how-to/realtime-audio +# Supported models and API versions: https://learn.microsoft.com/azure/ai-services/openai/how-to/realtime-audio#supported-models +# Entra ID auth: https://learn.microsoft.com/azure/ai-services/openai/how-to/managed-identity + + +async def main() -> None: + """The following example demonstrates how to configure Azure OpenAI to use the Realtime API. + For an audio example, see push_to_talk_app.py and update the client and model parameter accordingly. + + When prompted for user input, type a message and hit enter to send it to the model. + Enter "q" to quit the conversation. + """ + + credential = DefaultAzureCredential() + token_provider = get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default") + token = await token_provider() + + # The endpoint of your Azure OpenAI resource is required. You can set it in the AZURE_OPENAI_ENDPOINT + # environment variable. + # You can find it in the Microsoft Foundry portal in the Overview page of your Azure OpenAI resource. + # Example: https://{your-resource}.openai.azure.com + endpoint = os.environ["AZURE_OPENAI_ENDPOINT"] + + # The deployment name of the model you want to use is required. You can set it in the AZURE_OPENAI_DEPLOYMENT_NAME + # environment variable. + # You can find it in the Foundry portal in the "Models + endpoints" page of your Azure OpenAI resource. + # Example: gpt-realtime + deployment_name = os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"] + + base_url = endpoint.replace("https://", "wss://").rstrip("/") + "/openai/v1" + + # The APIs are compatible with the OpenAI client library. + # You can use the OpenAI client library to access the Azure OpenAI APIs. + # Make sure to set the baseURL and apiKey to use the Azure OpenAI endpoint and token. + client = AsyncOpenAI(websocket_base_url=base_url, api_key=token) + async with client.realtime.connect( + model=deployment_name, + ) as connection: + await connection.session.update( + session={ + "output_modalities": ["text"], + "model": deployment_name, + "type": "realtime", + } + ) + while True: + user_input = input("Enter a message: ") + if user_input == "q": + break + + await connection.conversation.item.create( + item={ + "type": "message", + "role": "user", + "content": [{"type": "input_text", "text": user_input}], + } + ) + await connection.response.create() + async for event in connection: + if event.type == "response.output_text.delta": + print(event.delta, flush=True, end="") + elif event.type == "response.output_text.done": + print() + elif event.type == "response.done": + break + + await credential.close() + + +asyncio.run(main()) diff --git a/examples/realtime/push_to_talk_app.py b/examples/realtime/push_to_talk_app.py new file mode 100755 index 0000000000..acf38995b2 --- /dev/null +++ b/examples/realtime/push_to_talk_app.py @@ -0,0 +1,291 @@ +#!/usr/bin/env uv run +#################################################################### +# Sample TUI app with a push to talk interface to the Realtime API # +# If you have `uv` installed and the `OPENAI_API_KEY` # +# environment variable set, you can run this example with just # +# # +# `./examples/realtime/push_to_talk_app.py` # +# # +# On Mac, you'll also need `brew install portaudio ffmpeg` # +#################################################################### +# +# /// script +# requires-python = ">=3.9" +# dependencies = [ +# "textual", +# "numpy", +# "pyaudio", +# "pydub", +# "sounddevice", +# "openai[realtime]", +# ] +# +# [tool.uv.sources] +# openai = { path = "../../", editable = true } +# /// +from __future__ import annotations + +import base64 +import asyncio +from typing import Any, cast +from typing_extensions import override + +from textual import events +from audio_util import CHANNELS, SAMPLE_RATE, AudioPlayerAsync +from textual.app import App, ComposeResult +from textual.widgets import Button, Static, RichLog +from textual.reactive import reactive +from textual.containers import Container + +from openai import AsyncOpenAI +from openai.types.realtime.session import Session +from openai.resources.realtime.realtime import AsyncRealtimeConnection + + +class SessionDisplay(Static): + """A widget that shows the current session ID.""" + + session_id = reactive("") + + @override + def render(self) -> str: + return f"Session ID: {self.session_id}" if self.session_id else "Connecting..." + + +class AudioStatusIndicator(Static): + """A widget that shows the current audio recording status.""" + + is_recording = reactive(False) + + @override + def render(self) -> str: + status = ( + "🔴 Recording... (Press K to stop)" if self.is_recording else "⚪ Press K to start recording (Q to quit)" + ) + return status + + +class RealtimeApp(App[None]): + CSS = """ + Screen { + background: #1a1b26; /* Dark blue-grey background */ + } + + Container { + border: double rgb(91, 164, 91); + } + + Horizontal { + width: 100%; + } + + #input-container { + height: 5; /* Explicit height for input container */ + margin: 1 1; + padding: 1 2; + } + + Input { + width: 80%; + height: 3; /* Explicit height for input */ + } + + Button { + width: 20%; + height: 3; /* Explicit height for button */ + } + + #bottom-pane { + width: 100%; + height: 82%; /* Reduced to make room for session display */ + border: round rgb(205, 133, 63); + content-align: center middle; + } + + #status-indicator { + height: 3; + content-align: center middle; + background: #2a2b36; + border: solid rgb(91, 164, 91); + margin: 1 1; + } + + #session-display { + height: 3; + content-align: center middle; + background: #2a2b36; + border: solid rgb(91, 164, 91); + margin: 1 1; + } + + Static { + color: white; + } + """ + + client: AsyncOpenAI + should_send_audio: asyncio.Event + audio_player: AudioPlayerAsync + last_audio_item_id: str | None + connection: AsyncRealtimeConnection | None + session: Session | None + connected: asyncio.Event + + def __init__(self) -> None: + super().__init__() + self.connection = None + self.session = None + self.client = AsyncOpenAI() + self.audio_player = AudioPlayerAsync() + self.last_audio_item_id = None + self.should_send_audio = asyncio.Event() + self.connected = asyncio.Event() + + @override + def compose(self) -> ComposeResult: + """Create child widgets for the app.""" + with Container(): + yield SessionDisplay(id="session-display") + yield AudioStatusIndicator(id="status-indicator") + yield RichLog(id="bottom-pane", wrap=True, highlight=True, markup=True) + + async def on_mount(self) -> None: + self.run_worker(self.handle_realtime_connection()) + self.run_worker(self.send_mic_audio()) + + async def handle_realtime_connection(self) -> None: + async with self.client.realtime.connect(model="gpt-realtime") as conn: + self.connection = conn + self.connected.set() + + # note: this is the default and can be omitted + # if you want to manually handle VAD yourself, then set `'turn_detection': None` + await conn.session.update( + session={ + "audio": { + "input": {"turn_detection": {"type": "server_vad"}}, + }, + "model": "gpt-realtime", + "type": "realtime", + } + ) + + acc_items: dict[str, Any] = {} + + async for event in conn: + if event.type == "session.created": + self.session = event.session + session_display = self.query_one(SessionDisplay) + assert event.session.id is not None + session_display.session_id = event.session.id + continue + + if event.type == "session.updated": + self.session = event.session + continue + + if event.type == "response.output_audio.delta": + if event.item_id != self.last_audio_item_id: + self.audio_player.reset_frame_count() + self.last_audio_item_id = event.item_id + + bytes_data = base64.b64decode(event.delta) + self.audio_player.add_data(bytes_data) + continue + + if event.type == "response.output_audio_transcript.delta": + try: + text = acc_items[event.item_id] + except KeyError: + acc_items[event.item_id] = event.delta + else: + acc_items[event.item_id] = text + event.delta + + # Clear and update the entire content because RichLog otherwise treats each delta as a new line + bottom_pane = self.query_one("#bottom-pane", RichLog) + bottom_pane.clear() + bottom_pane.write(acc_items[event.item_id]) + continue + + async def _get_connection(self) -> AsyncRealtimeConnection: + await self.connected.wait() + assert self.connection is not None + return self.connection + + async def send_mic_audio(self) -> None: + import sounddevice as sd # type: ignore + + sent_audio = False + + device_info = sd.query_devices() + print(device_info) + + read_size = int(SAMPLE_RATE * 0.02) + + stream = sd.InputStream( + channels=CHANNELS, + samplerate=SAMPLE_RATE, + dtype="int16", + ) + stream.start() + + status_indicator = self.query_one(AudioStatusIndicator) + + try: + while True: + if stream.read_available < read_size: + await asyncio.sleep(0) + continue + + await self.should_send_audio.wait() + status_indicator.is_recording = True + + data, _ = stream.read(read_size) + + connection = await self._get_connection() + if not sent_audio: + asyncio.create_task(connection.send({"type": "response.cancel"})) + sent_audio = True + + await connection.input_audio_buffer.append(audio=base64.b64encode(cast(Any, data)).decode("utf-8")) + + await asyncio.sleep(0) + except KeyboardInterrupt: + pass + finally: + stream.stop() + stream.close() + + async def on_key(self, event: events.Key) -> None: + """Handle key press events.""" + if event.key == "enter": + self.query_one(Button).press() + return + + if event.key == "q": + self.exit() + return + + if event.key == "k": + status_indicator = self.query_one(AudioStatusIndicator) + if status_indicator.is_recording: + self.should_send_audio.clear() + status_indicator.is_recording = False + + if self.session and self.session.turn_detection is None: + # The default in the API is that the model will automatically detect when the user has + # stopped talking and then start responding itself. + # + # However if we're in manual `turn_detection` mode then we need to + # manually tell the model to commit the audio buffer and start responding. + conn = await self._get_connection() + await conn.input_audio_buffer.commit() + await conn.response.create() + else: + self.should_send_audio.set() + status_indicator.is_recording = True + + +if __name__ == "__main__": + app = RealtimeApp() + app.run() diff --git a/examples/realtime/realtime.py b/examples/realtime/realtime.py new file mode 100755 index 0000000000..214961e54c --- /dev/null +++ b/examples/realtime/realtime.py @@ -0,0 +1,54 @@ +#!/usr/bin/env rye run python +import asyncio + +from openai import AsyncOpenAI + +# Azure OpenAI Realtime Docs + +# How-to: https://learn.microsoft.com/azure/ai-services/openai/how-to/realtime-audio +# Supported models and API versions: https://learn.microsoft.com/azure/ai-services/openai/how-to/realtime-audio#supported-models +# Entra ID auth: https://learn.microsoft.com/azure/ai-services/openai/how-to/managed-identity + + +async def main() -> None: + """The following example demonstrates how to configure OpenAI to use the Realtime API. + For an audio example, see push_to_talk_app.py and update the client and model parameter accordingly. + + When prompted for user input, type a message and hit enter to send it to the model. + Enter "q" to quit the conversation. + """ + + client = AsyncOpenAI() + async with client.realtime.connect( + model="gpt-realtime", + ) as connection: + await connection.session.update( + session={ + "output_modalities": ["text"], + "model": "gpt-realtime", + "type": "realtime", + } + ) + while True: + user_input = input("Enter a message: ") + if user_input == "q": + break + + await connection.conversation.item.create( + item={ + "type": "message", + "role": "user", + "content": [{"type": "input_text", "text": user_input}], + } + ) + await connection.response.create() + async for event in connection: + if event.type == "response.output_text.delta": + print(event.delta, flush=True, end="") + elif event.type == "response.output_text.done": + print() + elif event.type == "response.done": + break + + +asyncio.run(main()) diff --git a/examples/responses/__init__.py b/examples/responses/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/responses/background.py b/examples/responses/background.py new file mode 100644 index 0000000000..37b00f19be --- /dev/null +++ b/examples/responses/background.py @@ -0,0 +1,46 @@ +from typing import List + +import rich +from pydantic import BaseModel + +from openai import OpenAI + + +class Step(BaseModel): + explanation: str + output: str + + +class MathResponse(BaseModel): + steps: List[Step] + final_answer: str + + +client = OpenAI() +id = None + +with client.responses.create( + input="solve 8x + 31 = 2", + model="gpt-4o-2024-08-06", + background=True, + stream=True, +) as stream: + for event in stream: + if event.type == "response.created": + id = event.response.id + if "output_text" in event.type: + rich.print(event) + if event.sequence_number == 10: + break + +print("Interrupted. Continuing...") + +assert id is not None +with client.responses.retrieve( + response_id=id, + stream=True, + starting_after=10, +) as stream: + for event in stream: + if "output_text" in event.type: + rich.print(event) diff --git a/examples/responses/background_async.py b/examples/responses/background_async.py new file mode 100644 index 0000000000..9dbc78b784 --- /dev/null +++ b/examples/responses/background_async.py @@ -0,0 +1,52 @@ +import asyncio +from typing import List + +import rich +from pydantic import BaseModel + +from openai._client import AsyncOpenAI + + +class Step(BaseModel): + explanation: str + output: str + + +class MathResponse(BaseModel): + steps: List[Step] + final_answer: str + + +async def main() -> None: + client = AsyncOpenAI() + id = None + + async with await client.responses.create( + input="solve 8x + 31 = 2", + model="gpt-4o-2024-08-06", + background=True, + stream=True, + ) as stream: + async for event in stream: + if event.type == "response.created": + id = event.response.id + if "output_text" in event.type: + rich.print(event) + if event.sequence_number == 10: + break + + print("Interrupted. Continuing...") + + assert id is not None + async with await client.responses.retrieve( + response_id=id, + stream=True, + starting_after=10, + ) as stream: + async for event in stream: + if "output_text" in event.type: + rich.print(event) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/responses/background_streaming.py b/examples/responses/background_streaming.py new file mode 100755 index 0000000000..ed830d9910 --- /dev/null +++ b/examples/responses/background_streaming.py @@ -0,0 +1,48 @@ +#!/usr/bin/env -S rye run python +from typing import List + +import rich +from pydantic import BaseModel + +from openai import OpenAI + + +class Step(BaseModel): + explanation: str + output: str + + +class MathResponse(BaseModel): + steps: List[Step] + final_answer: str + + +client = OpenAI() +id = None +with client.responses.stream( + input="solve 8x + 31 = 2", + model="gpt-4o-2024-08-06", + text_format=MathResponse, + background=True, +) as stream: + for event in stream: + if event.type == "response.created": + id = event.response.id + if "output_text" in event.type: + rich.print(event) + if event.sequence_number == 10: + break + +print("Interrupted. Continuing...") + +assert id is not None +with client.responses.stream( + response_id=id, + starting_after=10, + text_format=MathResponse, +) as stream: + for event in stream: + if "output_text" in event.type: + rich.print(event) + + rich.print(stream.get_final_response()) diff --git a/examples/responses/background_streaming_async.py b/examples/responses/background_streaming_async.py new file mode 100644 index 0000000000..178150dc15 --- /dev/null +++ b/examples/responses/background_streaming_async.py @@ -0,0 +1,53 @@ +import asyncio +from typing import List + +import rich +from pydantic import BaseModel + +from openai import AsyncOpenAI + + +class Step(BaseModel): + explanation: str + output: str + + +class MathResponse(BaseModel): + steps: List[Step] + final_answer: str + + +async def main() -> None: + client = AsyncOpenAI() + id = None + async with client.responses.stream( + input="solve 8x + 31 = 2", + model="gpt-4o-2024-08-06", + text_format=MathResponse, + background=True, + ) as stream: + async for event in stream: + if event.type == "response.created": + id = event.response.id + if "output_text" in event.type: + rich.print(event) + if event.sequence_number == 10: + break + + print("Interrupted. Continuing...") + + assert id is not None + async with client.responses.stream( + response_id=id, + starting_after=10, + text_format=MathResponse, + ) as stream: + async for event in stream: + if "output_text" in event.type: + rich.print(event) + + rich.print(stream.get_final_response()) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/responses/streaming.py b/examples/responses/streaming.py new file mode 100644 index 0000000000..39787968d6 --- /dev/null +++ b/examples/responses/streaming.py @@ -0,0 +1,30 @@ +from typing import List + +import rich +from pydantic import BaseModel + +from openai import OpenAI + + +class Step(BaseModel): + explanation: str + output: str + + +class MathResponse(BaseModel): + steps: List[Step] + final_answer: str + + +client = OpenAI() + +with client.responses.stream( + input="solve 8x + 31 = 2", + model="gpt-4o-2024-08-06", + text_format=MathResponse, +) as stream: + for event in stream: + if "output_text" in event.type: + rich.print(event) + +rich.print(stream.get_final_response()) diff --git a/examples/responses/streaming_tools.py b/examples/responses/streaming_tools.py new file mode 100644 index 0000000000..f40cd9356d --- /dev/null +++ b/examples/responses/streaming_tools.py @@ -0,0 +1,68 @@ +from enum import Enum +from typing import List, Union + +import rich +from pydantic import BaseModel + +import openai +from openai import OpenAI + + +class Table(str, Enum): + orders = "orders" + customers = "customers" + products = "products" + + +class Column(str, Enum): + id = "id" + status = "status" + expected_delivery_date = "expected_delivery_date" + delivered_at = "delivered_at" + shipped_at = "shipped_at" + ordered_at = "ordered_at" + canceled_at = "canceled_at" + + +class Operator(str, Enum): + eq = "=" + gt = ">" + lt = "<" + le = "<=" + ge = ">=" + ne = "!=" + + +class OrderBy(str, Enum): + asc = "asc" + desc = "desc" + + +class DynamicValue(BaseModel): + column_name: str + + +class Condition(BaseModel): + column: str + operator: Operator + value: Union[str, int, DynamicValue] + + +class Query(BaseModel): + table_name: Table + columns: List[Column] + conditions: List[Condition] + order_by: OrderBy + + +client = OpenAI() + +with client.responses.stream( + model="gpt-4o-2024-08-06", + input="look up all my orders in november of last year that were fulfilled but not delivered on time", + tools=[ + openai.pydantic_function_tool(Query), + ], +) as stream: + for event in stream: + rich.print(event) diff --git a/examples/responses/structured_outputs.py b/examples/responses/structured_outputs.py new file mode 100644 index 0000000000..0b146bc0bc --- /dev/null +++ b/examples/responses/structured_outputs.py @@ -0,0 +1,55 @@ +from typing import List + +import rich +from pydantic import BaseModel + +from openai import OpenAI + + +class Step(BaseModel): + explanation: str + output: str + + +class MathResponse(BaseModel): + steps: List[Step] + final_answer: str + + +client = OpenAI() + +rsp = client.responses.parse( + input="solve 8x + 31 = 2", + model="gpt-4o-2024-08-06", + text_format=MathResponse, +) + +for output in rsp.output: + if output.type != "message": + raise Exception("Unexpected non message") + + for item in output.content: + if item.type != "output_text": + raise Exception("unexpected output type") + + if not item.parsed: + raise Exception("Could not parse response") + + rich.print(item.parsed) + + print("answer: ", item.parsed.final_answer) + +# or + +message = rsp.output[0] +assert message.type == "message" + +text = message.content[0] +assert text.type == "output_text" + +if not text.parsed: + raise Exception("Could not parse response") + +rich.print(text.parsed) + +print("answer: ", text.parsed.final_answer) diff --git a/examples/responses/structured_outputs_tools.py b/examples/responses/structured_outputs_tools.py new file mode 100644 index 0000000000..918348207d --- /dev/null +++ b/examples/responses/structured_outputs_tools.py @@ -0,0 +1,73 @@ +from enum import Enum +from typing import List, Union + +import rich +from pydantic import BaseModel + +import openai +from openai import OpenAI + + +class Table(str, Enum): + orders = "orders" + customers = "customers" + products = "products" + + +class Column(str, Enum): + id = "id" + status = "status" + expected_delivery_date = "expected_delivery_date" + delivered_at = "delivered_at" + shipped_at = "shipped_at" + ordered_at = "ordered_at" + canceled_at = "canceled_at" + + +class Operator(str, Enum): + eq = "=" + gt = ">" + lt = "<" + le = "<=" + ge = ">=" + ne = "!=" + + +class OrderBy(str, Enum): + asc = "asc" + desc = "desc" + + +class DynamicValue(BaseModel): + column_name: str + + +class Condition(BaseModel): + column: str + operator: Operator + value: Union[str, int, DynamicValue] + + +class Query(BaseModel): + table_name: Table + columns: List[Column] + conditions: List[Condition] + order_by: OrderBy + + +client = OpenAI() + +response = client.responses.parse( + model="gpt-4o-2024-08-06", + input="look up all my orders in november of last year that were fulfilled but not delivered on time", + tools=[ + openai.pydantic_function_tool(Query), + ], +) + +rich.print(response) + +function_call = response.output[0] +assert function_call.type == "function_call" +assert isinstance(function_call.parsed_arguments, Query) +print("table name:", function_call.parsed_arguments.table_name) diff --git a/examples/responses/websocket.py b/examples/responses/websocket.py new file mode 100644 index 0000000000..2c51d8ef02 --- /dev/null +++ b/examples/responses/websocket.py @@ -0,0 +1,436 @@ +from __future__ import annotations + +import json +import argparse +from typing import TYPE_CHECKING, Dict, Union, Literal, Optional, TypedDict, NamedTuple, cast + +from openai import OpenAI +from openai.types.responses import ( + FunctionToolParam, + ToolChoiceOptions, + ResponseInputParam, + ResponseFailedEvent, + ResponseCompletedEvent, + ResponseInputItemParam, + ResponseIncompleteEvent, + ToolChoiceFunctionParam, +) + +if TYPE_CHECKING: + from openai.resources.responses.responses import ResponsesConnection + +ToolName = Literal["get_sku_inventory", "get_supplier_eta", "get_quality_alerts"] +ToolChoice = Union[ToolChoiceOptions, ToolChoiceFunctionParam] + + +class DemoTurn(TypedDict): + tool_name: ToolName + prompt: str + + +class SKUArguments(TypedDict): + sku: str + + +class SKUInventoryOutput(TypedDict): + sku: str + warehouse: str + on_hand_units: int + reserved_units: int + reorder_point: int + safety_stock: int + + +class SupplierShipment(TypedDict): + shipment_id: str + eta_date: str + quantity: int + risk: str + + +class SupplierETAOutput(TypedDict): + sku: str + supplier_shipments: list[SupplierShipment] + + +class QualityAlert(TypedDict): + alert_id: str + status: str + severity: str + summary: str + + +class QualityAlertsOutput(TypedDict): + sku: str + alerts: list[QualityAlert] + + +class FunctionCallOutputItem(TypedDict): + type: Literal["function_call_output"] + call_id: str + output: str + + +class FunctionCallRequest(NamedTuple): + name: str + arguments_json: str + call_id: str + + +class RunResponseResult(NamedTuple): + text: str + response_id: str + function_calls: list[FunctionCallRequest] + + +class RunTurnResult(NamedTuple): + assistant_text: str + response_id: str + + +ToolOutput = Union[SKUInventoryOutput, SupplierETAOutput, QualityAlertsOutput] + +TOOLS: list[FunctionToolParam] = [ + { + "type": "function", + "name": "get_sku_inventory", + "description": "Return froge pond inventory details for a SKU.", + "strict": True, + "parameters": { + "type": "object", + "properties": { + "sku": { + "type": "string", + "description": "Stock-keeping unit identifier, such as sku-froge-lily-pad-deluxe.", + } + }, + "required": ["sku"], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "get_supplier_eta", + "description": "Return tadpole supplier restock ETA data for a SKU.", + "strict": True, + "parameters": { + "type": "object", + "properties": { + "sku": { + "type": "string", + "description": "Stock-keeping unit identifier, such as sku-froge-lily-pad-deluxe.", + } + }, + "required": ["sku"], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "get_quality_alerts", + "description": "Return recent froge quality alerts for a SKU.", + "strict": True, + "parameters": { + "type": "object", + "properties": { + "sku": { + "type": "string", + "description": "Stock-keeping unit identifier, such as sku-froge-lily-pad-deluxe.", + } + }, + "required": ["sku"], + "additionalProperties": False, + }, + }, +] + +DEMO_TURNS: list[DemoTurn] = [ + { + "tool_name": "get_sku_inventory", + "prompt": "Use get_sku_inventory for sku='sku-froge-lily-pad-deluxe' and summarize current pond stock health in one sentence.", + }, + { + "tool_name": "get_supplier_eta", + "prompt": "Now use get_supplier_eta for the same SKU and summarize restock ETA and tadpole shipment risk.", + }, + { + "tool_name": "get_quality_alerts", + "prompt": "Finally use get_quality_alerts for the same SKU and summarize unresolved froge quality concerns in one short paragraph.", + }, +] + +BETA_HEADER_VALUE = "responses_websockets=2026-02-06" + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=("Run a 3-turn Responses WebSocket demo with function calling and chained previous_response_id.") + ) + parser.add_argument("--model", default="gpt-5.2", help="Model used in the `response.create` payload.") + parser.add_argument( + "--use-beta-header", + action="store_true", + help=f"Include `OpenAI-Beta: {BETA_HEADER_VALUE}` for beta websocket behavior.", + ) + parser.add_argument( + "--show-events", + action="store_true", + help="Print non-text event types while streaming.", + ) + parser.add_argument( + "--show-tool-io", + action="store_true", + help="Print each tool call and tool output payload.", + ) + return parser.parse_args() + + +def parse_tool_name(name: str) -> ToolName: + if name not in {"get_sku_inventory", "get_supplier_eta", "get_quality_alerts"}: + raise ValueError(f"Unsupported tool requested: {name}") + return cast(ToolName, name) + + +def parse_sku_arguments(raw_arguments: str) -> SKUArguments: + parsed_raw = json.loads(raw_arguments) + if not isinstance(parsed_raw, dict): + raise ValueError(f"Tool arguments must be a JSON object: {raw_arguments}") + + parsed = cast(Dict[str, object], parsed_raw) + sku_value = parsed.get("sku") + if not isinstance(sku_value, str): + raise ValueError(f"Tool arguments must include a string `sku`: {raw_arguments}") + + return {"sku": sku_value} + + +def call_tool(name: ToolName, arguments: SKUArguments) -> ToolOutput: + sku = arguments["sku"] + + if name == "get_sku_inventory": + return { + "sku": sku, + "warehouse": "pond-west-1", + "on_hand_units": 84, + "reserved_units": 26, + "reorder_point": 60, + "safety_stock": 40, + } + + if name == "get_supplier_eta": + return { + "sku": sku, + "supplier_shipments": [ + { + "shipment_id": "frog_ship_2201", + "eta_date": "2026-02-24", + "quantity": 180, + "risk": "low", + }, + { + "shipment_id": "frog_ship_2205", + "eta_date": "2026-03-03", + "quantity": 220, + "risk": "medium", + }, + ], + } + + if name == "get_quality_alerts": + return { + "sku": sku, + "alerts": [ + { + "alert_id": "frog_qa_781", + "status": "open", + "severity": "high", + "summary": "Lily-pad coating chipping in lot LP-42", + }, + { + "alert_id": "frog_qa_795", + "status": "in_progress", + "severity": "medium", + "summary": "Pond-crate scuff rate above threshold", + }, + { + "alert_id": "frog_qa_802", + "status": "resolved", + "severity": "low", + "summary": "Froge label alignment issue corrected", + }, + ], + } + + raise ValueError(f"Unknown tool: {name}") + + +def run_response( + *, + connection: ResponsesConnection, + model: str, + previous_response_id: Optional[str], + input_payload: Union[str, ResponseInputParam], + tools: list[FunctionToolParam], + tool_choice: ToolChoice, + show_events: bool, +) -> RunResponseResult: + connection.response.create( + model=model, + input=input_payload, + stream=True, + previous_response_id=previous_response_id, + tools=tools, + tool_choice=tool_choice, + ) + + text_parts: list[str] = [] + function_calls: list[FunctionCallRequest] = [] + response_id: Optional[str] = None + + for event in connection: + if event.type == "response.output_text.delta": + text_parts.append(event.delta) + continue + + if event.type == "response.output_item.done" and event.item.type == "function_call": + function_calls.append( + FunctionCallRequest( + name=event.item.name, + arguments_json=event.item.arguments, + call_id=event.item.call_id, + ) + ) + continue + + if getattr(event, "type", None) == "error": + raise RuntimeError(f"WebSocket error event: {event!r}") + + if isinstance(event, (ResponseCompletedEvent, ResponseFailedEvent, ResponseIncompleteEvent)): + response_id = event.response.id + if not isinstance(event, ResponseCompletedEvent): + raise RuntimeError(f"Response ended with {event.type} (id={response_id})") + if show_events: + print(f"[{event.type}]") + break + + if getattr(event, "type", None) == "response.done": + # Responses over WebSocket currently emit `response.done` as the final event. + # The payload still includes `response.id`, which we use for chaining. + event_response = getattr(event, "response", None) + event_response_id: Optional[str] = None + if isinstance(event_response, dict): + event_response_dict = cast(Dict[str, object], event_response) + raw_event_response_id = event_response_dict.get("id") + if isinstance(raw_event_response_id, str): + event_response_id = raw_event_response_id + else: + raw_event_response_id = getattr(event_response, "id", None) + if isinstance(raw_event_response_id, str): + event_response_id = raw_event_response_id + + if not isinstance(event_response_id, str): + raise RuntimeError(f"response.done event did not include a valid response.id: {event!r}") + + response_id = event_response_id + if show_events: + print("[response.done]") + break + + if show_events: + print(f"[{event.type}]") + + if response_id is None: + raise RuntimeError("No terminal response event received.") + + return RunResponseResult( + text="".join(text_parts), + response_id=response_id, + function_calls=function_calls, + ) + + +def run_turn( + *, + connection: ResponsesConnection, + model: str, + previous_response_id: Optional[str], + turn_prompt: str, + forced_tool_name: ToolName, + show_events: bool, + show_tool_io: bool, +) -> RunTurnResult: + accumulated_text_parts: list[str] = [] + + current_input: Union[str, ResponseInputParam] = turn_prompt + current_tool_choice: ToolChoice = {"type": "function", "name": forced_tool_name} + current_previous_response_id = previous_response_id + + while True: + response_result = run_response( + connection=connection, + model=model, + previous_response_id=current_previous_response_id, + input_payload=current_input, + tools=TOOLS, + tool_choice=current_tool_choice, + show_events=show_events, + ) + + if response_result.text: + accumulated_text_parts.append(response_result.text) + + current_previous_response_id = response_result.response_id + if not response_result.function_calls: + break + + tool_outputs: ResponseInputParam = [] + for function_call in response_result.function_calls: + tool_name = parse_tool_name(function_call.name) + arguments = parse_sku_arguments(function_call.arguments_json) + output_payload = call_tool(tool_name, arguments) + if show_tool_io: + print(f"[tool_call] {function_call.name}({function_call.arguments_json})") + print(f"[tool_output] {json.dumps(output_payload)}") + + function_call_output: FunctionCallOutputItem = { + "type": "function_call_output", + "call_id": function_call.call_id, + "output": json.dumps(output_payload), + } + tool_outputs.append(cast(ResponseInputItemParam, function_call_output)) + + current_input = tool_outputs + current_tool_choice = "none" + + return RunTurnResult( + assistant_text="".join(accumulated_text_parts).strip(), + response_id=current_previous_response_id, + ) + + +def main() -> None: + args = parse_args() + + client = OpenAI() + extra_headers = {"OpenAI-Beta": BETA_HEADER_VALUE} if args.use_beta_header else {} + + with client.responses.connect(extra_headers=extra_headers) as connection: + previous_response_id: Optional[str] = None + for index, turn in enumerate(DEMO_TURNS, start=1): + print(f"\n=== Turn {index} ===") + print(f"User: {turn['prompt']}") + turn_result = run_turn( + connection=connection, + model=args.model, + previous_response_id=previous_response_id, + turn_prompt=turn["prompt"], + forced_tool_name=turn["tool_name"], + show_events=args.show_events, + show_tool_io=args.show_tool_io, + ) + previous_response_id = turn_result.response_id + print(f"Assistant: {turn_result.assistant_text}") + + +if __name__ == "__main__": + main() diff --git a/examples/responses_input_tokens.py b/examples/responses_input_tokens.py new file mode 100644 index 0000000000..39809b928f --- /dev/null +++ b/examples/responses_input_tokens.py @@ -0,0 +1,54 @@ +from typing import List + +from openai import OpenAI +from openai.types.responses.tool_param import ToolParam +from openai.types.responses.response_input_item_param import ResponseInputItemParam + + +def main() -> None: + client = OpenAI() + tools: List[ToolParam] = [ + { + "type": "function", + "name": "get_current_weather", + "description": "Get current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "City and state, e.g. San Francisco, CA", + }, + "unit": { + "type": "string", + "enum": ["c", "f"], + "description": "Temperature unit to use", + }, + }, + "required": ["location", "unit"], + "additionalProperties": False, + }, + "strict": True, + } + ] + + input_items: List[ResponseInputItemParam] = [ + { + "type": "message", + "role": "user", + "content": [{"type": "input_text", "text": "What's the weather in San Francisco today?"}], + } + ] + + response = client.responses.input_tokens.count( + model="gpt-5", + instructions="You are a concise assistant.", + input=input_items, + tools=tools, + tool_choice={"type": "function", "name": "get_current_weather"}, + ) + print(f"input tokens: {response.input_tokens}") + + +if __name__ == "__main__": + main() diff --git a/examples/speech_to_text.py b/examples/speech_to_text.py new file mode 100755 index 0000000000..cc3f56b424 --- /dev/null +++ b/examples/speech_to_text.py @@ -0,0 +1,25 @@ +#!/usr/bin/env rye run python + +import asyncio + +from openai import AsyncOpenAI +from openai.helpers import Microphone + +# gets OPENAI_API_KEY from your environment variables +openai = AsyncOpenAI() + + +async def main() -> None: + print("Recording for the next 10 seconds...") + recording = await Microphone(timeout=10).record() + print("Recording complete") + transcription = await openai.audio.transcriptions.create( + model="whisper-1", + file=recording, + ) + + print(transcription.text) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/text_to_speech.py b/examples/text_to_speech.py new file mode 100755 index 0000000000..ac8b12b0ab --- /dev/null +++ b/examples/text_to_speech.py @@ -0,0 +1,31 @@ +#!/usr/bin/env rye run python + +import time +import asyncio + +from openai import AsyncOpenAI +from openai.helpers import LocalAudioPlayer + +# gets OPENAI_API_KEY from your environment variables +openai = AsyncOpenAI() + + +async def main() -> None: + start_time = time.time() + + async with openai.audio.speech.with_streaming_response.create( + model="tts-1", + voice="alloy", + response_format="pcm", # similar to WAV, but without a header chunk at the start. + input="""I see skies of blue and clouds of white + The bright blessed days, the dark sacred nights + And I think to myself + What a wonderful world""", + ) as response: + print(f"Time to first byte: {int((time.time() - start_time) * 1000)}ms") + await LocalAudioPlayer().play(response) + print(f"Time to play: {int((time.time() - start_time) * 1000)}ms") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/video.py b/examples/video.py new file mode 100644 index 0000000000..ee89e64697 --- /dev/null +++ b/examples/video.py @@ -0,0 +1,22 @@ +#!/usr/bin/env -S poetry run python + +import asyncio + +from openai import AsyncOpenAI + +client = AsyncOpenAI() + + +async def main() -> None: + video = await client.videos.create_and_poll( + model="sora-2", + prompt="A video of the words 'Thank you' in sparkling letters", + ) + + if video.status == "completed": + print("Video successfully completed: ", video) + else: + print("Video creation failed. Status: ", video.status) + + +asyncio.run(main()) diff --git a/helpers.md b/helpers.md index 965dd6e23c..89ff4498cf 100644 --- a/helpers.md +++ b/helpers.md @@ -2,7 +2,7 @@ The OpenAI API supports extracting JSON from the model with the `response_format` request param, for more details on the API, see [this guide](https://platform.openai.com/docs/guides/structured-outputs). -The SDK provides a `client.beta.chat.completions.parse()` method which is a wrapper over the `client.chat.completions.create()` that +The SDK provides a `client.chat.completions.parse()` method which is a wrapper over the `client.chat.completions.create()` that provides richer integrations with Python specific types & returns a `ParsedChatCompletion` object, which is a subclass of the standard `ChatCompletion` class. ## Auto-parsing response content with Pydantic models @@ -24,7 +24,7 @@ class MathResponse(BaseModel): final_answer: str client = OpenAI() -completion = client.beta.chat.completions.parse( +completion = client.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ {"role": "system", "content": "You are a helpful math tutor."}, @@ -44,6 +44,7 @@ else: ## Auto-parsing function tool calls The `.parse()` method will also automatically parse `function` tool calls if: + - You use the `openai.pydantic_function_tool()` helper method - You mark your tool schema with `"strict": True` @@ -96,7 +97,7 @@ class Query(BaseModel): order_by: OrderBy client = openai.OpenAI() -completion = client.beta.chat.completions.parse( +completion = client.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -121,20 +122,20 @@ print(tool_call.function.parsed_arguments.table_name) ### Differences from `.create()` -The `beta.chat.completions.parse()` method imposes some additional restrictions on it's usage that `chat.completions.create()` does not. +The `chat.completions.parse()` method imposes some additional restrictions on it's usage that `chat.completions.create()` does not. - If the completion completes with `finish_reason` set to `length` or `content_filter`, the `LengthFinishReasonError` / `ContentFilterFinishReasonError` errors will be raised. - Only strict function tools can be passed, e.g. `{'type': 'function', 'function': {..., 'strict': True}}` # Streaming Helpers -OpenAI supports streaming responses when interacting with the [Chat Completion] & [Assistant](#assistant-streaming-api) APIs. +OpenAI supports streaming responses when interacting with the [Chat Completion](#chat-completions-api) & [Assistant](#assistant-streaming-api) APIs. ## Chat Completions API -The SDK provides a `.beta.chat.completions.stream()` method that wraps the `.chat.completions.create(stream=True)` stream providing a more granular event API & automatic accumulation of each delta. +The SDK provides a `.chat.completions.stream()` method that wraps the `.chat.completions.create(stream=True)` stream providing a more granular event API & automatic accumulation of each delta. -It also supports all aforementioned [parsing helpers](#parsing-helpers). +It also supports all aforementioned [parsing helpers](#structured-outputs-parsing-helpers). Unlike `.create(stream=True)`, the `.stream()` method requires usage within a context manager to prevent accidental leakage of the response: @@ -143,7 +144,7 @@ from openai import AsyncOpenAI client = AsyncOpenAI() -async with client.beta.chat.completions.stream( +async with client.chat.completions.stream( model='gpt-4o-2024-08-06', messages=[...], ) as stream: @@ -263,7 +264,7 @@ A handful of helper methods are provided on the stream class for additional conv Returns the accumulated `ParsedChatCompletion` object ```py -async with client.beta.chat.completions.stream(...) as stream: +async with client.chat.completions.stream(...) as stream: ... completion = await stream.get_final_completion() @@ -275,7 +276,7 @@ print(completion.choices[0].message) If you want to wait for the stream to complete, you can use the `.until_done()` method. ```py -async with client.beta.chat.completions.stream(...) as stream: +async with client.chat.completions.stream(...) as stream: await stream.until_done() # stream is now finished ``` @@ -508,9 +509,10 @@ The polling methods are: ```python client.beta.threads.create_and_run_poll(...) client.beta.threads.runs.create_and_poll(...) -client.beta.threads.runs.submit_tool_ouptputs_and_poll(...) +client.beta.threads.runs.submit_tool_outputs_and_poll(...) client.beta.vector_stores.files.upload_and_poll(...) client.beta.vector_stores.files.create_and_poll(...) client.beta.vector_stores.file_batches.create_and_poll(...) client.beta.vector_stores.file_batches.upload_and_poll(...) +client.videos.create_and_poll(...) ``` diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index a4517a002d..0000000000 --- a/mypy.ini +++ /dev/null @@ -1,47 +0,0 @@ -[mypy] -pretty = True -show_error_codes = True - -# Exclude _files.py because mypy isn't smart enough to apply -# the correct type narrowing and as this is an internal module -# it's fine to just use Pyright. -exclude = ^(src/openai/_files\.py|_dev/.*\.py)$ - -strict_equality = True -implicit_reexport = True -check_untyped_defs = True -no_implicit_optional = True - -warn_return_any = True -warn_unreachable = True -warn_unused_configs = True - -# Turn these options off as it could cause conflicts -# with the Pyright options. -warn_unused_ignores = False -warn_redundant_casts = False - -disallow_any_generics = True -disallow_untyped_defs = True -disallow_untyped_calls = True -disallow_subclassing_any = True -disallow_incomplete_defs = True -disallow_untyped_decorators = True -cache_fine_grained = True - -# By default, mypy reports an error if you assign a value to the result -# of a function call that doesn't return anything. We do this in our test -# cases: -# ``` -# result = ... -# assert result is None -# ``` -# Changing this codegen to make mypy happy would increase complexity -# and would not be worth it. -disable_error_code = func-returns-value - -# https://github.com/python/mypy/issues/12162 -[mypy.overrides] -module = "black.files.*" -ignore_errors = true -ignore_missing_imports = true diff --git a/pyproject.toml b/pyproject.toml index 7828d0f0e1..d3a6a58a76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,33 +1,34 @@ [project] name = "openai" -version = "1.45.1" +version = "2.41.0" description = "The official Python library for the openai API" dynamic = ["readme"] license = "Apache-2.0" authors = [ { name = "OpenAI", email = "support@openai.com" }, ] + dependencies = [ - "httpx>=0.23.0, <1", - "pydantic>=1.9.0, <3", - "typing-extensions>=4.11, <5", - "anyio>=3.5.0, <5", - "distro>=1.7.0, <2", - "sniffio", - "cached-property; python_version < '3.8'", + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.11, <5", "typing-extensions>=4.14, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", "tqdm > 4", - "jiter>=0.4.0, <1", + "jiter>=0.10.0, <1", ] -requires-python = ">= 3.7.1" + +requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", @@ -37,22 +38,22 @@ classifiers = [ "License :: OSI Approved :: Apache Software License" ] -[project.optional-dependencies] -datalib = ["numpy >= 1", "pandas >= 1.2.3", "pandas-stubs >= 1.1.0.11"] - [project.urls] Homepage = "https://github.com/openai/openai-python" Repository = "https://github.com/openai/openai-python" -[project.scripts] -openai = "openai.cli:main" +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] +realtime = ["websockets >= 13, < 16"] +datalib = ["numpy >= 1", "pandas >= 1.2.3", "pandas-stubs >= 1.1.0.11"] +voice_helpers = ["sounddevice>=0.5.1", "numpy>=2.0.2"] [tool.rye] managed = true # version pins are in requirements-dev.lock dev-dependencies = [ - "pyright>=1.1.359", - "mypy", + "pyright==1.1.399", + "mypy==1.17", "respx", "pytest", "pytest-asyncio", @@ -62,11 +63,14 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", - "inline-snapshot >=0.7.0", + "inline-snapshot>=0.28.0", "azure-identity >=1.14.1", "types-tqdm > 4", "types-pyaudio > 0", - "trio >=0.22.2" + "trio >=0.22.2", + "nest_asyncio==1.6.0", + "pytest-xdist>=3.6.1", + "griffe>=1", ] [tool.rye.scripts] @@ -74,11 +78,11 @@ format = { chain = [ "format:ruff", "format:docs", "fix:ruff", + # run formatting again to fix any inconsistencies when imports are stripped + "format:ruff", ]} -"format:black" = "black ." -"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md" +"format:docs" = "bash -c 'python scripts/utils/ruffen-docs.py README.md $(find . -type f -name api.md)'" "format:ruff" = "ruff format" -"format:isort" = "isort ." "lint" = { chain = [ "check:ruff", @@ -94,12 +98,12 @@ typecheck = { chain = [ "typecheck:pyright", "typecheck:mypy" ]} -"typecheck:pyright" = "pyright" +"typecheck:pyright" = "scripts/run-pyright" "typecheck:verify-types" = "pyright --verifytypes openai --ignoreexternal" "typecheck:mypy" = "mypy ." [build-system] -requires = ["hatchling", "hatch-fancy-pypi-readme"] +requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"] build-backend = "hatchling.build" [tool.hatch.build] @@ -136,42 +140,109 @@ path = "README.md" pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)' replacement = '[\1](https://github.com/openai/openai-python/tree/main/\g<2>)' -[tool.black] -line-length = 120 -target-version = ["py37"] - [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--tb=short" +addopts = "--tb=short -n auto" xfail_strict = true asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" filterwarnings = [ "error" ] +[tool.inline-snapshot] +format-command="ruff format --stdin-filename {filename}" + [tool.pyright] # this enables practically every flag given by pyright. # there are a couple of flags that are still disabled by # default in strict mode as they are experimental and niche. typeCheckingMode = "strict" -pythonVersion = "3.7" +pythonVersion = "3.9" exclude = [ "_dev", ".venv", ".nox", + ".git", + + # uses inline `uv` script dependencies + # which means it can't be type checked + "examples/realtime/audio_util.py", + "examples/realtime/push_to_talk_app.py" ] reportImplicitOverride = true +reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false +[tool.mypy] +pretty = true +show_error_codes = true + +# Exclude _files.py because mypy isn't smart enough to apply +# the correct type narrowing and as this is an internal module +# it's fine to just use Pyright. +# +# We also exclude our `tests` as mypy doesn't always infer +# types correctly and Pyright will still catch any type errors. +# +# realtime examples use inline `uv` script dependencies +# which means it can't be type checked +exclude = [ + 'src/openai/_files.py', + '_dev/.*.py', + 'tests/.*', + 'src/openai/_utils/_logs.py', + 'examples/realtime/audio_util.py', + 'examples/realtime/push_to_talk_app.py', +] + +strict_equality = true +implicit_reexport = true +check_untyped_defs = true +no_implicit_optional = true + +warn_return_any = true +warn_unreachable = true +warn_unused_configs = true + +# Turn these options off as it could cause conflicts +# with the Pyright options. +warn_unused_ignores = false +warn_redundant_casts = false + +disallow_any_generics = true +disallow_untyped_defs = true +disallow_untyped_calls = true +disallow_subclassing_any = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +cache_fine_grained = true + +# By default, mypy reports an error if you assign a value to the result +# of a function call that doesn't return anything. We do this in our test +# cases: +# ``` +# result = ... +# assert result is None +# ``` +# Changing this codegen to make mypy happy would increase complexity +# and would not be worth it. +disable_error_code = "func-returns-value,overload-cannot-match" + +# https://github.com/python/mypy/issues/12162 +[[tool.mypy.overrides]] +module = "black.files.*" +ignore_errors = true +ignore_missing_imports = true [tool.ruff] line-length = 120 output-format = "grouped" -target-version = "py37" +target-version = "py38" [tool.ruff.format] docstring-code-format = true @@ -184,6 +255,8 @@ select = [ "B", # remove unused imports "F401", + # check for missing future annotations + "FA102", # bare except statements "E722", # unused arguments @@ -192,7 +265,7 @@ select = [ "T201", "T203", # misuse of typing.TYPE_CHECKING - "TCH004", + "TC004", # import rules "TID251", ] @@ -206,6 +279,8 @@ unfixable = [ "T203", ] +extend-safe-fixes = ["FA102"] + [tool.ruff.lint.flake8-tidy-imports.banned-api] "functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead" diff --git a/requirements-dev.lock b/requirements-dev.lock index a47de9656a..73312bcee1 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -7,182 +7,224 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. -annotated-types==0.6.0 +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.3 + # via httpx-aiohttp + # via openai +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 # via pydantic -anyio==4.1.0 +anyio==4.12.1 # via httpx # via openai -argcomplete==3.1.2 +argcomplete==3.6.3 # via nox -asttokens==2.4.1 +asttokens==3.0.1 # via inline-snapshot -attrs==23.1.0 +async-timeout==5.0.1 + # via aiohttp +attrs==25.4.0 + # via aiohttp + # via nox # via outcome - # via pytest # via trio -azure-core==1.30.1 +azure-core==1.36.0 # via azure-identity -azure-identity==1.15.0 -black==24.4.2 - # via inline-snapshot -certifi==2023.7.22 +azure-identity==1.25.1 +backports-asyncio-runner==1.2.0 + # via pytest-asyncio +certifi==2026.1.4 # via httpcore # via httpx # via requests -cffi==1.16.0 +cffi==2.0.0 # via cryptography -charset-normalizer==3.3.2 + # via sounddevice +charset-normalizer==3.4.4 # via requests -click==8.1.7 - # via black - # via inline-snapshot -colorlog==6.7.0 +colorama==0.4.6 + # via griffe +colorlog==6.10.1 # via nox -cryptography==42.0.7 +cryptography==46.0.3 # via azure-identity # via msal # via pyjwt -dirty-equals==0.6.0 -distlib==0.3.7 +dependency-groups==1.3.1 + # via nox +dirty-equals==0.11 +distlib==0.4.0 # via virtualenv -distro==1.8.0 +distro==1.9.0 # via openai -exceptiongroup==1.1.3 +exceptiongroup==1.3.1 # via anyio + # via pytest # via trio -executing==2.0.1 +execnet==2.1.2 + # via pytest-xdist +executing==2.2.1 # via inline-snapshot -filelock==3.12.4 +filelock==3.19.1 # via virtualenv -h11==0.14.0 +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +griffe==1.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx -httpx==0.25.2 +httpx==0.28.1 + # via httpx-aiohttp # via openai # via respx -idna==3.4 +httpx-aiohttp==0.1.12 + # via openai +humanize==4.13.0 + # via nox +idna==3.11 # via anyio # via httpx # via requests # via trio -importlib-metadata==7.0.0 -iniconfig==2.0.0 + # via yarl +importlib-metadata==8.7.1 +iniconfig==2.1.0 # via pytest -inline-snapshot==0.10.2 -jiter==0.5.0 +inline-snapshot==0.31.1 +jiter==0.12.0 # via openai markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -msal==1.29.0 +msal==1.34.0 # via azure-identity # via msal-extensions -msal-extensions==1.2.0 +msal-extensions==1.3.1 # via azure-identity -mypy==1.11.2 -mypy-extensions==1.0.0 - # via black +multidict==6.7.0 + # via aiohttp + # via yarl +mypy==1.17.0 +mypy-extensions==1.1.0 # via mypy -nodeenv==1.8.0 +nest-asyncio==1.6.0 +nodeenv==1.9.1 # via pyright -nox==2023.4.22 -numpy==1.26.3 +nox==2025.11.12 +numpy==2.0.2 # via openai # via pandas # via pandas-stubs outcome==1.3.0.post0 # via trio -packaging==23.2 - # via black +packaging==25.0 + # via dependency-groups # via nox # via pytest -pandas==2.1.4 +pandas==2.3.3 # via openai -pandas-stubs==2.1.4.231227 +pandas-stubs==2.2.2.240807 # via openai pathspec==0.12.1 - # via black -platformdirs==3.11.0 - # via black + # via mypy +platformdirs==4.4.0 # via virtualenv -pluggy==1.3.0 +pluggy==1.6.0 # via pytest -portalocker==2.8.2 - # via msal-extensions -py==1.11.0 - # via pytest -pycparser==2.22 +propcache==0.4.1 + # via aiohttp + # via yarl +pycparser==2.23 # via cffi -pydantic==2.7.1 +pydantic==2.12.5 # via openai -pydantic-core==2.18.2 +pydantic-core==2.41.5 # via pydantic -pygments==2.18.0 +pygments==2.19.2 + # via pytest # via rich -pyjwt==2.8.0 +pyjwt==2.10.1 # via msal -pyright==1.1.380 -pytest==7.1.1 +pyright==1.1.399 +pytest==8.4.2 + # via inline-snapshot # via pytest-asyncio -pytest-asyncio==0.21.1 -python-dateutil==2.8.2 + # via pytest-xdist +pytest-asyncio==1.2.0 +pytest-xdist==3.8.0 +python-dateutil==2.9.0.post0 # via pandas # via time-machine -pytz==2023.3.post1 - # via dirty-equals +pytz==2025.2 # via pandas -requests==2.31.0 +requests==2.32.5 # via azure-core # via msal -respx==0.20.2 -rich==13.7.1 +respx==0.22.0 +rich==14.2.0 # via inline-snapshot -ruff==0.6.5 -setuptools==68.2.2 - # via nodeenv -six==1.16.0 - # via asttokens - # via azure-core +ruff==0.14.7 +six==1.17.0 # via python-dateutil -sniffio==1.3.0 - # via anyio - # via httpx +sniffio==1.3.1 # via openai # via trio sortedcontainers==2.4.0 # via trio -time-machine==2.9.0 -toml==0.10.2 +sounddevice==0.5.3 + # via openai +time-machine==2.19.0 +tomli==2.4.0 + # via dependency-groups # via inline-snapshot -tomli==2.0.1 - # via black # via mypy + # via nox # via pytest -tqdm==4.66.1 +tqdm==4.67.1 # via openai -trio==0.22.2 -types-pyaudio==0.2.16.20240106 -types-pytz==2024.1.0.20240417 +trio==0.31.0 +types-pyaudio==0.2.16.20250801 +types-pytz==2025.2.0.20251108 # via pandas-stubs -types-toml==0.10.8.20240310 - # via inline-snapshot -types-tqdm==4.66.0.2 -typing-extensions==4.12.2 +types-requests==2.32.4.20250913 + # via types-tqdm +types-tqdm==4.67.0.20250809 +typing-extensions==4.15.0 + # via aiosignal + # via anyio # via azure-core - # via black + # via azure-identity + # via cryptography + # via exceptiongroup + # via multidict # via mypy # via openai # via pydantic # via pydantic-core -tzdata==2024.1 + # via pyright + # via pytest-asyncio + # via typing-inspection + # via virtualenv +typing-inspection==0.4.2 + # via pydantic +tzdata==2025.2 # via pandas -urllib3==2.2.1 +urllib3==2.5.0 # via requests -virtualenv==20.24.5 + # via types-requests +virtualenv==20.35.4 # via nox -zipp==3.17.0 +websockets==15.0.1 + # via openai +yarl==1.22.0 + # via aiohttp +zipp==3.23.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index de632aefbd..af6e4f99e8 100644 --- a/requirements.lock +++ b/requirements.lock @@ -7,60 +7,100 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. -annotated-types==0.6.0 +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.3 + # via httpx-aiohttp + # via openai +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 # via pydantic -anyio==4.1.0 +anyio==4.12.1 # via httpx # via openai -certifi==2023.7.22 +async-timeout==5.0.1 + # via aiohttp +attrs==25.4.0 + # via aiohttp +certifi==2026.1.4 # via httpcore # via httpx -distro==1.8.0 +cffi==2.0.0 + # via sounddevice +distro==1.9.0 # via openai -exceptiongroup==1.1.3 +exceptiongroup==1.3.1 # via anyio -h11==0.14.0 +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx -httpx==0.25.2 +httpx==0.28.1 + # via httpx-aiohttp + # via openai +httpx-aiohttp==0.1.12 # via openai -idna==3.4 +idna==3.11 # via anyio # via httpx -jiter==0.5.0 + # via yarl +jiter==0.12.0 # via openai -numpy==1.26.4 +multidict==6.7.0 + # via aiohttp + # via yarl +numpy==2.0.2 # via openai # via pandas # via pandas-stubs -pandas==2.2.2 +pandas==2.3.3 # via openai -pandas-stubs==2.2.1.240316 +pandas-stubs==2.2.2.240807 # via openai -pydantic==2.7.1 +propcache==0.4.1 + # via aiohttp + # via yarl +pycparser==2.23 + # via cffi +pydantic==2.12.5 # via openai -pydantic-core==2.18.2 +pydantic-core==2.41.5 # via pydantic python-dateutil==2.9.0.post0 # via pandas -pytz==2024.1 +pytz==2025.2 # via pandas -six==1.16.0 +six==1.17.0 # via python-dateutil -sniffio==1.3.0 - # via anyio - # via httpx +sniffio==1.3.1 # via openai -tqdm==4.66.1 +sounddevice==0.5.3 # via openai -types-pytz==2024.1.0.20240417 +tqdm==4.67.1 + # via openai +types-pytz==2025.2.0.20251108 # via pandas-stubs -typing-extensions==4.12.2 +typing-extensions==4.15.0 + # via aiosignal + # via anyio + # via exceptiongroup + # via multidict # via openai # via pydantic # via pydantic-core -tzdata==2024.1 + # via typing-inspection +typing-inspection==0.4.2 + # via pydantic +tzdata==2025.2 # via pandas +websockets==15.0.1 + # via openai +yarl==1.22.0 + # via aiohttp diff --git a/scripts/bootstrap b/scripts/bootstrap index 29df07e77b..3e25ebec99 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo } fi diff --git a/scripts/detect-breaking-changes b/scripts/detect-breaking-changes new file mode 100755 index 0000000000..25b6aa485f --- /dev/null +++ b/scripts/detect-breaking-changes @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Detecting breaking changes" + +TEST_PATHS=( + tests/api_resources + tests/test_client.py + tests/test_response.py + tests/test_legacy_response.py +) + +for PATHSPEC in "${TEST_PATHS[@]}"; do + # Try to check out previous versions of the test files + # with the current SDK. + git checkout "$1" -- "${PATHSPEC}" 2>/dev/null || true +done + +# Instead of running the tests, use the linter to check if an +# older test is no longer compatible with the latest SDK. +PYRIGHT_PROJECT=scripts/pyrightconfig.breaking-changes.json ./scripts/lint diff --git a/scripts/detect-breaking-changes.py b/scripts/detect-breaking-changes.py new file mode 100644 index 0000000000..3a30f3db2f --- /dev/null +++ b/scripts/detect-breaking-changes.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import sys +from typing import Iterator +from pathlib import Path + +import rich +import griffe +from rich.text import Text +from rich.style import Style + + +def public_members(obj: griffe.Object | griffe.Alias) -> dict[str, griffe.Object | griffe.Alias]: + if isinstance(obj, griffe.Alias): + # ignore imports for now, they're technically part of the public API + # but we don't have good preventative measures in place to prevent + # changing them + return {} + + return {name: value for name, value in obj.all_members.items() if not name.startswith("_")} + + +def find_breaking_changes( + new_obj: griffe.Object | griffe.Alias, + old_obj: griffe.Object | griffe.Alias, + *, + path: list[str], +) -> Iterator[Text | str]: + new_members = public_members(new_obj) + old_members = public_members(old_obj) + + for name, old_member in old_members.items(): + if isinstance(old_member, griffe.Alias) and len(path) > 2: + # ignore imports in `/types/` for now, they're technically part of the public API + # but we don't have good preventative measures in place to prevent changing them + continue + + new_member = new_members.get(name) + if new_member is None: + cls_name = old_member.__class__.__name__ + yield Text(f"({cls_name})", style=Style(color="rgb(119, 119, 119)")) + yield from [" " for _ in range(10 - len(cls_name))] + yield f" {'.'.join(path)}.{name}" + yield "\n" + continue + + yield from find_breaking_changes(new_member, old_member, path=[*path, name]) + + +def main() -> None: + try: + against_ref = sys.argv[1] + except IndexError as err: + raise RuntimeError("You must specify a base ref to run breaking change detection against") from err + + package = griffe.load( + "openai", + search_paths=[Path(__file__).parent.parent.joinpath("src")], + ) + old_package = griffe.load_git( + "openai", + ref=against_ref, + search_paths=["src"], + ) + assert isinstance(package, griffe.Module) + assert isinstance(old_package, griffe.Module) + + output = list(find_breaking_changes(package, old_package, path=["openai"])) + if output: + rich.print(Text("Breaking changes detected!", style=Style(color="rgb(165, 79, 87)"))) + rich.print() + + for text in output: + rich.print(text, end="") + + sys.exit(1) + + +main() diff --git a/scripts/lint b/scripts/lint index 64495ee345..275a2bf1a1 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,9 +4,13 @@ set -e cd "$(dirname "$0")/.." -echo "==> Running lints" -rye run lint +if [ "$1" = "--fix" ]; then + echo "==> Running lints with --fix" + rye run fix:ruff +else + echo "==> Running lints" + rye run lint +fi echo "==> Making sure it imports" rye run python -c 'import openai' - diff --git a/scripts/mock b/scripts/mock index d2814ae6a0..04d29019fc 100755 --- a/scripts/mock +++ b/scripts/mock @@ -19,23 +19,34 @@ fi echo "==> Starting mock server with URL ${URL}" -# Run prism mock on the given spec +# Run steady mock on the given spec if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log & + # Pre-install the package so the download doesn't eat into the startup timeout + npm exec --package=@stdy/cli@0.22.1 -- steady --version - # Wait for server to come online + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=brackets --validator-form-array-format=brackets --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & + + # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + attempts=0 + while ! curl --silent --fail "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1; do + if ! kill -0 $! 2>/dev/null; then + echo + cat .stdy.log + exit 1 + fi + attempts=$((attempts + 1)) + if [ "$attempts" -ge 300 ]; then + echo + echo "Timed out waiting for Steady server to start" + cat .stdy.log + exit 1 + fi echo -n "." sleep 0.1 done - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - echo else - npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=brackets --validator-form-array-format=brackets --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" fi diff --git a/scripts/pyrightconfig.breaking-changes.json b/scripts/pyrightconfig.breaking-changes.json new file mode 100644 index 0000000000..87bfd3bbc0 --- /dev/null +++ b/scripts/pyrightconfig.breaking-changes.json @@ -0,0 +1,4 @@ +{ + "extends": "../pyproject.toml", + "reportDeprecated": false +} diff --git a/scripts/run-pyright b/scripts/run-pyright new file mode 100755 index 0000000000..1c71ba0587 --- /dev/null +++ b/scripts/run-pyright @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$(dirname "$0")/.." + +CONFIG=${PYRIGHT_PROJECT:-pyproject.toml} +exec pyright -p "$CONFIG" "$@" diff --git a/scripts/test b/scripts/test index 4fa5698b8f..7b05e44fd9 100755 --- a/scripts/test +++ b/scripts/test @@ -9,8 +9,8 @@ GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 +function steady_is_running() { + curl --silent "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1 } kill_server_on_port() { @@ -25,7 +25,7 @@ function is_overriding_api_base_url() { [ -n "$TEST_API_BASE_URL" ] } -if ! is_overriding_api_base_url && ! prism_is_running ; then +if ! is_overriding_api_base_url && ! steady_is_running ; then # When we exit this script, make sure to kill the background mock server process trap 'kill_server_on_port 4010' EXIT @@ -36,22 +36,24 @@ fi if is_overriding_api_base_url ; then echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" +elif ! steady_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Steady server" echo -e "running against your OpenAPI spec." echo echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" + echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.22.1 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=brackets --validator-form-array-format=brackets --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" echo exit 1 else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo -e "${GREEN}✔ Mock steady server is running with your OpenAPI spec${NC}" echo fi +export DEFER_PYDANTIC_BUILD=false + echo "==> Running tests" rye run pytest "$@" diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py index 37b3d94f0f..0cf2bd2fd9 100644 --- a/scripts/utils/ruffen-docs.py +++ b/scripts/utils/ruffen-docs.py @@ -47,7 +47,7 @@ def _md_match(match: Match[str]) -> str: with _collect_error(match): code = format_code_block(code) code = textwrap.indent(code, match["indent"]) - return f'{match["before"]}{code}{match["after"]}' + return f"{match['before']}{code}{match['after']}" def _pycon_match(match: Match[str]) -> str: code = "" @@ -97,7 +97,7 @@ def finish_fragment() -> None: def _md_pycon_match(match: Match[str]) -> str: code = _pycon_match(match) code = textwrap.indent(code, match["indent"]) - return f'{match["before"]}{code}{match["after"]}' + return f"{match['before']}{code}{match['after']}" src = MD_RE.sub(_md_match, src) src = MD_PYCON_RE.sub(_md_pycon_match, src) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 0000000000..cd522975fc --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -exuo pipefail + +FILENAME=$(basename dist/*.whl) + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: binary/octet-stream" \ + --data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/openai-python/$SHA/$FILENAME'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi diff --git a/src/openai/__init__.py b/src/openai/__init__.py index 3c1ebb573d..3786d106cb 100644 --- a/src/openai/__init__.py +++ b/src/openai/__init__.py @@ -3,10 +3,11 @@ from __future__ import annotations import os as _os +import typing as _t from typing_extensions import override from . import types -from ._types import NOT_GIVEN, NoneType, NotGiven, Transport, ProxiesTypes +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given from ._utils import file_from_path from ._client import Client, OpenAI, Stream, Timeout, Transport, AsyncClient, AsyncOpenAI, AsyncStream, RequestOptions from ._models import BaseModel @@ -15,6 +16,7 @@ from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS from ._exceptions import ( APIError, + OAuthError, OpenAIError, ConflictError, NotFoundError, @@ -27,12 +29,17 @@ InternalServerError, PermissionDeniedError, LengthFinishReasonError, + WebSocketQueueFullError, UnprocessableEntityError, APIResponseValidationError, + InvalidWebhookSignatureError, ContentFilterFinishReasonError, + WebSocketConnectionClosedError, ) -from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient +from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient from ._utils._logs import setup_logging as _setup_logging +from ._legacy_response import HttpxBinaryResponseContent as HttpxBinaryResponseContent +from .types.websocket_reconnection import ReconnectingEvent, ReconnectingOverrides __all__ = [ "types", @@ -43,6 +50,9 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "not_given", + "Omit", + "omit", "OpenAIError", "APIError", "APIStatusError", @@ -51,6 +61,7 @@ "APIResponseValidationError", "BadRequestError", "AuthenticationError", + "OAuthError", "PermissionDeniedError", "NotFoundError", "ConflictError", @@ -59,6 +70,7 @@ "InternalServerError", "LengthFinishReasonError", "ContentFilterFinishReasonError", + "InvalidWebhookSignatureError", "Timeout", "RequestOptions", "Client", @@ -67,6 +79,8 @@ "AsyncStream", "OpenAI", "AsyncOpenAI", + "BedrockOpenAI", + "AsyncBedrockOpenAI", "file_from_path", "BaseModel", "DEFAULT_TIMEOUT", @@ -74,11 +88,20 @@ "DEFAULT_CONNECTION_LIMITS", "DefaultHttpxClient", "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", + "ReconnectingEvent", + "ReconnectingOverrides", + "WebSocketQueueFullError", + "WebSocketConnectionClosedError", ] -from .lib import azure as _azure, pydantic_function_tool as pydantic_function_tool +if not _t.TYPE_CHECKING: + from ._utils._resources_proxy import resources as resources + +from .lib import azure as _azure, bedrock as _bedrock, pydantic_function_tool as pydantic_function_tool from .version import VERSION as VERSION from .lib.azure import AzureOpenAI as AzureOpenAI, AsyncAzureOpenAI as AsyncAzureOpenAI +from .lib.bedrock import BedrockOpenAI as BedrockOpenAI, AsyncBedrockOpenAI as AsyncBedrockOpenAI from .lib._old_api import * from .lib.streaming import ( AssistantEventHandler as AssistantEventHandler, @@ -110,10 +133,14 @@ api_key: str | None = None +admin_api_key: str | None = None + organization: str | None = None project: str | None = None +webhook_secret: str | None = None + base_url: str | _httpx.URL | None = None timeout: float | Timeout | None = DEFAULT_TIMEOUT @@ -126,7 +153,7 @@ http_client: _httpx.Client | None = None -_ApiType = _te.Literal["openai", "azure"] +_ApiType = _te.Literal["openai", "azure", "amazon-bedrock"] api_type: _ApiType | None = _t.cast(_ApiType, _os.environ.get("OPENAI_API_TYPE")) @@ -138,6 +165,10 @@ azure_ad_token_provider: _azure.AzureADTokenProvider | None = None +_bedrock_api_key: str | None = None + +bedrock_token_provider: _bedrock.BedrockTokenProvider | None = None + class _ModuleClient(OpenAI): # Note: we have to use type: ignores here as overriding class members @@ -154,6 +185,17 @@ def api_key(self, value: str | None) -> None: # type: ignore api_key = value + @property # type: ignore + @override + def admin_api_key(self) -> str | None: + return admin_api_key + + @admin_api_key.setter # type: ignore + def admin_api_key(self, value: str | None) -> None: # type: ignore + global admin_api_key + + admin_api_key = value + @property # type: ignore @override def organization(self) -> str | None: @@ -176,6 +218,17 @@ def project(self, value: str | None) -> None: # type: ignore project = value + @property # type: ignore + @override + def webhook_secret(self) -> str | None: + return webhook_secret + + @webhook_secret.setter # type: ignore + def webhook_secret(self, value: str | None) -> None: # type: ignore + global webhook_secret + + webhook_secret = value + @property @override def base_url(self) -> _httpx.URL: @@ -248,10 +301,23 @@ class _AzureModuleClient(_ModuleClient, AzureOpenAI): # type: ignore ... +class _BedrockModuleClient(_ModuleClient, BedrockOpenAI): # type: ignore + @property # type: ignore + @override + def api_key(self) -> str | None: + return api_key if api_key is not None else _bedrock_api_key + + @api_key.setter # type: ignore + def api_key(self, value: str | None) -> None: # type: ignore + global _bedrock_api_key + + _bedrock_api_key = value + + class _AmbiguousModuleClientUsageError(OpenAIError): def __init__(self) -> None: super().__init__( - "Ambiguous use of module client; please set `openai.api_type` or the `OPENAI_API_TYPE` environment variable to `openai` or `azure`" + "Ambiguous use of module client; please set `openai.api_type` or the `OPENAI_API_TYPE` environment variable to `openai`, `azure`, or `amazon-bedrock`" ) @@ -324,16 +390,35 @@ def _load_client() -> OpenAI: # type: ignore[reportUnusedFunction] ) return _client + if api_type == "amazon-bedrock": + _client = _BedrockModuleClient( # type: ignore + api_key=api_key, + bedrock_token_provider=bedrock_token_provider, + organization=organization, + project=project, + webhook_secret=webhook_secret, + base_url=base_url, + timeout=timeout, + max_retries=max_retries, + default_headers=default_headers, + default_query=default_query, + http_client=http_client, + ) + return _client + _client = _ModuleClient( api_key=api_key, + admin_api_key=admin_api_key, organization=organization, project=project, + webhook_secret=webhook_secret, base_url=base_url, timeout=timeout, max_retries=max_retries, default_headers=default_headers, default_query=default_query, http_client=http_client, + _enforce_credentials=False, ) return _client @@ -349,13 +434,24 @@ def _reset_client() -> None: # type: ignore[reportUnusedFunction] from ._module_client import ( beta as beta, chat as chat, + admin as admin, audio as audio, + evals as evals, files as files, images as images, models as models, + skills as skills, + videos as videos, batches as batches, + uploads as uploads, + realtime as realtime, + webhooks as webhooks, + responses as responses, + containers as containers, embeddings as embeddings, completions as completions, fine_tuning as fine_tuning, moderations as moderations, + conversations as conversations, + vector_stores as vector_stores, ) diff --git a/src/openai/__main__.py b/src/openai/__main__.py deleted file mode 100644 index 4e28416e10..0000000000 --- a/src/openai/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .cli import main - -main() diff --git a/src/openai/_base_client.py b/src/openai/_base_client.py index f374449dbc..216b36aabd 100644 --- a/src/openai/_base_client.py +++ b/src/openai/_base_client.py @@ -30,20 +30,19 @@ cast, overload, ) -from typing_extensions import Literal, override, get_origin +from typing_extensions import Unpack, Literal, override, get_origin import anyio import httpx import distro import pydantic -from httpx import URL, Limits +from httpx import URL from pydantic import PrivateAttr from . import _exceptions from ._qs import Querystring from ._files import to_httpx_files, async_to_httpx_files from ._types import ( - NOT_GIVEN, Body, Omit, Query, @@ -51,20 +50,20 @@ Timeout, NotGiven, ResponseT, - Transport, AnyMapping, PostParser, - ProxiesTypes, + BinaryTypes, RequestFiles, HttpxSendArgs, - AsyncTransport, RequestOptions, + AsyncBinaryTypes, HttpxRequestFiles, ModelBuilderProtocol, + not_given, ) -from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import model_copy, model_dump -from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type +from ._utils import SensitiveHeadersFilter, is_dict, is_list, asyncify, is_given, lru_cache, is_mapping +from ._compat import PYDANTIC_V1, model_copy, model_dump +from ._models import GenericModel, SecurityOptions, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, BaseAPIResponse, @@ -82,14 +81,17 @@ ) from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder from ._exceptions import ( + OpenAIError, APIStatusError, APITimeoutError, APIConnectionError, APIResponseValidationError, ) +from ._utils._json import openapi_dumps from ._legacy_response import LegacyAPIResponse log: logging.Logger = logging.getLogger(__name__) +log.addFilter(SensitiveHeadersFilter()) # TODO: make base page type vars covariant SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]") @@ -103,7 +105,11 @@ _AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) if TYPE_CHECKING: - from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + from httpx._config import ( + DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] + ) + + HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG else: try: from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT @@ -120,6 +126,7 @@ class PageInfo: url: URL | NotGiven params: Query | NotGiven + json: Body | NotGiven @overload def __init__( @@ -135,15 +142,32 @@ def __init__( params: Query, ) -> None: ... + @overload + def __init__( + self, + *, + json: Body, + ) -> None: ... + def __init__( self, *, - url: URL | NotGiven = NOT_GIVEN, - params: Query | NotGiven = NOT_GIVEN, + url: URL | NotGiven = not_given, + json: Body | NotGiven = not_given, + params: Query | NotGiven = not_given, ) -> None: self.url = url + self.json = json self.params = params + @override + def __repr__(self) -> str: + if self.url: + return f"{self.__class__.__name__}(url={self.url})" + if self.json: + return f"{self.__class__.__name__}(json={self.json})" + return f"{self.__class__.__name__}(params={self.params})" + class BasePage(GenericModel, Generic[_T]): """ @@ -190,6 +214,19 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: options.url = str(url) return options + if not isinstance(info.json, NotGiven): + if not is_mapping(info.json): + raise TypeError("Pagination is only supported with mappings") + + if not options.json_data: + options.json_data = {**info.json} + else: + if not is_mapping(options.json_data): + raise TypeError("Pagination is only supported with mappings") + + options.json_data = {**options.json_data, **info.json} + return options + raise ValueError("Unexpected PageInfo state") @@ -202,6 +239,9 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -287,6 +327,9 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -326,9 +369,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): _base_url: URL max_retries: int timeout: Union[float, Timeout, None] - _limits: httpx.Limits - _proxies: ProxiesTypes | None - _transport: Transport | AsyncTransport | None _strict_response_validation: bool _idempotency_header: str | None _default_stream_cls: type[_DefaultStreamT] | None = None @@ -341,9 +381,6 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None = DEFAULT_TIMEOUT, - limits: httpx.Limits, - transport: Transport | AsyncTransport | None, - proxies: ProxiesTypes | None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: @@ -351,9 +388,6 @@ def __init__( self._base_url = self._enforce_trailing_slash(URL(base_url)) self.max_retries = max_retries self.timeout = timeout - self._limits = limits - self._proxies = proxies - self._transport = transport self._custom_headers = custom_headers or {} self._custom_query = custom_query or {} self._strict_response_validation = _strict_response_validation @@ -401,24 +435,47 @@ def _make_status_error( ) -> _exceptions.APIStatusError: raise NotImplementedError() - def _remaining_retries( + def _auth_headers( self, - remaining_retries: Optional[int], - options: FinalRequestOptions, - ) -> int: - return remaining_retries if remaining_retries is not None else options.get_max_retries(self.max_retries) + security: SecurityOptions, # noqa: ARG002 + ) -> dict[str, str]: + return {} + + def _auth_query( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> dict[str, str]: + return {} + + def _custom_auth( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> httpx.Auth | None: + return None - def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers: + def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers: custom_headers = options.headers or {} - headers_dict = _merge_mappings(self.default_headers, custom_headers) + headers_dict = _merge_mappings({**self._auth_headers(options.security), **self.default_headers}, custom_headers) self._validate_headers(headers_dict, custom_headers) # headers are case-insensitive while dictionaries are not. headers = httpx.Headers(headers_dict) idempotency_header = self._idempotency_header - if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: - headers[idempotency_header] = options.idempotency_key or self._idempotency_key() + if idempotency_header and options.idempotency_key and idempotency_header not in headers: + headers[idempotency_header] = options.idempotency_key + + # Don't set these headers if they were already set or removed by the caller. We check + # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case. + lower_custom_headers = [header.lower() for header in custom_headers] + if "x-stainless-retry-count" not in lower_custom_headers: + headers["x-stainless-retry-count"] = str(retries_taken) + if "x-stainless-read-timeout" not in lower_custom_headers: + timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout + if isinstance(timeout, Timeout): + timeout = timeout.read + if timeout is not None: + headers["x-stainless-read-timeout"] = str(timeout) return headers @@ -441,10 +498,23 @@ def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder: def _build_request( self, options: FinalRequestOptions, + *, + retries_taken: int = 0, ) -> httpx.Request: if log.isEnabledFor(logging.DEBUG): - log.debug("Request options: %s", model_dump(options, exclude_unset=True)) - + log.debug( + "Request options: %s", + model_dump( + options, + exclude_unset=True, + # Pydantic v1 can't dump every type we support in content, so we exclude it for now. + exclude={ + "content", + } + if PYDANTIC_V1 + else {}, + ), + ) kwargs: dict[str, Any] = {} json_data = options.json_data @@ -456,8 +526,8 @@ def _build_request( else: raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`") - headers = self._build_headers(options) - params = _merge_mappings(self.default_query, options.params) + headers = self._build_headers(options, retries_taken=retries_taken) + params = _merge_mappings({**self._auth_query(options.security), **self.default_query}, options.params) content_type = headers.get("Content-Type") files = options.files @@ -490,19 +560,46 @@ def _build_request( if not files: files = cast(HttpxRequestFiles, ForceMultipartDict()) + prepared_url = self._prepare_url(options.url) + # preserve hard-coded query params from the url + if params and prepared_url.query: + params = {**dict(prepared_url.params.items()), **params} + prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0]) + if "_" in prepared_url.host: + # work around https://github.com/encode/httpx/discussions/2880 + kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + if options.content is not None and json_data is not None: + raise TypeError("Passing both `content` and `json_data` is not supported") + if options.content is not None and files is not None: + raise TypeError("Passing both `content` and `files` is not supported") + if options.content is not None: + kwargs["content"] = options.content + elif isinstance(json_data, bytes): + kwargs["content"] = json_data + elif not files: + # Don't set content when JSON is sent as multipart/form-data, + # since httpx's content param overrides other body arguments + kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + # TODO: report this error to httpx return self._client.build_request( # pyright: ignore[reportUnknownMemberType] headers=headers, timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout, method=options.method, - url=self._prepare_url(options.url), + url=prepared_url, # the `Query` type that we use is incompatible with qs' # `Params` type as it needs to be typed as `Mapping[str, object]` # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data, - files=files, **kwargs, ) @@ -546,7 +643,7 @@ def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalReques # we internally support defining a temporary header to override the # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` # see _response.py for implementation details - override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN) + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given) if is_given(override_cast_to): options.headers = headers return cast(Type[ResponseT], override_cast_to) @@ -599,7 +696,6 @@ def default_headers(self) -> dict[str, str | Omit]: "Content-Type": "application/json", "User-Agent": self.user_agent, **self.platform_headers(), - **self.auth_headers, **self._custom_headers, } @@ -685,7 +781,8 @@ def _calculate_retry_timeout( if retry_after is not None and 0 < retry_after <= 60: return retry_after - nb_retries = max_retries - remaining_retries + # Also cap retry count to 1000 to avoid any potential overflows with `pow` + nb_retries = min(max_retries - remaining_retries, 1000) # Apply exponential backoff, but not more than the max. sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY) @@ -756,6 +853,9 @@ def __init__(self, **kwargs: Any) -> None: class SyncHttpxClientWrapper(DefaultHttpxClient): def __del__(self) -> None: + if self.is_closed: + return + try: self.close() except Exception: @@ -772,44 +872,12 @@ def __init__( version: str, base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: Transport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, _strict_response_validation: bool, ) -> None: - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -830,12 +898,9 @@ def __init__( super().__init__( version=version, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, base_url=base_url, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -845,10 +910,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, - limits=limits, - follow_redirects=True, ) def is_closed(self) -> bool: @@ -893,12 +954,20 @@ def _prepare_request( """ return None + def _send_request( + self, + request: httpx.Request, + *, + stream: bool, + **kwargs: Unpack[HttpxSendArgs], + ) -> httpx.Response: + return self._client.send(request, stream=stream, **kwargs) + @overload def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[True], stream_cls: Type[_StreamT], @@ -909,7 +978,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[False] = False, ) -> ResponseT: ... @@ -919,7 +987,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: Type[_StreamT] | None = None, @@ -929,157 +996,140 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: - return self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - remaining_retries=remaining_retries, - ) + cast_to = self._maybe_override_cast_to(cast_to, options) - def _request( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - remaining_retries: int | None, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() - cast_to = self._maybe_override_cast_to(cast_to, options) - options = self._prepare_options(options) + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - retries = self._remaining_retries(remaining_retries, options) - request = self._build_request(options) - self._prepare_request(request) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = self._prepare_options(options) - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) - log.debug("Sending HTTP Request: %s %s", request.method, request.url) + kwargs: HttpxSendArgs = {} + custom_auth = self._custom_auth(options.security) + if custom_auth is not None: + kwargs["auth"] = custom_auth - try: - response = self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) - - if retries > 0: - return self._retry_request( - input_options, - cast_to, - retries, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - if retries > 0: - return self._retry_request( - input_options, - cast_to, - retries, - stream=stream, - stream_cls=stream_cls, - response_headers=None, + response = None + try: + response = self._send_request( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except OpenAIError as err: + # Propagate OpenAIErrors as-is, without retrying or wrapping in APIConnectionError + raise err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) + log.debug("request_id: %s", response.headers.get("x-request-id")) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err - - log.debug( - 'HTTP Response: %s %s "%i %s" %s', - request.method, - request.url, - response.status_code, - response.reason_phrase, - response.headers, - ) - log.debug("request_id: %s", response.headers.get("x-request-id")) + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + err.response.close() + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if retries > 0 and self._should_retry(err.response): - err.response.close() - return self._retry_request( - input_options, - cast_to, - retries, - err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + err.response.read() - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - err.response.read() + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return self._process_response( cast_to=cast_to, options=options, response=response, stream=stream, stream_cls=stream_cls, - retries_taken=options.get_max_retries(self.max_retries) - retries, + retries_taken=retries_taken, ) - def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - remaining_retries: int, - response_headers: httpx.Headers | None, - *, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: - remaining = remaining_retries - 1 - if remaining == 1: + def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken + if remaining_retries == 1: log.debug("1 retry left") else: - log.debug("%i retries left", remaining) + log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) - # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a - # different thread if necessary. time.sleep(timeout) - return self._request( - options=options, - cast_to=cast_to, - remaining_retries=remaining, - stream=stream, - stream_cls=stream_cls, - ) - def _process_response( self, *, @@ -1106,7 +1156,14 @@ def _process_response( origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, APIResponse): raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") @@ -1212,6 +1269,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[False] = False, @@ -1224,6 +1282,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[True], @@ -1237,6 +1296,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool, @@ -1249,13 +1309,25 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) @@ -1265,9 +1337,24 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options + ) return self.request(cast_to, opts) def put( @@ -1276,11 +1363,23 @@ def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return self.request(cast_to, opts) @@ -1290,9 +1389,19 @@ def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return self.request(cast_to, opts) def get_api_list( @@ -1317,6 +1426,24 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + if TYPE_CHECKING: DefaultAsyncHttpxClient = httpx.AsyncClient """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK @@ -1325,12 +1452,19 @@ def __init__(self, **kwargs: Any) -> None: This is useful because overriding the `http_client` with your own instance of `httpx.AsyncClient` will result in httpx's defaults being used, not ours. """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" else: DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): def __del__(self) -> None: + if self.is_closed: + return + try: # TODO(someday): support non asyncio runtimes here asyncio.get_running_loop().create_task(self.aclose()) @@ -1349,43 +1483,11 @@ def __init__( base_url: str | URL, _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: AsyncTransport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -1407,11 +1509,8 @@ def __init__( super().__init__( version=version, base_url=base_url, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -1421,10 +1520,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, - limits=limits, - follow_redirects=True, ) def is_closed(self) -> bool: @@ -1466,6 +1561,15 @@ async def _prepare_request( """ return None + async def _send_request( + self, + request: httpx.Request, + *, + stream: bool, + **kwargs: Unpack[HttpxSendArgs], + ) -> httpx.Response: + return await self._client.send(request, stream=stream, **kwargs) + @overload async def request( self, @@ -1473,7 +1577,6 @@ async def request( options: FinalRequestOptions, *, stream: Literal[False] = False, - remaining_retries: Optional[int] = None, ) -> ResponseT: ... @overload @@ -1484,7 +1587,6 @@ async def request( *, stream: Literal[True], stream_cls: type[_AsyncStreamT], - remaining_retries: Optional[int] = None, ) -> _AsyncStreamT: ... @overload @@ -1495,7 +1597,6 @@ async def request( *, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, ) -> ResponseT | _AsyncStreamT: ... async def request( @@ -1505,149 +1606,141 @@ async def request( *, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, - ) -> ResponseT | _AsyncStreamT: - return await self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - remaining_retries=remaining_retries, - ) - - async def _request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - remaining_retries: int | None, ) -> ResponseT | _AsyncStreamT: if self._platform is None: # `get_platform` can make blocking IO calls so we # execute it earlier while we are in an async context self._platform = await asyncify(get_platform)() + cast_to = self._maybe_override_cast_to(cast_to, options) + # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() - cast_to = self._maybe_override_cast_to(cast_to, options) - options = await self._prepare_options(options) + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - retries = self._remaining_retries(remaining_retries, options) - request = self._build_request(options) - await self._prepare_request(request) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) - try: - response = await self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) - - if retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects - if retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err + response = None + try: + response = await self._send_request( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, + ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except OpenAIError as err: + # Propagate OpenAIErrors as-is, without retrying or wrapping in APIConnectionError + raise err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) + log.debug("request_id: %s", response.headers.get("x-request-id")) - log.debug( - 'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase - ) + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + await err.response.aclose() + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if retries > 0 and self._should_retry(err.response): - await err.response.aclose() - return await self._retry_request( - input_options, - cast_to, - retries, - err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + await err.response.aread() - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - await err.response.aread() + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return await self._process_response( cast_to=cast_to, options=options, response=response, stream=stream, stream_cls=stream_cls, - retries_taken=options.get_max_retries(self.max_retries) - retries, + retries_taken=retries_taken, ) - async def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - remaining_retries: int, - response_headers: httpx.Headers | None, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - ) -> ResponseT | _AsyncStreamT: - remaining = remaining_retries - 1 - if remaining == 1: + async def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken + if remaining_retries == 1: log.debug("1 retry left") else: - log.debug("%i retries left", remaining) + log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) await anyio.sleep(timeout) - return await self._request( - options=options, - cast_to=cast_to, - remaining_retries=remaining, - stream=stream, - stream_cls=stream_cls, - ) - async def _process_response( self, *, @@ -1674,7 +1767,14 @@ async def _process_response( origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, AsyncAPIResponse): raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") @@ -1768,6 +1868,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[False] = False, @@ -1780,6 +1881,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[True], @@ -1793,6 +1895,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool, @@ -1805,13 +1908,25 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, ) -> ResponseT | _AsyncStreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) @@ -1821,9 +1936,29 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="patch", + url=path, + json_data=body, + content=content, + files=await async_to_httpx_files(files), + **options, + ) return await self.request(cast_to, opts) async def put( @@ -1832,11 +1967,23 @@ async def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts) @@ -1846,9 +1993,19 @@ async def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return await self.request(cast_to, opts) def get_api_list( @@ -1872,8 +2029,10 @@ def make_request_options( extra_query: Query | None = None, extra_body: Body | None = None, idempotency_key: str | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - post_parser: PostParser | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + post_parser: PostParser | NotGiven = not_given, + security: SecurityOptions | None = None, + synthesize_event_and_data: bool | None = None, ) -> RequestOptions: """Create a dict of type RequestOptions without keys of NotGiven values.""" options: RequestOptions = {} @@ -1899,6 +2058,12 @@ def make_request_options( # internal options["post_parser"] = post_parser # type: ignore + if security is not None: + options["security"] = security + + if synthesize_event_and_data is not None: + options["synthesize_event_and_data"] = synthesize_event_and_data + return options diff --git a/src/openai/_client.py b/src/openai/_client.py index d3ee6cf0f1..499a62dfe5 100644 --- a/src/openai/_client.py +++ b/src/openai/_client.py @@ -3,27 +3,33 @@ from __future__ import annotations import os -from typing import Any, Union, Mapping -from typing_extensions import Self, override +from typing import TYPE_CHECKING, Any, Mapping, Callable, Awaitable +from typing_extensions import Self, Unpack, override import httpx -from . import resources, _exceptions +from . import _exceptions from ._qs import Querystring +from .auth import WorkloadIdentity, WorkloadIdentityAuth from ._types import ( - NOT_GIVEN, Omit, + Headers, Timeout, NotGiven, Transport, ProxiesTypes, + HttpxSendArgs, RequestOptions, + not_given, ) from ._utils import ( is_given, is_mapping, + is_mapping_t, get_async_library, ) +from ._compat import cached_property +from ._models import SecurityOptions, FinalRequestOptions from ._version import __version__ from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import OpenAIError, APIStatusError @@ -33,48 +39,98 @@ AsyncAPIClient, ) -__all__ = [ - "Timeout", - "Transport", - "ProxiesTypes", - "RequestOptions", - "resources", - "OpenAI", - "AsyncOpenAI", - "Client", - "AsyncClient", -] +if TYPE_CHECKING: + from .resources import ( + beta, + chat, + admin, + audio, + evals, + files, + images, + models, + skills, + videos, + batches, + uploads, + realtime, + responses, + containers, + embeddings, + completions, + fine_tuning, + moderations, + conversations, + vector_stores, + ) + from .resources.files import Files, AsyncFiles + from .resources.images import Images, AsyncImages + from .resources.models import Models, AsyncModels + from .resources.videos import Videos, AsyncVideos + from .resources.batches import Batches, AsyncBatches + from .resources.beta.beta import Beta, AsyncBeta + from .resources.chat.chat import Chat, AsyncChat + from .resources.embeddings import Embeddings, AsyncEmbeddings + from .resources.admin.admin import Admin, AsyncAdmin + from .resources.audio.audio import Audio, AsyncAudio + from .resources.completions import Completions, AsyncCompletions + from .resources.evals.evals import Evals, AsyncEvals + from .resources.moderations import Moderations, AsyncModerations + from .resources.skills.skills import Skills, AsyncSkills + from .resources.uploads.uploads import Uploads, AsyncUploads + from .resources.realtime.realtime import Realtime, AsyncRealtime + from .resources.webhooks.webhooks import Webhooks, AsyncWebhooks + from .resources.responses.responses import Responses, AsyncResponses + from .resources.containers.containers import Containers, AsyncContainers + from .resources.fine_tuning.fine_tuning import FineTuning, AsyncFineTuning + from .resources.conversations.conversations import Conversations, AsyncConversations + from .resources.vector_stores.vector_stores import VectorStores, AsyncVectorStores + +__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "OpenAI", "AsyncOpenAI", "Client", "AsyncClient"] + +WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER = "workload-identity-auth" + + +def _has_header(headers: Headers, header: str) -> bool: + header = header.lower() + return any(key.lower() == header for key in headers) + + +def _has_omitted_header(headers: Headers, header: str) -> bool: + header = header.lower() + return any(key.lower() == header and isinstance(value, Omit) for key, value in headers.items()) class OpenAI(SyncAPIClient): - completions: resources.Completions - chat: resources.Chat - embeddings: resources.Embeddings - files: resources.Files - images: resources.Images - audio: resources.Audio - moderations: resources.Moderations - models: resources.Models - fine_tuning: resources.FineTuning - beta: resources.Beta - batches: resources.Batches - uploads: resources.Uploads - with_raw_response: OpenAIWithRawResponse - with_streaming_response: OpenAIWithStreamedResponse - # client options api_key: str + admin_api_key: str | None + workload_identity: WorkloadIdentity | None organization: str | None project: str | None + webhook_secret: str | None + _workload_identity_auth: WorkloadIdentityAuth | None + + websocket_base_url: str | httpx.URL | None + """Base URL for WebSocket connections. + + If not specified, the default base URL will be used, with 'wss://' replacing the + 'http://' or 'https://' scheme. For example: 'http://example.com' becomes + 'wss://example.com' + """ def __init__( self, *, - api_key: str | None = None, + api_key: str | Callable[[], str] | None = None, + admin_api_key: str | None = None, + workload_identity: WorkloadIdentity | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, base_url: str | httpx.URL | None = None, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + websocket_base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -91,21 +147,53 @@ def __init__( # outlining your use-case to help us decide if it should be # part of our public interface in the future. _strict_response_validation: bool = False, + _enforce_credentials: bool = True, ) -> None: - """Construct a new synchronous openai client instance. + """Construct a new synchronous OpenAI client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `api_key` from `OPENAI_API_KEY` + - `admin_api_key` from `OPENAI_ADMIN_KEY` - `organization` from `OPENAI_ORG_ID` - `project` from `OPENAI_PROJECT_ID` + - `webhook_secret` from `OPENAI_WEBHOOK_SECRET` """ - if api_key is None: - api_key = os.environ.get("OPENAI_API_KEY") - if api_key is None: + if api_key is not None and api_key != WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER and workload_identity is not None: + raise OpenAIError("The `api_key` and `workload_identity` arguments are mutually exclusive") + + self.workload_identity = workload_identity + + if workload_identity is not None: + self.api_key = WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER + self._api_key_provider = None + self._workload_identity_auth = WorkloadIdentityAuth( + workload_identity=workload_identity, + ) + else: + if api_key is None: + api_key = os.environ.get("OPENAI_API_KEY") + if callable(api_key): + self.api_key = "" + self._api_key_provider: Callable[[], str] | None = api_key # type: ignore[no-redef] + else: + self.api_key = api_key or "" + self._api_key_provider = None + self._workload_identity_auth = None + + if admin_api_key is None: + admin_api_key = os.environ.get("OPENAI_ADMIN_KEY") + self.admin_api_key = admin_api_key + + if ( + _enforce_credentials + and not self.api_key + and self._api_key_provider is None + and workload_identity is None + and self.admin_api_key is None + ): raise OpenAIError( - "The api_key client option must be set either by passing api_key to the client or by setting the OPENAI_API_KEY environment variable" + "Missing credentials. Please pass an `api_key`, `workload_identity`, `admin_api_key`, or set the `OPENAI_API_KEY` or `OPENAI_ADMIN_KEY` environment variable." ) - self.api_key = api_key if organization is None: organization = os.environ.get("OPENAI_ORG_ID") @@ -115,11 +203,26 @@ def __init__( project = os.environ.get("OPENAI_PROJECT_ID") self.project = project + if webhook_secret is None: + webhook_secret = os.environ.get("OPENAI_WEBHOOK_SECRET") + self.webhook_secret = webhook_secret + + self.websocket_base_url = websocket_base_url + if base_url is None: base_url = os.environ.get("OPENAI_BASE_URL") if base_url is None: base_url = f"https://api.openai.com/v1" + custom_headers_env = os.environ.get("OPENAI_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, @@ -133,32 +236,243 @@ def __init__( self._default_stream_cls = Stream - self.completions = resources.Completions(self) - self.chat = resources.Chat(self) - self.embeddings = resources.Embeddings(self) - self.files = resources.Files(self) - self.images = resources.Images(self) - self.audio = resources.Audio(self) - self.moderations = resources.Moderations(self) - self.models = resources.Models(self) - self.fine_tuning = resources.FineTuning(self) - self.beta = resources.Beta(self) - self.batches = resources.Batches(self) - self.uploads = resources.Uploads(self) - self.with_raw_response = OpenAIWithRawResponse(self) - self.with_streaming_response = OpenAIWithStreamedResponse(self) + @cached_property + def completions(self) -> Completions: + """ + Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + """ + from .resources.completions import Completions + + return Completions(self) + + @cached_property + def chat(self) -> Chat: + from .resources.chat import Chat + + return Chat(self) + + @cached_property + def embeddings(self) -> Embeddings: + """ + Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + """ + from .resources.embeddings import Embeddings + + return Embeddings(self) + + @cached_property + def files(self) -> Files: + """ + Files are used to upload documents that can be used with features like Assistants and Fine-tuning. + """ + from .resources.files import Files + + return Files(self) + + @cached_property + def images(self) -> Images: + """Given a prompt and/or an input image, the model will generate a new image.""" + from .resources.images import Images + + return Images(self) + + @cached_property + def audio(self) -> Audio: + from .resources.audio import Audio + + return Audio(self) + + @cached_property + def moderations(self) -> Moderations: + """ + Given text and/or image inputs, classifies if those inputs are potentially harmful. + """ + from .resources.moderations import Moderations + + return Moderations(self) + + @cached_property + def models(self) -> Models: + """List and describe the various models available in the API.""" + from .resources.models import Models + + return Models(self) + + @cached_property + def fine_tuning(self) -> FineTuning: + from .resources.fine_tuning import FineTuning + + return FineTuning(self) + + @cached_property + def vector_stores(self) -> VectorStores: + from .resources.vector_stores import VectorStores + + return VectorStores(self) + + @cached_property + def webhooks(self) -> Webhooks: + from .resources.webhooks import Webhooks + + return Webhooks(self) + + @cached_property + def beta(self) -> Beta: + from .resources.beta import Beta + + return Beta(self) + + @cached_property + def batches(self) -> Batches: + """Create large batches of API requests to run asynchronously.""" + from .resources.batches import Batches + + return Batches(self) + + @cached_property + def uploads(self) -> Uploads: + """Use Uploads to upload large files in multiple parts.""" + from .resources.uploads import Uploads + + return Uploads(self) + + @cached_property + def admin(self) -> Admin: + from .resources.admin import Admin + + return Admin(self) + + @cached_property + def responses(self) -> Responses: + from .resources.responses import Responses + + return Responses(self) + + @cached_property + def realtime(self) -> Realtime: + from .resources.realtime import Realtime + + return Realtime(self) + + @cached_property + def conversations(self) -> Conversations: + """Manage conversations and conversation items.""" + from .resources.conversations import Conversations + + return Conversations(self) + + @cached_property + def evals(self) -> Evals: + """Manage and run evals in the OpenAI platform.""" + from .resources.evals import Evals + + return Evals(self) + + @cached_property + def containers(self) -> Containers: + from .resources.containers import Containers + + return Containers(self) + + @cached_property + def skills(self) -> Skills: + from .resources.skills import Skills + + return Skills(self) + + @cached_property + def videos(self) -> Videos: + from .resources.videos import Videos + + return Videos(self) + + @cached_property + def with_raw_response(self) -> OpenAIWithRawResponse: + return OpenAIWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> OpenAIWithStreamedResponse: + return OpenAIWithStreamedResponse(self) @property @override def qs(self) -> Querystring: return Querystring(array_format="brackets") + def _send_with_auth_retry( + self, + request: httpx.Request, + *, + stream: bool, + retried: bool = False, + **kwargs: Unpack[HttpxSendArgs], + ) -> httpx.Response: + used_workload_identity_auth = False + + if self._workload_identity_auth is not None: + authorization = request.headers.get("Authorization") + if authorization == f"Bearer {WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER}": + request.headers["Authorization"] = f"Bearer {self._workload_identity_auth.get_token()}" + used_workload_identity_auth = True + + response = super()._send_request(request, stream=stream, **kwargs) + if ( + response.status_code == 401 + and self._workload_identity_auth is not None + and used_workload_identity_auth + and not retried + ): + response.close() + self._workload_identity_auth.invalidate_token() + request.headers["Authorization"] = f"Bearer {self._workload_identity_auth.get_token()}" + return self._send_with_auth_retry(request, stream=stream, retried=True, **kwargs) + + return response + + @override + def _send_request( + self, + request: httpx.Request, + *, + stream: bool, + **kwargs: Unpack[HttpxSendArgs], + ) -> httpx.Response: + return self._send_with_auth_retry(request, stream=stream, **kwargs) + + @override + def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: + if security.get("bearer_auth", False): + headers = self._bearer_auth + if headers: + return headers + + if security.get("admin_api_key_auth", False): + return self._admin_api_key_auth + + return {} + + @property + def _bearer_auth(self) -> dict[str, str]: + api_key = self.api_key + if not api_key: + return {} + return {"Authorization": f"Bearer {api_key}"} + @property @override def auth_headers(self) -> dict[str, str]: api_key = self.api_key + if not api_key or api_key == WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER: + return {} return {"Authorization": f"Bearer {api_key}"} + @property + def _admin_api_key_auth(self) -> dict[str, str]: + admin_api_key = self.admin_api_key + if admin_api_key is None: + return {} + return {"Authorization": f"Bearer {admin_api_key}"} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -170,20 +484,47 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if _has_header(headers, "Authorization") or _has_omitted_header(custom_headers, "Authorization"): + return + + raise TypeError( + '"Could not resolve authentication method. Expected either api_key or admin_api_key to be set. Or for one of the `Authorization` or `Authorization` headers to be explicitly omitted"' + ) + + @override + def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: + if self._api_key_provider is not None and options.security.get("bearer_auth", False): + self._refresh_api_key() + + return super()._prepare_options(options) + + def _refresh_api_key(self) -> str: + if self._api_key_provider is not None: + self.api_key = self._api_key_provider() + + return self.api_key + def copy( self, *, - api_key: str | None = None, + api_key: str | Callable[[], str] | None = None, + admin_api_key: str | None = None, + workload_identity: WorkloadIdentity | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, set_default_query: Mapping[str, object] | None = None, + _enforce_credentials: bool | None = None, _extra_kwargs: Mapping[str, Any] = {}, ) -> Self: """ @@ -208,16 +549,22 @@ def copy( params = set_default_query http_client = http_client or self._client + return self.__class__( - api_key=api_key or self.api_key, + api_key=api_key or self._api_key_provider or self.api_key, + admin_api_key=admin_api_key or self.admin_api_key, + workload_identity=workload_identity or self.workload_identity, organization=organization or self.organization, project=project or self.project, + webhook_secret=webhook_secret or self.webhook_secret, + websocket_base_url=websocket_base_url or self.websocket_base_url, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, max_retries=max_retries if is_given(max_retries) else self.max_retries, default_headers=headers, default_query=params, + _enforce_credentials=True if _enforce_credentials is None else _enforce_credentials, **_extra_kwargs, ) @@ -261,34 +608,35 @@ def _make_status_error( class AsyncOpenAI(AsyncAPIClient): - completions: resources.AsyncCompletions - chat: resources.AsyncChat - embeddings: resources.AsyncEmbeddings - files: resources.AsyncFiles - images: resources.AsyncImages - audio: resources.AsyncAudio - moderations: resources.AsyncModerations - models: resources.AsyncModels - fine_tuning: resources.AsyncFineTuning - beta: resources.AsyncBeta - batches: resources.AsyncBatches - uploads: resources.AsyncUploads - with_raw_response: AsyncOpenAIWithRawResponse - with_streaming_response: AsyncOpenAIWithStreamedResponse - # client options api_key: str + admin_api_key: str | None + workload_identity: WorkloadIdentity | None organization: str | None project: str | None + webhook_secret: str | None + _workload_identity_auth: WorkloadIdentityAuth | None + + websocket_base_url: str | httpx.URL | None + """Base URL for WebSocket connections. + + If not specified, the default base URL will be used, with 'wss://' replacing the + 'http://' or 'https://' scheme. For example: 'http://example.com' becomes + 'wss://example.com' + """ def __init__( self, *, - api_key: str | None = None, + api_key: str | Callable[[], Awaitable[str]] | None = None, + admin_api_key: str | None = None, + workload_identity: WorkloadIdentity | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, base_url: str | httpx.URL | None = None, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + websocket_base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -305,21 +653,53 @@ def __init__( # outlining your use-case to help us decide if it should be # part of our public interface in the future. _strict_response_validation: bool = False, + _enforce_credentials: bool = True, ) -> None: - """Construct a new async openai client instance. + """Construct a new async AsyncOpenAI client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `api_key` from `OPENAI_API_KEY` + - `admin_api_key` from `OPENAI_ADMIN_KEY` - `organization` from `OPENAI_ORG_ID` - `project` from `OPENAI_PROJECT_ID` + - `webhook_secret` from `OPENAI_WEBHOOK_SECRET` """ - if api_key is None: - api_key = os.environ.get("OPENAI_API_KEY") - if api_key is None: + if api_key is not None and api_key != WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER and workload_identity is not None: + raise OpenAIError("The `api_key` and `workload_identity` arguments are mutually exclusive") + + self.workload_identity = workload_identity + + if workload_identity is not None: + self.api_key = WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER + self._api_key_provider = None + self._workload_identity_auth = WorkloadIdentityAuth( + workload_identity=workload_identity, + ) + else: + if api_key is None: + api_key = os.environ.get("OPENAI_API_KEY") + if callable(api_key): + self.api_key = "" + self._api_key_provider: Callable[[], Awaitable[str]] | None = api_key # type: ignore[no-redef] + else: + self.api_key = api_key or "" + self._api_key_provider = None + self._workload_identity_auth = None + + if admin_api_key is None: + admin_api_key = os.environ.get("OPENAI_ADMIN_KEY") + self.admin_api_key = admin_api_key + + if ( + _enforce_credentials + and not self.api_key + and self._api_key_provider is None + and workload_identity is None + and self.admin_api_key is None + ): raise OpenAIError( - "The api_key client option must be set either by passing api_key to the client or by setting the OPENAI_API_KEY environment variable" + "Missing credentials. Please pass an `api_key`, `workload_identity`, `admin_api_key`, or set the `OPENAI_API_KEY` or `OPENAI_ADMIN_KEY` environment variable." ) - self.api_key = api_key if organization is None: organization = os.environ.get("OPENAI_ORG_ID") @@ -329,11 +709,26 @@ def __init__( project = os.environ.get("OPENAI_PROJECT_ID") self.project = project + if webhook_secret is None: + webhook_secret = os.environ.get("OPENAI_WEBHOOK_SECRET") + self.webhook_secret = webhook_secret + + self.websocket_base_url = websocket_base_url + if base_url is None: base_url = os.environ.get("OPENAI_BASE_URL") if base_url is None: base_url = f"https://api.openai.com/v1" + custom_headers_env = os.environ.get("OPENAI_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, @@ -347,32 +742,243 @@ def __init__( self._default_stream_cls = AsyncStream - self.completions = resources.AsyncCompletions(self) - self.chat = resources.AsyncChat(self) - self.embeddings = resources.AsyncEmbeddings(self) - self.files = resources.AsyncFiles(self) - self.images = resources.AsyncImages(self) - self.audio = resources.AsyncAudio(self) - self.moderations = resources.AsyncModerations(self) - self.models = resources.AsyncModels(self) - self.fine_tuning = resources.AsyncFineTuning(self) - self.beta = resources.AsyncBeta(self) - self.batches = resources.AsyncBatches(self) - self.uploads = resources.AsyncUploads(self) - self.with_raw_response = AsyncOpenAIWithRawResponse(self) - self.with_streaming_response = AsyncOpenAIWithStreamedResponse(self) + @cached_property + def completions(self) -> AsyncCompletions: + """ + Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + """ + from .resources.completions import AsyncCompletions + + return AsyncCompletions(self) + + @cached_property + def chat(self) -> AsyncChat: + from .resources.chat import AsyncChat + + return AsyncChat(self) + + @cached_property + def embeddings(self) -> AsyncEmbeddings: + """ + Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + """ + from .resources.embeddings import AsyncEmbeddings + + return AsyncEmbeddings(self) + + @cached_property + def files(self) -> AsyncFiles: + """ + Files are used to upload documents that can be used with features like Assistants and Fine-tuning. + """ + from .resources.files import AsyncFiles + + return AsyncFiles(self) + + @cached_property + def images(self) -> AsyncImages: + """Given a prompt and/or an input image, the model will generate a new image.""" + from .resources.images import AsyncImages + + return AsyncImages(self) + + @cached_property + def audio(self) -> AsyncAudio: + from .resources.audio import AsyncAudio + + return AsyncAudio(self) + + @cached_property + def moderations(self) -> AsyncModerations: + """ + Given text and/or image inputs, classifies if those inputs are potentially harmful. + """ + from .resources.moderations import AsyncModerations + + return AsyncModerations(self) + + @cached_property + def models(self) -> AsyncModels: + """List and describe the various models available in the API.""" + from .resources.models import AsyncModels + + return AsyncModels(self) + + @cached_property + def fine_tuning(self) -> AsyncFineTuning: + from .resources.fine_tuning import AsyncFineTuning + + return AsyncFineTuning(self) + + @cached_property + def vector_stores(self) -> AsyncVectorStores: + from .resources.vector_stores import AsyncVectorStores + + return AsyncVectorStores(self) + + @cached_property + def webhooks(self) -> AsyncWebhooks: + from .resources.webhooks import AsyncWebhooks + + return AsyncWebhooks(self) + + @cached_property + def beta(self) -> AsyncBeta: + from .resources.beta import AsyncBeta + + return AsyncBeta(self) + + @cached_property + def batches(self) -> AsyncBatches: + """Create large batches of API requests to run asynchronously.""" + from .resources.batches import AsyncBatches + + return AsyncBatches(self) + + @cached_property + def uploads(self) -> AsyncUploads: + """Use Uploads to upload large files in multiple parts.""" + from .resources.uploads import AsyncUploads + + return AsyncUploads(self) + + @cached_property + def admin(self) -> AsyncAdmin: + from .resources.admin import AsyncAdmin + + return AsyncAdmin(self) + + @cached_property + def responses(self) -> AsyncResponses: + from .resources.responses import AsyncResponses + + return AsyncResponses(self) + + @cached_property + def realtime(self) -> AsyncRealtime: + from .resources.realtime import AsyncRealtime + + return AsyncRealtime(self) + + @cached_property + def conversations(self) -> AsyncConversations: + """Manage conversations and conversation items.""" + from .resources.conversations import AsyncConversations + + return AsyncConversations(self) + + @cached_property + def evals(self) -> AsyncEvals: + """Manage and run evals in the OpenAI platform.""" + from .resources.evals import AsyncEvals + + return AsyncEvals(self) + + @cached_property + def containers(self) -> AsyncContainers: + from .resources.containers import AsyncContainers + + return AsyncContainers(self) + + @cached_property + def skills(self) -> AsyncSkills: + from .resources.skills import AsyncSkills + + return AsyncSkills(self) + + @cached_property + def videos(self) -> AsyncVideos: + from .resources.videos import AsyncVideos + + return AsyncVideos(self) + + @cached_property + def with_raw_response(self) -> AsyncOpenAIWithRawResponse: + return AsyncOpenAIWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncOpenAIWithStreamedResponse: + return AsyncOpenAIWithStreamedResponse(self) @property @override def qs(self) -> Querystring: return Querystring(array_format="brackets") + async def _send_with_auth_retry( + self, + request: httpx.Request, + *, + stream: bool, + retried: bool = False, + **kwargs: Unpack[HttpxSendArgs], + ) -> httpx.Response: + used_workload_identity_auth = False + + if self._workload_identity_auth is not None: + authorization = request.headers.get("Authorization") + if authorization == f"Bearer {WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER}": + request.headers["Authorization"] = f"Bearer {await self._workload_identity_auth.get_token_async()}" + used_workload_identity_auth = True + + response = await super()._send_request(request, stream=stream, **kwargs) + if ( + response.status_code == 401 + and self._workload_identity_auth is not None + and used_workload_identity_auth + and not retried + ): + await response.aclose() + self._workload_identity_auth.invalidate_token() + request.headers["Authorization"] = f"Bearer {await self._workload_identity_auth.get_token_async()}" + return await self._send_with_auth_retry(request, stream=stream, retried=True, **kwargs) + + return response + + @override + async def _send_request( + self, + request: httpx.Request, + *, + stream: bool, + **kwargs: Unpack[HttpxSendArgs], + ) -> httpx.Response: + return await self._send_with_auth_retry(request, stream=stream, **kwargs) + + @override + def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: + if security.get("bearer_auth", False): + headers = self._bearer_auth + if headers: + return headers + + if security.get("admin_api_key_auth", False): + return self._admin_api_key_auth + + return {} + + @property + def _bearer_auth(self) -> dict[str, str]: + api_key = self.api_key + if not api_key: + return {} + return {"Authorization": f"Bearer {api_key}"} + @property @override def auth_headers(self) -> dict[str, str]: api_key = self.api_key + if not api_key or api_key == WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER: + return {} return {"Authorization": f"Bearer {api_key}"} + @property + def _admin_api_key_auth(self) -> dict[str, str]: + admin_api_key = self.admin_api_key + if admin_api_key is None: + return {} + return {"Authorization": f"Bearer {admin_api_key}"} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -384,20 +990,47 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if _has_header(headers, "Authorization") or _has_omitted_header(custom_headers, "Authorization"): + return + + raise TypeError( + '"Could not resolve authentication method. Expected either api_key or admin_api_key to be set. Or for one of the `Authorization` or `Authorization` headers to be explicitly omitted"' + ) + + @override + async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: + if self._api_key_provider is not None and options.security.get("bearer_auth", False): + await self._refresh_api_key() + + return await super()._prepare_options(options) + + async def _refresh_api_key(self) -> str: + if self._api_key_provider is not None: + self.api_key = await self._api_key_provider() + + return self.api_key + def copy( self, *, - api_key: str | None = None, + api_key: str | Callable[[], Awaitable[str]] | None = None, + admin_api_key: str | None = None, + workload_identity: WorkloadIdentity | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, set_default_query: Mapping[str, object] | None = None, + _enforce_credentials: bool | None = None, _extra_kwargs: Mapping[str, Any] = {}, ) -> Self: """ @@ -423,15 +1056,20 @@ def copy( http_client = http_client or self._client return self.__class__( - api_key=api_key or self.api_key, + api_key=api_key or self._api_key_provider or self.api_key, + admin_api_key=admin_api_key or self.admin_api_key, + workload_identity=workload_identity or self.workload_identity, organization=organization or self.organization, project=project or self.project, + webhook_secret=webhook_secret or self.webhook_secret, + websocket_base_url=websocket_base_url or self.websocket_base_url, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, max_retries=max_retries if is_given(max_retries) else self.max_retries, default_headers=headers, default_query=params, + _enforce_credentials=True if _enforce_credentials is None else _enforce_credentials, **_extra_kwargs, ) @@ -475,67 +1113,607 @@ def _make_status_error( class OpenAIWithRawResponse: + _client: OpenAI + def __init__(self, client: OpenAI) -> None: - self.completions = resources.CompletionsWithRawResponse(client.completions) - self.chat = resources.ChatWithRawResponse(client.chat) - self.embeddings = resources.EmbeddingsWithRawResponse(client.embeddings) - self.files = resources.FilesWithRawResponse(client.files) - self.images = resources.ImagesWithRawResponse(client.images) - self.audio = resources.AudioWithRawResponse(client.audio) - self.moderations = resources.ModerationsWithRawResponse(client.moderations) - self.models = resources.ModelsWithRawResponse(client.models) - self.fine_tuning = resources.FineTuningWithRawResponse(client.fine_tuning) - self.beta = resources.BetaWithRawResponse(client.beta) - self.batches = resources.BatchesWithRawResponse(client.batches) - self.uploads = resources.UploadsWithRawResponse(client.uploads) + self._client = client + + @cached_property + def completions(self) -> completions.CompletionsWithRawResponse: + """ + Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + """ + from .resources.completions import CompletionsWithRawResponse + + return CompletionsWithRawResponse(self._client.completions) + + @cached_property + def chat(self) -> chat.ChatWithRawResponse: + from .resources.chat import ChatWithRawResponse + + return ChatWithRawResponse(self._client.chat) + + @cached_property + def embeddings(self) -> embeddings.EmbeddingsWithRawResponse: + """ + Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + """ + from .resources.embeddings import EmbeddingsWithRawResponse + + return EmbeddingsWithRawResponse(self._client.embeddings) + + @cached_property + def files(self) -> files.FilesWithRawResponse: + """ + Files are used to upload documents that can be used with features like Assistants and Fine-tuning. + """ + from .resources.files import FilesWithRawResponse + + return FilesWithRawResponse(self._client.files) + + @cached_property + def images(self) -> images.ImagesWithRawResponse: + """Given a prompt and/or an input image, the model will generate a new image.""" + from .resources.images import ImagesWithRawResponse + + return ImagesWithRawResponse(self._client.images) + + @cached_property + def audio(self) -> audio.AudioWithRawResponse: + from .resources.audio import AudioWithRawResponse + + return AudioWithRawResponse(self._client.audio) + + @cached_property + def moderations(self) -> moderations.ModerationsWithRawResponse: + """ + Given text and/or image inputs, classifies if those inputs are potentially harmful. + """ + from .resources.moderations import ModerationsWithRawResponse + + return ModerationsWithRawResponse(self._client.moderations) + + @cached_property + def models(self) -> models.ModelsWithRawResponse: + """List and describe the various models available in the API.""" + from .resources.models import ModelsWithRawResponse + + return ModelsWithRawResponse(self._client.models) + + @cached_property + def fine_tuning(self) -> fine_tuning.FineTuningWithRawResponse: + from .resources.fine_tuning import FineTuningWithRawResponse + + return FineTuningWithRawResponse(self._client.fine_tuning) + + @cached_property + def vector_stores(self) -> vector_stores.VectorStoresWithRawResponse: + from .resources.vector_stores import VectorStoresWithRawResponse + + return VectorStoresWithRawResponse(self._client.vector_stores) + + @cached_property + def beta(self) -> beta.BetaWithRawResponse: + from .resources.beta import BetaWithRawResponse + + return BetaWithRawResponse(self._client.beta) + + @cached_property + def batches(self) -> batches.BatchesWithRawResponse: + """Create large batches of API requests to run asynchronously.""" + from .resources.batches import BatchesWithRawResponse + + return BatchesWithRawResponse(self._client.batches) + + @cached_property + def uploads(self) -> uploads.UploadsWithRawResponse: + """Use Uploads to upload large files in multiple parts.""" + from .resources.uploads import UploadsWithRawResponse + + return UploadsWithRawResponse(self._client.uploads) + + @cached_property + def admin(self) -> admin.AdminWithRawResponse: + from .resources.admin import AdminWithRawResponse + + return AdminWithRawResponse(self._client.admin) + + @cached_property + def responses(self) -> responses.ResponsesWithRawResponse: + from .resources.responses import ResponsesWithRawResponse + + return ResponsesWithRawResponse(self._client.responses) + + @cached_property + def realtime(self) -> realtime.RealtimeWithRawResponse: + from .resources.realtime import RealtimeWithRawResponse + + return RealtimeWithRawResponse(self._client.realtime) + + @cached_property + def conversations(self) -> conversations.ConversationsWithRawResponse: + """Manage conversations and conversation items.""" + from .resources.conversations import ConversationsWithRawResponse + + return ConversationsWithRawResponse(self._client.conversations) + + @cached_property + def evals(self) -> evals.EvalsWithRawResponse: + """Manage and run evals in the OpenAI platform.""" + from .resources.evals import EvalsWithRawResponse + + return EvalsWithRawResponse(self._client.evals) + + @cached_property + def containers(self) -> containers.ContainersWithRawResponse: + from .resources.containers import ContainersWithRawResponse + + return ContainersWithRawResponse(self._client.containers) + + @cached_property + def skills(self) -> skills.SkillsWithRawResponse: + from .resources.skills import SkillsWithRawResponse + + return SkillsWithRawResponse(self._client.skills) + + @cached_property + def videos(self) -> videos.VideosWithRawResponse: + from .resources.videos import VideosWithRawResponse + + return VideosWithRawResponse(self._client.videos) class AsyncOpenAIWithRawResponse: + _client: AsyncOpenAI + def __init__(self, client: AsyncOpenAI) -> None: - self.completions = resources.AsyncCompletionsWithRawResponse(client.completions) - self.chat = resources.AsyncChatWithRawResponse(client.chat) - self.embeddings = resources.AsyncEmbeddingsWithRawResponse(client.embeddings) - self.files = resources.AsyncFilesWithRawResponse(client.files) - self.images = resources.AsyncImagesWithRawResponse(client.images) - self.audio = resources.AsyncAudioWithRawResponse(client.audio) - self.moderations = resources.AsyncModerationsWithRawResponse(client.moderations) - self.models = resources.AsyncModelsWithRawResponse(client.models) - self.fine_tuning = resources.AsyncFineTuningWithRawResponse(client.fine_tuning) - self.beta = resources.AsyncBetaWithRawResponse(client.beta) - self.batches = resources.AsyncBatchesWithRawResponse(client.batches) - self.uploads = resources.AsyncUploadsWithRawResponse(client.uploads) + self._client = client + + @cached_property + def completions(self) -> completions.AsyncCompletionsWithRawResponse: + """ + Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + """ + from .resources.completions import AsyncCompletionsWithRawResponse + + return AsyncCompletionsWithRawResponse(self._client.completions) + + @cached_property + def chat(self) -> chat.AsyncChatWithRawResponse: + from .resources.chat import AsyncChatWithRawResponse + + return AsyncChatWithRawResponse(self._client.chat) + + @cached_property + def embeddings(self) -> embeddings.AsyncEmbeddingsWithRawResponse: + """ + Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + """ + from .resources.embeddings import AsyncEmbeddingsWithRawResponse + + return AsyncEmbeddingsWithRawResponse(self._client.embeddings) + + @cached_property + def files(self) -> files.AsyncFilesWithRawResponse: + """ + Files are used to upload documents that can be used with features like Assistants and Fine-tuning. + """ + from .resources.files import AsyncFilesWithRawResponse + + return AsyncFilesWithRawResponse(self._client.files) + + @cached_property + def images(self) -> images.AsyncImagesWithRawResponse: + """Given a prompt and/or an input image, the model will generate a new image.""" + from .resources.images import AsyncImagesWithRawResponse + + return AsyncImagesWithRawResponse(self._client.images) + + @cached_property + def audio(self) -> audio.AsyncAudioWithRawResponse: + from .resources.audio import AsyncAudioWithRawResponse + + return AsyncAudioWithRawResponse(self._client.audio) + + @cached_property + def moderations(self) -> moderations.AsyncModerationsWithRawResponse: + """ + Given text and/or image inputs, classifies if those inputs are potentially harmful. + """ + from .resources.moderations import AsyncModerationsWithRawResponse + + return AsyncModerationsWithRawResponse(self._client.moderations) + + @cached_property + def models(self) -> models.AsyncModelsWithRawResponse: + """List and describe the various models available in the API.""" + from .resources.models import AsyncModelsWithRawResponse + + return AsyncModelsWithRawResponse(self._client.models) + + @cached_property + def fine_tuning(self) -> fine_tuning.AsyncFineTuningWithRawResponse: + from .resources.fine_tuning import AsyncFineTuningWithRawResponse + + return AsyncFineTuningWithRawResponse(self._client.fine_tuning) + + @cached_property + def vector_stores(self) -> vector_stores.AsyncVectorStoresWithRawResponse: + from .resources.vector_stores import AsyncVectorStoresWithRawResponse + + return AsyncVectorStoresWithRawResponse(self._client.vector_stores) + + @cached_property + def beta(self) -> beta.AsyncBetaWithRawResponse: + from .resources.beta import AsyncBetaWithRawResponse + + return AsyncBetaWithRawResponse(self._client.beta) + + @cached_property + def batches(self) -> batches.AsyncBatchesWithRawResponse: + """Create large batches of API requests to run asynchronously.""" + from .resources.batches import AsyncBatchesWithRawResponse + + return AsyncBatchesWithRawResponse(self._client.batches) + + @cached_property + def uploads(self) -> uploads.AsyncUploadsWithRawResponse: + """Use Uploads to upload large files in multiple parts.""" + from .resources.uploads import AsyncUploadsWithRawResponse + + return AsyncUploadsWithRawResponse(self._client.uploads) + + @cached_property + def admin(self) -> admin.AsyncAdminWithRawResponse: + from .resources.admin import AsyncAdminWithRawResponse + + return AsyncAdminWithRawResponse(self._client.admin) + + @cached_property + def responses(self) -> responses.AsyncResponsesWithRawResponse: + from .resources.responses import AsyncResponsesWithRawResponse + + return AsyncResponsesWithRawResponse(self._client.responses) + + @cached_property + def realtime(self) -> realtime.AsyncRealtimeWithRawResponse: + from .resources.realtime import AsyncRealtimeWithRawResponse + + return AsyncRealtimeWithRawResponse(self._client.realtime) + + @cached_property + def conversations(self) -> conversations.AsyncConversationsWithRawResponse: + """Manage conversations and conversation items.""" + from .resources.conversations import AsyncConversationsWithRawResponse + + return AsyncConversationsWithRawResponse(self._client.conversations) + + @cached_property + def evals(self) -> evals.AsyncEvalsWithRawResponse: + """Manage and run evals in the OpenAI platform.""" + from .resources.evals import AsyncEvalsWithRawResponse + + return AsyncEvalsWithRawResponse(self._client.evals) + + @cached_property + def containers(self) -> containers.AsyncContainersWithRawResponse: + from .resources.containers import AsyncContainersWithRawResponse + + return AsyncContainersWithRawResponse(self._client.containers) + + @cached_property + def skills(self) -> skills.AsyncSkillsWithRawResponse: + from .resources.skills import AsyncSkillsWithRawResponse + + return AsyncSkillsWithRawResponse(self._client.skills) + + @cached_property + def videos(self) -> videos.AsyncVideosWithRawResponse: + from .resources.videos import AsyncVideosWithRawResponse + + return AsyncVideosWithRawResponse(self._client.videos) class OpenAIWithStreamedResponse: + _client: OpenAI + def __init__(self, client: OpenAI) -> None: - self.completions = resources.CompletionsWithStreamingResponse(client.completions) - self.chat = resources.ChatWithStreamingResponse(client.chat) - self.embeddings = resources.EmbeddingsWithStreamingResponse(client.embeddings) - self.files = resources.FilesWithStreamingResponse(client.files) - self.images = resources.ImagesWithStreamingResponse(client.images) - self.audio = resources.AudioWithStreamingResponse(client.audio) - self.moderations = resources.ModerationsWithStreamingResponse(client.moderations) - self.models = resources.ModelsWithStreamingResponse(client.models) - self.fine_tuning = resources.FineTuningWithStreamingResponse(client.fine_tuning) - self.beta = resources.BetaWithStreamingResponse(client.beta) - self.batches = resources.BatchesWithStreamingResponse(client.batches) - self.uploads = resources.UploadsWithStreamingResponse(client.uploads) + self._client = client + + @cached_property + def completions(self) -> completions.CompletionsWithStreamingResponse: + """ + Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + """ + from .resources.completions import CompletionsWithStreamingResponse + + return CompletionsWithStreamingResponse(self._client.completions) + + @cached_property + def chat(self) -> chat.ChatWithStreamingResponse: + from .resources.chat import ChatWithStreamingResponse + + return ChatWithStreamingResponse(self._client.chat) + + @cached_property + def embeddings(self) -> embeddings.EmbeddingsWithStreamingResponse: + """ + Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + """ + from .resources.embeddings import EmbeddingsWithStreamingResponse + + return EmbeddingsWithStreamingResponse(self._client.embeddings) + + @cached_property + def files(self) -> files.FilesWithStreamingResponse: + """ + Files are used to upload documents that can be used with features like Assistants and Fine-tuning. + """ + from .resources.files import FilesWithStreamingResponse + + return FilesWithStreamingResponse(self._client.files) + + @cached_property + def images(self) -> images.ImagesWithStreamingResponse: + """Given a prompt and/or an input image, the model will generate a new image.""" + from .resources.images import ImagesWithStreamingResponse + + return ImagesWithStreamingResponse(self._client.images) + + @cached_property + def audio(self) -> audio.AudioWithStreamingResponse: + from .resources.audio import AudioWithStreamingResponse + + return AudioWithStreamingResponse(self._client.audio) + + @cached_property + def moderations(self) -> moderations.ModerationsWithStreamingResponse: + """ + Given text and/or image inputs, classifies if those inputs are potentially harmful. + """ + from .resources.moderations import ModerationsWithStreamingResponse + + return ModerationsWithStreamingResponse(self._client.moderations) + + @cached_property + def models(self) -> models.ModelsWithStreamingResponse: + """List and describe the various models available in the API.""" + from .resources.models import ModelsWithStreamingResponse + + return ModelsWithStreamingResponse(self._client.models) + + @cached_property + def fine_tuning(self) -> fine_tuning.FineTuningWithStreamingResponse: + from .resources.fine_tuning import FineTuningWithStreamingResponse + + return FineTuningWithStreamingResponse(self._client.fine_tuning) + + @cached_property + def vector_stores(self) -> vector_stores.VectorStoresWithStreamingResponse: + from .resources.vector_stores import VectorStoresWithStreamingResponse + + return VectorStoresWithStreamingResponse(self._client.vector_stores) + + @cached_property + def beta(self) -> beta.BetaWithStreamingResponse: + from .resources.beta import BetaWithStreamingResponse + + return BetaWithStreamingResponse(self._client.beta) + + @cached_property + def batches(self) -> batches.BatchesWithStreamingResponse: + """Create large batches of API requests to run asynchronously.""" + from .resources.batches import BatchesWithStreamingResponse + + return BatchesWithStreamingResponse(self._client.batches) + + @cached_property + def uploads(self) -> uploads.UploadsWithStreamingResponse: + """Use Uploads to upload large files in multiple parts.""" + from .resources.uploads import UploadsWithStreamingResponse + + return UploadsWithStreamingResponse(self._client.uploads) + + @cached_property + def admin(self) -> admin.AdminWithStreamingResponse: + from .resources.admin import AdminWithStreamingResponse + + return AdminWithStreamingResponse(self._client.admin) + + @cached_property + def responses(self) -> responses.ResponsesWithStreamingResponse: + from .resources.responses import ResponsesWithStreamingResponse + + return ResponsesWithStreamingResponse(self._client.responses) + + @cached_property + def realtime(self) -> realtime.RealtimeWithStreamingResponse: + from .resources.realtime import RealtimeWithStreamingResponse + + return RealtimeWithStreamingResponse(self._client.realtime) + + @cached_property + def conversations(self) -> conversations.ConversationsWithStreamingResponse: + """Manage conversations and conversation items.""" + from .resources.conversations import ConversationsWithStreamingResponse + + return ConversationsWithStreamingResponse(self._client.conversations) + + @cached_property + def evals(self) -> evals.EvalsWithStreamingResponse: + """Manage and run evals in the OpenAI platform.""" + from .resources.evals import EvalsWithStreamingResponse + + return EvalsWithStreamingResponse(self._client.evals) + + @cached_property + def containers(self) -> containers.ContainersWithStreamingResponse: + from .resources.containers import ContainersWithStreamingResponse + + return ContainersWithStreamingResponse(self._client.containers) + + @cached_property + def skills(self) -> skills.SkillsWithStreamingResponse: + from .resources.skills import SkillsWithStreamingResponse + + return SkillsWithStreamingResponse(self._client.skills) + + @cached_property + def videos(self) -> videos.VideosWithStreamingResponse: + from .resources.videos import VideosWithStreamingResponse + + return VideosWithStreamingResponse(self._client.videos) class AsyncOpenAIWithStreamedResponse: + _client: AsyncOpenAI + def __init__(self, client: AsyncOpenAI) -> None: - self.completions = resources.AsyncCompletionsWithStreamingResponse(client.completions) - self.chat = resources.AsyncChatWithStreamingResponse(client.chat) - self.embeddings = resources.AsyncEmbeddingsWithStreamingResponse(client.embeddings) - self.files = resources.AsyncFilesWithStreamingResponse(client.files) - self.images = resources.AsyncImagesWithStreamingResponse(client.images) - self.audio = resources.AsyncAudioWithStreamingResponse(client.audio) - self.moderations = resources.AsyncModerationsWithStreamingResponse(client.moderations) - self.models = resources.AsyncModelsWithStreamingResponse(client.models) - self.fine_tuning = resources.AsyncFineTuningWithStreamingResponse(client.fine_tuning) - self.beta = resources.AsyncBetaWithStreamingResponse(client.beta) - self.batches = resources.AsyncBatchesWithStreamingResponse(client.batches) - self.uploads = resources.AsyncUploadsWithStreamingResponse(client.uploads) + self._client = client + + @cached_property + def completions(self) -> completions.AsyncCompletionsWithStreamingResponse: + """ + Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + """ + from .resources.completions import AsyncCompletionsWithStreamingResponse + + return AsyncCompletionsWithStreamingResponse(self._client.completions) + + @cached_property + def chat(self) -> chat.AsyncChatWithStreamingResponse: + from .resources.chat import AsyncChatWithStreamingResponse + + return AsyncChatWithStreamingResponse(self._client.chat) + + @cached_property + def embeddings(self) -> embeddings.AsyncEmbeddingsWithStreamingResponse: + """ + Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + """ + from .resources.embeddings import AsyncEmbeddingsWithStreamingResponse + + return AsyncEmbeddingsWithStreamingResponse(self._client.embeddings) + + @cached_property + def files(self) -> files.AsyncFilesWithStreamingResponse: + """ + Files are used to upload documents that can be used with features like Assistants and Fine-tuning. + """ + from .resources.files import AsyncFilesWithStreamingResponse + + return AsyncFilesWithStreamingResponse(self._client.files) + + @cached_property + def images(self) -> images.AsyncImagesWithStreamingResponse: + """Given a prompt and/or an input image, the model will generate a new image.""" + from .resources.images import AsyncImagesWithStreamingResponse + + return AsyncImagesWithStreamingResponse(self._client.images) + + @cached_property + def audio(self) -> audio.AsyncAudioWithStreamingResponse: + from .resources.audio import AsyncAudioWithStreamingResponse + + return AsyncAudioWithStreamingResponse(self._client.audio) + + @cached_property + def moderations(self) -> moderations.AsyncModerationsWithStreamingResponse: + """ + Given text and/or image inputs, classifies if those inputs are potentially harmful. + """ + from .resources.moderations import AsyncModerationsWithStreamingResponse + + return AsyncModerationsWithStreamingResponse(self._client.moderations) + + @cached_property + def models(self) -> models.AsyncModelsWithStreamingResponse: + """List and describe the various models available in the API.""" + from .resources.models import AsyncModelsWithStreamingResponse + + return AsyncModelsWithStreamingResponse(self._client.models) + + @cached_property + def fine_tuning(self) -> fine_tuning.AsyncFineTuningWithStreamingResponse: + from .resources.fine_tuning import AsyncFineTuningWithStreamingResponse + + return AsyncFineTuningWithStreamingResponse(self._client.fine_tuning) + + @cached_property + def vector_stores(self) -> vector_stores.AsyncVectorStoresWithStreamingResponse: + from .resources.vector_stores import AsyncVectorStoresWithStreamingResponse + + return AsyncVectorStoresWithStreamingResponse(self._client.vector_stores) + + @cached_property + def beta(self) -> beta.AsyncBetaWithStreamingResponse: + from .resources.beta import AsyncBetaWithStreamingResponse + + return AsyncBetaWithStreamingResponse(self._client.beta) + + @cached_property + def batches(self) -> batches.AsyncBatchesWithStreamingResponse: + """Create large batches of API requests to run asynchronously.""" + from .resources.batches import AsyncBatchesWithStreamingResponse + + return AsyncBatchesWithStreamingResponse(self._client.batches) + + @cached_property + def uploads(self) -> uploads.AsyncUploadsWithStreamingResponse: + """Use Uploads to upload large files in multiple parts.""" + from .resources.uploads import AsyncUploadsWithStreamingResponse + + return AsyncUploadsWithStreamingResponse(self._client.uploads) + + @cached_property + def admin(self) -> admin.AsyncAdminWithStreamingResponse: + from .resources.admin import AsyncAdminWithStreamingResponse + + return AsyncAdminWithStreamingResponse(self._client.admin) + + @cached_property + def responses(self) -> responses.AsyncResponsesWithStreamingResponse: + from .resources.responses import AsyncResponsesWithStreamingResponse + + return AsyncResponsesWithStreamingResponse(self._client.responses) + + @cached_property + def realtime(self) -> realtime.AsyncRealtimeWithStreamingResponse: + from .resources.realtime import AsyncRealtimeWithStreamingResponse + + return AsyncRealtimeWithStreamingResponse(self._client.realtime) + + @cached_property + def conversations(self) -> conversations.AsyncConversationsWithStreamingResponse: + """Manage conversations and conversation items.""" + from .resources.conversations import AsyncConversationsWithStreamingResponse + + return AsyncConversationsWithStreamingResponse(self._client.conversations) + + @cached_property + def evals(self) -> evals.AsyncEvalsWithStreamingResponse: + """Manage and run evals in the OpenAI platform.""" + from .resources.evals import AsyncEvalsWithStreamingResponse + + return AsyncEvalsWithStreamingResponse(self._client.evals) + + @cached_property + def containers(self) -> containers.AsyncContainersWithStreamingResponse: + from .resources.containers import AsyncContainersWithStreamingResponse + + return AsyncContainersWithStreamingResponse(self._client.containers) + + @cached_property + def skills(self) -> skills.AsyncSkillsWithStreamingResponse: + from .resources.skills import AsyncSkillsWithStreamingResponse + + return AsyncSkillsWithStreamingResponse(self._client.skills) + + @cached_property + def videos(self) -> videos.AsyncVideosWithStreamingResponse: + from .resources.videos import AsyncVideosWithStreamingResponse + + return AsyncVideosWithStreamingResponse(self._client.videos) Client = OpenAI diff --git a/src/openai/_compat.py b/src/openai/_compat.py index c0dd8c1ee5..340c91a6d0 100644 --- a/src/openai/_compat.py +++ b/src/openai/_compat.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload from datetime import date, datetime -from typing_extensions import Self +from typing_extensions import Self, Literal, TypedDict import pydantic from pydantic.fields import FieldInfo @@ -12,14 +12,13 @@ _T = TypeVar("_T") _ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) -# --------------- Pydantic v2 compatibility --------------- +# --------------- Pydantic v2, v3 compatibility --------------- # Pyright incorrectly reports some of our functions as overriding a method when they don't # pyright: reportIncompatibleMethodOverride=false -PYDANTIC_V2 = pydantic.VERSION.startswith("2.") +PYDANTIC_V1 = pydantic.VERSION.startswith("1.") -# v1 re-exports if TYPE_CHECKING: def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 @@ -44,131 +43,145 @@ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 ... else: - if PYDANTIC_V2: - from pydantic.v1.typing import ( + # v1 re-exports + if PYDANTIC_V1: + from pydantic.typing import ( get_args as get_args, is_union as is_union, get_origin as get_origin, is_typeddict as is_typeddict, is_literal_type as is_literal_type, ) - from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime + from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime else: - from pydantic.typing import ( + from ._utils import ( get_args as get_args, is_union as is_union, get_origin as get_origin, + parse_date as parse_date, is_typeddict as is_typeddict, + parse_datetime as parse_datetime, is_literal_type as is_literal_type, ) - from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime # refactored config if TYPE_CHECKING: from pydantic import ConfigDict as ConfigDict else: - if PYDANTIC_V2: - from pydantic import ConfigDict - else: + if PYDANTIC_V1: # TODO: provide an error message here? ConfigDict = None + else: + from pydantic import ConfigDict as ConfigDict # renamed methods / properties def parse_obj(model: type[_ModelT], value: object) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(value) - else: + if PYDANTIC_V1: return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + else: + return model.model_validate(value) def field_is_required(field: FieldInfo) -> bool: - if PYDANTIC_V2: - return field.is_required() - return field.required # type: ignore + if PYDANTIC_V1: + return field.required # type: ignore + return field.is_required() def field_get_default(field: FieldInfo) -> Any: value = field.get_default() - if PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None + if PYDANTIC_V1: return value + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None return value def field_outer_type(field: FieldInfo) -> Any: - if PYDANTIC_V2: - return field.annotation - return field.outer_type_ # type: ignore + if PYDANTIC_V1: + return field.outer_type_ # type: ignore + return field.annotation def get_model_config(model: type[pydantic.BaseModel]) -> Any: - if PYDANTIC_V2: - return model.model_config - return model.__config__ # type: ignore + if PYDANTIC_V1: + return model.__config__ # type: ignore + return model.model_config def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: - if PYDANTIC_V2: - return model.model_fields - return model.__fields__ # type: ignore + if PYDANTIC_V1: + return model.__fields__ # type: ignore + return model.model_fields def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT: - if PYDANTIC_V2: - return model.model_copy(deep=deep) - return model.copy(deep=deep) # type: ignore + if PYDANTIC_V1: + return model.copy(deep=deep) # type: ignore + return model.model_copy(deep=deep) def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str: - if PYDANTIC_V2: - return model.model_dump_json(indent=indent) - return model.json(indent=indent) # type: ignore + if PYDANTIC_V1: + return model.json(indent=indent) # type: ignore + return model.model_dump_json(indent=indent) + + +class _ModelDumpKwargs(TypedDict, total=False): + by_alias: bool def model_dump( model: pydantic.BaseModel, *, - exclude: IncEx = None, + exclude: IncEx | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, + warnings: bool = True, + mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, ) -> dict[str, Any]: - if PYDANTIC_V2: + if (not PYDANTIC_V1) or hasattr(model, "model_dump"): + kwargs: _ModelDumpKwargs = {} + if by_alias is not None: + kwargs["by_alias"] = by_alias return model.model_dump( + mode=mode, exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, + # warnings are not supported in Pydantic v1 + warnings=True if PYDANTIC_V1 else warnings, + **kwargs, ) return cast( "dict[str, Any]", model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - exclude=exclude, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) ), ) def model_parse(model: type[_ModelT], data: Any) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(data) - return model.parse_obj(data) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return model.parse_obj(data) # pyright: ignore[reportDeprecated] + return model.model_validate(data) def model_parse_json(model: type[_ModelT], data: str | bytes) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate_json(data) - return model.parse_raw(data) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return model.parse_raw(data) # pyright: ignore[reportDeprecated] + return model.model_validate_json(data) def model_json_schema(model: type[_ModelT]) -> dict[str, Any]: - if PYDANTIC_V2: - return model.model_json_schema() - return model.schema() # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return model.schema() # pyright: ignore[reportDeprecated] + return model.model_json_schema() # generic models @@ -177,17 +190,16 @@ def model_json_schema(model: type[_ModelT]) -> dict[str, Any]: class GenericModel(pydantic.BaseModel): ... else: - if PYDANTIC_V2: + if PYDANTIC_V1: + import pydantic.generics + + class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... + else: # there no longer needs to be a distinction in v2 but # we still have to create our own subclass to avoid # inconsistent MRO ordering errors class GenericModel(pydantic.BaseModel): ... - else: - import pydantic.generics - - class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... - # cached properties if TYPE_CHECKING: @@ -221,9 +233,6 @@ def __set_name__(self, owner: type[Any], name: str) -> None: ... # __set__ is not defined at runtime, but @cached_property is designed to be settable def __set__(self, instance: object, value: _T) -> None: ... else: - try: - from functools import cached_property as cached_property - except ImportError: - from cached_property import cached_property as cached_property + from functools import cached_property as cached_property typed_cached_property = cached_property diff --git a/src/openai/_constants.py b/src/openai/_constants.py index 3f82bed037..7029dc72b0 100644 --- a/src/openai/_constants.py +++ b/src/openai/_constants.py @@ -6,7 +6,7 @@ OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" # default timeout is 10 minutes -DEFAULT_TIMEOUT = httpx.Timeout(timeout=600.0, connect=5.0) +DEFAULT_TIMEOUT = httpx.Timeout(timeout=600, connect=5.0) DEFAULT_MAX_RETRIES = 2 DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=1000, max_keepalive_connections=100) diff --git a/src/openai/_event_handler.py b/src/openai/_event_handler.py new file mode 100644 index 0000000000..d9a3a59d71 --- /dev/null +++ b/src/openai/_event_handler.py @@ -0,0 +1,85 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import threading +from typing import Any, Callable + +EventHandler = Callable[..., Any] + + +class EventHandlerRegistry: + """Thread-safe (optional) registry of event handlers.""" + + def __init__(self, *, use_lock: bool = False) -> None: + self._handlers: dict[str, list[EventHandler]] = {} + self._once_ids: set[int] = set() + self._lock: threading.Lock | None = threading.Lock() if use_lock else None + + def _acquire(self) -> None: + if self._lock is not None: + self._lock.acquire() + + def _release(self) -> None: + if self._lock is not None: + self._lock.release() + + def add(self, event_type: str, handler: EventHandler, *, once: bool = False) -> None: + self._acquire() + try: + handlers = self._handlers.setdefault(event_type, []) + handlers.append(handler) + if once: + self._once_ids.add(id(handler)) + finally: + self._release() + + def remove(self, event_type: str, handler: EventHandler) -> None: + self._acquire() + try: + handlers = self._handlers.get(event_type) + if handlers is not None: + try: + handlers.remove(handler) + except ValueError: + pass + self._once_ids.discard(id(handler)) + finally: + self._release() + + def get_handlers(self, event_type: str) -> list[EventHandler]: + """Return a snapshot of handlers for the given event type, removing once-handlers.""" + self._acquire() + try: + handlers = self._handlers.get(event_type) + if not handlers: + return [] + result = list(handlers) + to_remove = [h for h in result if id(h) in self._once_ids] + for h in to_remove: + handlers.remove(h) + self._once_ids.discard(id(h)) + return result + finally: + self._release() + + def has_handlers(self, event_type: str) -> bool: + self._acquire() + try: + handlers = self._handlers.get(event_type) + return bool(handlers) + finally: + self._release() + + def merge_into(self, target: EventHandlerRegistry) -> None: + """Move all handlers from this registry into *target*, then clear self.""" + self._acquire() + try: + for event_type, handlers in self._handlers.items(): + for handler in handlers: + once = id(handler) in self._once_ids + target.add(event_type, handler, once=once) + self._handlers.clear() + self._once_ids.clear() + finally: + self._release() diff --git a/src/openai/_exceptions.py b/src/openai/_exceptions.py index e326ed9578..86f44b0e15 100644 --- a/src/openai/_exceptions.py +++ b/src/openai/_exceptions.py @@ -9,6 +9,7 @@ from ._utils import is_dict from ._models import construct_type +from .types.shared.oauth_error_code import OAuthErrorCode if TYPE_CHECKING: from .types.chat import ChatCompletion @@ -16,6 +17,7 @@ __all__ = [ "BadRequestError", "AuthenticationError", + "OAuthError", "PermissionDeniedError", "NotFoundError", "ConflictError", @@ -24,6 +26,10 @@ "InternalServerError", "LengthFinishReasonError", "ContentFilterFinishReasonError", + "InvalidWebhookSignatureError", + "SubjectTokenProviderError", + "WebSocketConnectionClosedError", + "WebSocketQueueFullError", ] @@ -31,6 +37,14 @@ class OpenAIError(Exception): pass +class SubjectTokenProviderError(OpenAIError): + response: httpx.Response | None + + def __init__(self, message: str, *, response: httpx.Response | None = None) -> None: + super().__init__(message) + self.response = response + + class APIError(OpenAIError): message: str request: httpx.Request @@ -108,6 +122,23 @@ class AuthenticationError(APIStatusError): status_code: Literal[401] = 401 # pyright: ignore[reportIncompatibleVariableOverride] +class OAuthError(AuthenticationError): + error: Optional[OAuthErrorCode] + + def __init__(self, *, response: httpx.Response, body: object | None) -> None: + message = "OAuth authentication error." + error = None + + if is_dict(body): + error = body.get("error") + description = body.get("error_description") + if description and isinstance(description, str): + message = description + + super().__init__(message, response=response, body=body) + self.error = cast(Optional[OAuthErrorCode], error) + + class PermissionDeniedError(APIStatusError): status_code: Literal[403] = 403 # pyright: ignore[reportIncompatibleVariableOverride] @@ -154,3 +185,23 @@ def __init__(self) -> None: super().__init__( f"Could not parse response content as the request was rejected by the content filter", ) + + +class InvalidWebhookSignatureError(ValueError): + """Raised when a webhook signature is invalid, meaning the computed signature does not match the expected signature.""" + + +class WebSocketConnectionClosedError(OpenAIError): + """Raised when a WebSocket connection closes with unsent messages.""" + + unsent_messages: list[str] + + def __init__(self, message: str, *, unsent_messages: list[str]) -> None: + super().__init__(message) + self.unsent_messages = unsent_messages + + +class WebSocketQueueFullError(OpenAIError): + """Raised when the outgoing WebSocket message queue exceeds its byte-size limit.""" + + pass diff --git a/src/openai/_extras/__init__.py b/src/openai/_extras/__init__.py index 864dac4171..692de248c0 100644 --- a/src/openai/_extras/__init__.py +++ b/src/openai/_extras/__init__.py @@ -1,2 +1,3 @@ from .numpy_proxy import numpy as numpy, has_numpy as has_numpy from .pandas_proxy import pandas as pandas +from .sounddevice_proxy import sounddevice as sounddevice diff --git a/src/openai/_extras/numpy_proxy.py b/src/openai/_extras/numpy_proxy.py index 27880bf132..2b0669576e 100644 --- a/src/openai/_extras/numpy_proxy.py +++ b/src/openai/_extras/numpy_proxy.py @@ -10,7 +10,7 @@ import numpy as numpy -NUMPY_INSTRUCTIONS = format_instructions(library="numpy", extra="datalib") +NUMPY_INSTRUCTIONS = format_instructions(library="numpy", extra="voice_helpers") class NumpyProxy(LazyProxy[Any]): diff --git a/src/openai/_extras/sounddevice_proxy.py b/src/openai/_extras/sounddevice_proxy.py new file mode 100644 index 0000000000..482d4c6874 --- /dev/null +++ b/src/openai/_extras/sounddevice_proxy.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any +from typing_extensions import override + +from .._utils import LazyProxy +from ._common import MissingDependencyError, format_instructions + +if TYPE_CHECKING: + import sounddevice as sounddevice # type: ignore + + +SOUNDDEVICE_INSTRUCTIONS = format_instructions(library="sounddevice", extra="voice_helpers") + + +class SounddeviceProxy(LazyProxy[Any]): + @override + def __load__(self) -> Any: + try: + import sounddevice # type: ignore + except ImportError as err: + raise MissingDependencyError(SOUNDDEVICE_INSTRUCTIONS) from err + + return sounddevice + + +if not TYPE_CHECKING: + sounddevice = SounddeviceProxy() diff --git a/src/openai/_files.py b/src/openai/_files.py index 801a0d2928..1a2cc77478 100644 --- a/src/openai/_files.py +++ b/src/openai/_files.py @@ -3,8 +3,8 @@ import io import os import pathlib -from typing import overload -from typing_extensions import TypeGuard +from typing import Sequence, cast, overload +from typing_extensions import TypeVar, TypeGuard import anyio @@ -17,7 +17,9 @@ HttpxFileContent, HttpxRequestFiles, ) -from ._utils import is_tuple_t, is_mapping_t, is_sequence_t +from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t + +_T = TypeVar("_T") def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: @@ -69,12 +71,12 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], _read_file_content(file[1]), *file[2:]) + return (file[0], read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -def _read_file_content(file: FileContent) -> HttpxFileContent: +def read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return pathlib.Path(file).read_bytes() return file @@ -97,7 +99,7 @@ async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles elif is_sequence_t(files): files = [(key, await _async_transform_file(file)) for key, file in files] else: - raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence") + raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") return files @@ -111,13 +113,61 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], await _async_read_file_content(file[1]), *file[2:]) + return (file[0], await async_read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -async def _async_read_file_content(file: FileContent) -> HttpxFileContent: +async def async_read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return await anyio.Path(file).read_bytes() return file + + +def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T: + """Copy only the containers along the given paths. + + Used to guard against mutation by extract_files without copying the entire structure. + Only dicts and lists that lie on a path are copied; everything else + is returned by reference. + + For example, given paths=[["foo", "files", "file"]] and the structure: + { + "foo": { + "bar": {"baz": {}}, + "files": {"file": } + } + } + The root dict, "foo", and "files" are copied (they lie on the path). + "bar" and "baz" are returned by reference (off the path). + """ + return _deepcopy_with_paths(item, paths, 0) + + +def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T: + if not paths: + return item + if is_mapping(item): + key_to_paths: dict[str, list[Sequence[str]]] = {} + for path in paths: + if index < len(path): + key_to_paths.setdefault(path[index], []).append(path) + + # if no path continues through this mapping, it won't be mutated and copying it is redundant + if not key_to_paths: + return item + + result = dict(item) + for key, subpaths in key_to_paths.items(): + if key in result: + result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1) + return cast(_T, result) + if is_list(item): + array_paths = [path for path in paths if index < len(path) and path[index] == ""] + + # if no path expects a list here, nothing will be mutated inside it - return by reference + if not array_paths: + return cast(_T, item) + return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item]) + return item diff --git a/src/openai/_legacy_response.py b/src/openai/_legacy_response.py index c42fb8b83e..1a58c2dfc3 100644 --- a/src/openai/_legacy_response.py +++ b/src/openai/_legacy_response.py @@ -24,8 +24,8 @@ import pydantic from ._types import NoneType -from ._utils import is_given, extract_type_arg, is_annotated_type -from ._models import BaseModel, is_basemodel +from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type +from ._models import BaseModel, is_basemodel, add_request_id from ._constants import RAW_RESPONSE_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type from ._exceptions import APIResponseValidationError @@ -138,8 +138,11 @@ class MyModel(BaseModel): if is_given(self._options.post_parser): parsed = self._options.post_parser(parsed) + if isinstance(parsed, BaseModel): + add_request_id(parsed, self.request_id) + self._parsed_by_type[cache_key] = parsed - return parsed + return cast(R, parsed) @property def headers(self) -> httpx.Headers: @@ -192,9 +195,17 @@ def elapsed(self) -> datetime.timedelta: return self.http_response.elapsed def _parse(self, *, to: type[_T] | None = None) -> R | _T: + cast_to = to if to is not None else self._cast_to + + # unwrap `TypeAlias('Name', T)` -> `T` + if is_type_alias_type(cast_to): + cast_to = cast_to.__value__ # type: ignore[unreachable] + # unwrap `Annotated[T, ...]` -> `T` - if to and is_annotated_type(to): - to = extract_type_arg(to, 0) + if cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) + + origin = get_origin(cast_to) or cast_to if self._stream: if to: @@ -210,6 +221,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -220,6 +232,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -230,18 +243,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: return cast( R, stream_cls( - cast_to=self._cast_to, + cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) - cast_to = to if to is not None else self._cast_to - - # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(cast_to): - cast_to = extract_type_arg(cast_to, 0) - if cast_to is NoneType: return cast(R, None) @@ -255,7 +263,8 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == float: return cast(R, float(response.text)) - origin = get_origin(cast_to) or cast_to + if cast_to == bool: + return cast(R, response.text.lower() == "true") if inspect.isclass(origin) and issubclass(origin, HttpxBinaryResponseContent): return cast(R, cast_to(response)) # type: ignore @@ -263,7 +272,9 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if origin == LegacyAPIResponse: raise RuntimeError("Unexpected state - cast_to is `APIResponse`") - if inspect.isclass(origin) and issubclass(origin, httpx.Response): + if inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) and issubclass(origin, httpx.Response): # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response # and pass that class to our request functions. We cannot change the variance to be either # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct @@ -273,7 +284,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") return cast(R, response) - if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + if ( + inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) + and not issubclass(origin, BaseModel) + and issubclass(origin, pydantic.BaseModel) + ): raise TypeError("Pydantic models must subclass our base model type, e.g. `from openai import BaseModel`") if ( @@ -290,7 +307,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: # split is required to handle cases where additional information is included # in the response, e.g. application/json; charset=utf-8 content_type, *_ = response.headers.get("content-type", "*").split(";") - if content_type != "application/json": + if not content_type.endswith("json"): if is_basemodel(cast_to): try: data = response.json() diff --git a/src/openai/_models.py b/src/openai/_models.py index d386eaa3a4..ed4c1f82d6 100644 --- a/src/openai/_models.py +++ b/src/openai/_models.py @@ -2,15 +2,34 @@ import os import inspect -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +import weakref +from typing import ( + IO, + TYPE_CHECKING, + Any, + Type, + Tuple, + Union, + Generic, + TypeVar, + Callable, + Iterable, + Optional, + AsyncIterable, + cast, +) from datetime import date, datetime from typing_extensions import ( + List, Unpack, Literal, ClassVar, Protocol, Required, + Sequence, + Annotated, ParamSpec, + TypeAlias, TypedDict, TypeGuard, final, @@ -19,7 +38,6 @@ ) import pydantic -import pydantic.generics from pydantic.fields import FieldInfo from ._types import ( @@ -37,6 +55,7 @@ PropertyInfo, is_list, is_given, + json_safe, lru_cache, is_mapping, parse_date, @@ -45,10 +64,11 @@ strip_not_given, extract_type_arg, is_annotated_type, + is_type_alias_type, strip_annotated_type, ) from ._compat import ( - PYDANTIC_V2, + PYDANTIC_V1, ConfigDict, GenericModel as BaseGenericModel, get_args, @@ -63,7 +83,15 @@ from ._constants import RAW_RESPONSE_HEADER if TYPE_CHECKING: - from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema + from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler + from pydantic_core import CoreSchema, core_schema + from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema +else: + try: + from pydantic_core import CoreSchema, core_schema + except ImportError: + CoreSchema = None + core_schema = None __all__ = ["BaseModel", "GenericModel"] @@ -72,6 +100,8 @@ P = ParamSpec("P") +ReprArgs = Sequence[Tuple[Optional[str], Any]] + @runtime_checkable class _ConfigProtocol(Protocol): @@ -79,11 +109,7 @@ class _ConfigProtocol(Protocol): class BaseModel(pydantic.BaseModel): - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict( - extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) - ) - else: + if PYDANTIC_V1: @property @override @@ -94,6 +120,32 @@ def model_fields_set(self) -> set[str]: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] extra: Any = pydantic.Extra.allow # type: ignore + @override + def __repr_args__(self) -> ReprArgs: + # we don't want these attributes to be included when something like `rich.print` is used + return [arg for arg in super().__repr_args__() if arg[0] not in {"_request_id", "__exclude_fields__"}] + else: + model_config: ClassVar[ConfigDict] = ConfigDict( + extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) + ) + + if TYPE_CHECKING: + _request_id: Optional[str] = None + """The ID of the request, returned via the X-Request-ID header. Useful for debugging requests and reporting issues to OpenAI. + + This will **only** be set for the top-level response object, it will not be defined for nested objects. For example: + + ```py + completion = await client.chat.completions.create(...) + completion._request_id # req_id_xxx + completion.usage._request_id # raises `AttributeError` + ``` + + Note: unlike other properties that use an `_` prefix, this property + *is* public. Unless documented otherwise, all other `_` prefix properties, + methods and modules are *private*. + """ + def to_dict( self, *, @@ -170,21 +222,21 @@ def to_json( @override def __str__(self) -> str: # mypy complains about an invalid self arg - return f'{self.__repr_name__()}({self.__repr_str__(", ")})' # type: ignore[misc] + return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc] # Override the 'construct' method in a way that supports recursive parsing without validation. # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836. @classmethod @override - def construct( - cls: Type[ModelT], + def construct( # pyright: ignore[reportIncompatibleMethodOverride] + __cls: Type[ModelT], _fields_set: set[str] | None = None, **values: object, ) -> ModelT: - m = cls.__new__(cls) + m = __cls.__new__(__cls) fields_values: dict[str, object] = {} - config = get_model_config(cls) + config = get_model_config(__cls) populate_by_name = ( config.allow_population_by_field_name if isinstance(config, _ConfigProtocol) @@ -194,7 +246,7 @@ def construct( if _fields_set is None: _fields_set = set() - model_fields = get_model_fields(cls) + model_fields = get_model_fields(__cls) for name, field in model_fields.items(): key = field.alias if key is None or (key not in values and populate_by_name): @@ -206,28 +258,32 @@ def construct( else: fields_values[name] = field_get_default(field) + extra_field_type = _get_extra_fields_type(__cls) + _extra = {} for key, value in values.items(): if key not in model_fields: - if PYDANTIC_V2: - _extra[key] = value - else: + parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value + + if PYDANTIC_V1: _fields_set.add(key) - fields_values[key] = value + fields_values[key] = parsed + else: + _extra[key] = parsed object.__setattr__(m, "__dict__", fields_values) - if PYDANTIC_V2: - # these properties are copied from Pydantic's `model_construct()` method - object.__setattr__(m, "__pydantic_private__", None) - object.__setattr__(m, "__pydantic_extra__", _extra) - object.__setattr__(m, "__pydantic_fields_set__", _fields_set) - else: + if PYDANTIC_V1: # init_private_attributes() does not exist in v2 m._init_private_attributes() # type: ignore # copied from Pydantic v1's `construct()` method object.__setattr__(m, "__fields_set__", _fields_set) + else: + # these properties are copied from Pydantic's `model_construct()` method + object.__setattr__(m, "__pydantic_private__", None) + object.__setattr__(m, "__pydantic_extra__", _extra) + object.__setattr__(m, "__pydantic_fields_set__", _fields_set) return m @@ -237,7 +293,7 @@ def construct( # although not in practice model_construct = construct - if not PYDANTIC_V2: + if PYDANTIC_V1: # we define aliases for some of the new pydantic v2 methods so # that we can just document these methods without having to specify # a specific pydantic version as some users may not know which @@ -248,15 +304,17 @@ def model_dump( self, *, mode: Literal["json", "python"] | str = "python", - include: IncEx = None, - exclude: IncEx = None, - by_alias: bool = False, + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any | None = None, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, + exclude_computed_fields: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, - context: dict[str, Any] | None = None, + fallback: Callable[[Any], Any] | None = None, serialize_as_any: bool = False, ) -> dict[str, Any]: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump @@ -265,22 +323,30 @@ def model_dump( Args: mode: The mode in which `to_python` should run. - If mode is 'json', the dictionary will only contain JSON serializable types. - If mode is 'python', the dictionary may contain any Python objects. - include: A list of fields to include in the output. - exclude: A list of fields to exclude from the output. + If mode is 'json', the output will only contain JSON serializable types. + If mode is 'python', the output may contain non-JSON-serializable Python objects. + include: A set of fields to include in the output. + exclude: A set of fields to exclude from the output. + context: Additional context to pass to the serializer. by_alias: Whether to use the field's alias in the dictionary key if defined. - exclude_unset: Whether to exclude fields that are unset or None from the output. - exclude_defaults: Whether to exclude fields that are set to their default value from the output. - exclude_none: Whether to exclude fields that have a value of `None` from the output. - round_trip: Whether to enable serialization and deserialization round-trip support. - warnings: Whether to log warnings when invalid fields are encountered. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. Returns: A dictionary representation of the model. """ - if mode != "python": - raise ValueError("mode is only supported in Pydantic v2") + if mode not in {"json", "python"}: + raise ValueError("mode must be either 'json' or 'python'") if round_trip != False: raise ValueError("round_trip is only supported in Pydantic v2") if warnings != True: @@ -289,29 +355,38 @@ def model_dump( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") - return super().dict( # pyright: ignore[reportDeprecated] + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") + dumped = super().dict( # pyright: ignore[reportDeprecated] include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, ) + return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped + @override def model_dump_json( self, *, indent: int | None = None, - include: IncEx = None, - exclude: IncEx = None, - by_alias: bool = False, + ensure_ascii: bool = False, + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any | None = None, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, + exclude_computed_fields: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, - context: dict[str, Any] | None = None, + fallback: Callable[[Any], Any] | None = None, serialize_as_any: bool = False, ) -> str: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json @@ -340,30 +415,123 @@ def model_dump_json( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if ensure_ascii != False: + raise ValueError("ensure_ascii is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") return super().json( # type: ignore[reportDeprecated] indent=indent, include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, ) +class _EagerIterable(list[_T], Generic[_T]): + """ + Accepts any Iterable[T] input (including generators), consumes it + eagerly, and validates all items upfront. + + Validation preserves the original container type where possible + (e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON) + always emits a list — round-tripping through model_dump() will not + restore the original container type. + """ + + @classmethod + def __get_pydantic_core_schema__( + cls, + source_type: Any, + handler: GetCoreSchemaHandler, + ) -> CoreSchema: + (item_type,) = get_args(source_type) or (Any,) + item_schema: CoreSchema = handler.generate_schema(item_type) + list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema) + + return core_schema.no_info_wrap_validator_function( + cls._validate, + list_of_items_schema, + serialization=core_schema.plain_serializer_function_ser_schema( + cls._serialize, + info_arg=False, + ), + ) + + @staticmethod + def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any: + original_type: type[Any] = type(v) + + # Normalize to list so list_schema can validate each item + if isinstance(v, list): + items: list[_T] = v + else: + try: + items = list(v) + except TypeError as e: + raise TypeError("Value is not iterable") from e + + # Validate items against the inner schema + validated: list[_T] = handler(items) + + # Reconstruct original container type + if original_type is list: + return validated + # str(list) produces the list's repr, not a string built from items, + # so skip reconstruction for str and its subclasses. + if issubclass(original_type, str): + return validated + try: + return original_type(validated) + except (TypeError, ValueError): + # If the type cannot be reconstructed, just return the validated list + return validated + + @staticmethod + def _serialize(v: Iterable[_T]) -> list[_T]: + """Always serialize as a list so Pydantic's JSON encoder is happy.""" + if isinstance(v, list): + return v + return list(v) + + +EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable] + + def _construct_field(value: object, field: FieldInfo, key: str) -> object: if value is None: return field_get_default(field) - if PYDANTIC_V2: - type_ = field.annotation - else: + if PYDANTIC_V1: type_ = cast(type, field.outer_type_) # type: ignore + else: + type_ = field.annotation # type: ignore if type_ is None: raise RuntimeError(f"Unexpected field type is None for {key}") - return construct_type(value=value, type_=type_) + return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) + + +def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: + if PYDANTIC_V1: + # TODO + return None + + schema = cls.__pydantic_core_schema__ + if schema["type"] == "model": + fields = schema["schema"] + if fields["type"] == "model-fields": + extras = fields.get("extras_schema") + if extras and "cls" in extras: + # mypy can't narrow the type + return extras["cls"] # type: ignore[no-any-return] + + return None def is_basemodel(type_: type) -> bool: @@ -417,18 +585,28 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: return cast(_T, construct_type(value=value, type_=type_)) -def construct_type(*, value: object, type_: object) -> object: +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: """Loose coercion to the expected type with construction of nested values. If the given value does not match the expected type then it is returned as-is. """ + + # store a reference to the original type we were given before we extract any inner + # types so that we can properly resolve forward references in `TypeAliasType` annotations + original_type = None + # we allow `object` as the input type because otherwise, passing things like # `Literal['value']` will be reported as a type error by type checkers type_ = cast("type[object]", type_) + if is_type_alias_type(type_): + original_type = type_ # type: ignore[unreachable] + type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(type_): - meta: tuple[Any, ...] = get_args(type_)[1:] + if metadata is not None and len(metadata) > 0: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] type_ = extract_type_arg(type_, 0) else: meta = tuple() @@ -440,7 +618,7 @@ def construct_type(*, value: object, type_: object) -> object: if is_union(origin): try: - return validate_type(type_=cast("type[object]", type_), value=value) + return validate_type(type_=cast("type[object]", original_type or type_), value=value) except Exception: pass @@ -482,7 +660,11 @@ def construct_type(*, value: object, type_: object) -> object: _, items_type = get_args(type_) # Dict[_, items_type] return {key: construct_type(value=item, type_=items_type) for key, item in value.items()} - if not is_literal_type(type_) and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)): + if ( + not is_literal_type(type_) + and inspect.isclass(origin) + and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)) + ): if is_list(value): return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value] @@ -528,6 +710,9 @@ class CachedDiscriminatorType(Protocol): __discriminator__: DiscriminatorDetails +DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary() + + class DiscriminatorDetails: field_name: str """The name of the discriminator field in the variant class, e.g. @@ -570,8 +755,9 @@ def __init__( def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: - if isinstance(union, CachedDiscriminatorType): - return union.__discriminator__ + cached = DISCRIMINATOR_CACHE.get(union) + if cached is not None: + return cached discriminator_field_name: str | None = None @@ -589,30 +775,30 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, for variant in get_args(union): variant = strip_annotated_type(variant) if is_basemodel_type(variant): - if PYDANTIC_V2: - field = _extract_field_schema_pv2(variant, discriminator_field_name) - if not field: + if PYDANTIC_V1: + field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + if not field_info: continue # Note: if one variant defines an alias then they all should - discriminator_alias = field.get("serialization_alias") - - field_schema = field["schema"] + discriminator_alias = field_info.alias - if field_schema["type"] == "literal": - for entry in cast("LiteralSchema", field_schema)["expected"]: + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): if isinstance(entry, str): mapping[entry] = variant else: - field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - if not field_info: + field = _extract_field_schema_pv2(variant, discriminator_field_name) + if not field: continue # Note: if one variant defines an alias then they all should - discriminator_alias = field_info.alias + discriminator_alias = field.get("serialization_alias") - if field_info.annotation and is_literal_type(field_info.annotation): - for entry in get_args(field_info.annotation): + field_schema = field["schema"] + + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: if isinstance(entry, str): mapping[entry] = variant @@ -624,21 +810,24 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, discriminator_field=discriminator_field_name, discriminator_alias=discriminator_alias, ) - cast(CachedDiscriminatorType, union).__discriminator__ = details + DISCRIMINATOR_CACHE.setdefault(union, details) return details def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: schema = model.__pydantic_core_schema__ + if schema["type"] == "definitions": + schema = schema["schema"] + if schema["type"] != "model": return None + schema = cast("ModelSchema", schema) fields_schema = schema["schema"] if fields_schema["type"] != "model-fields": return None fields_schema = cast("ModelFieldsSchema", fields_schema) - field = fields_schema["fields"].get(field_name) if not field: return None @@ -662,7 +851,22 @@ def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None: setattr(typ, "__pydantic_config__", config) # noqa: B010 -# our use of subclasssing here causes weirdness for type checkers, +def add_request_id(obj: BaseModel, request_id: str | None) -> None: + obj._request_id = request_id + + # in Pydantic v1, using setattr like we do above causes the attribute + # to be included when serializing the model which we don't want in this + # case so we need to explicitly exclude it + if PYDANTIC_V1: + try: + exclude_fields = obj.__exclude_fields__ # type: ignore + except AttributeError: + cast(Any, obj).__exclude_fields__ = {"_request_id", "__exclude_fields__"} + else: + cast(Any, obj).__exclude_fields__ = {*(exclude_fields or {}), "_request_id", "__exclude_fields__"} + + +# our use of subclassing here causes weirdness for type checkers, # so we just pretend that we don't subclass if TYPE_CHECKING: GenericModel = BaseModel @@ -672,7 +876,7 @@ class GenericModel(BaseGenericModel, BaseModel): pass -if PYDANTIC_V2: +if not PYDANTIC_V1: from pydantic import TypeAdapter as _TypeAdapter _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter)) @@ -708,6 +912,11 @@ def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]: return RootModel[type_] # type: ignore +class SecurityOptions(TypedDict, total=False): + bearer_auth: bool + admin_api_key_auth: bool + + class FinalRequestOptionsInput(TypedDict, total=False): method: Required[str] url: Required[str] @@ -717,8 +926,12 @@ class FinalRequestOptionsInput(TypedDict, total=False): timeout: float | Timeout | None files: HttpxRequestFiles | None idempotency_key: str + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] json_data: Body extra_json: AnyMapping + follow_redirects: bool + security: SecurityOptions + synthesize_event_and_data: bool @final @@ -732,18 +945,25 @@ class FinalRequestOptions(pydantic.BaseModel): files: Union[HttpxRequestFiles, None] = None idempotency_key: Union[str, None] = None post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() - + follow_redirects: Union[bool, None] = None + security: SecurityOptions = { + "bearer_auth": True, + "admin_api_key_auth": True, + } + synthesize_event_and_data: Optional[bool] = None + + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. json_data: Union[Body, None] = None extra_json: Union[AnyMapping, None] = None - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) - else: + if PYDANTIC_V1: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] arbitrary_types_allowed: bool = True + else: + model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) def get_max_retries(self, max_retries: int) -> int: if isinstance(self.max_retries, NotGiven): @@ -776,9 +996,9 @@ def construct( # type: ignore key: strip_not_given(value) for key, value in values.items() } - if PYDANTIC_V2: - return super().model_construct(_fields_set, **kwargs) - return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + return super().model_construct(_fields_set, **kwargs) if not TYPE_CHECKING: # type checkers incorrectly complain about this assignment diff --git a/src/openai/_module_client.py b/src/openai/_module_client.py index 6f7356eb3c..3554e11e50 100644 --- a/src/openai/_module_client.py +++ b/src/openai/_module_client.py @@ -1,85 +1,189 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from __future__ import annotations + +from typing import TYPE_CHECKING from typing_extensions import override -from . import resources, _load_client +if TYPE_CHECKING: + from .resources.files import Files + from .resources.images import Images + from .resources.models import Models + from .resources.videos import Videos + from .resources.batches import Batches + from .resources.beta.beta import Beta + from .resources.chat.chat import Chat + from .resources.embeddings import Embeddings + from .resources.admin.admin import Admin + from .resources.audio.audio import Audio + from .resources.completions import Completions + from .resources.evals.evals import Evals + from .resources.moderations import Moderations + from .resources.skills.skills import Skills + from .resources.uploads.uploads import Uploads + from .resources.realtime.realtime import Realtime + from .resources.webhooks.webhooks import Webhooks + from .resources.responses.responses import Responses + from .resources.containers.containers import Containers + from .resources.fine_tuning.fine_tuning import FineTuning + from .resources.conversations.conversations import Conversations + from .resources.vector_stores.vector_stores import VectorStores + +from . import _load_client from ._utils import LazyProxy -class ChatProxy(LazyProxy[resources.Chat]): +class ChatProxy(LazyProxy["Chat"]): @override - def __load__(self) -> resources.Chat: + def __load__(self) -> Chat: return _load_client().chat -class BetaProxy(LazyProxy[resources.Beta]): +class BetaProxy(LazyProxy["Beta"]): @override - def __load__(self) -> resources.Beta: + def __load__(self) -> Beta: return _load_client().beta -class FilesProxy(LazyProxy[resources.Files]): +class FilesProxy(LazyProxy["Files"]): @override - def __load__(self) -> resources.Files: + def __load__(self) -> Files: return _load_client().files -class AudioProxy(LazyProxy[resources.Audio]): +class AudioProxy(LazyProxy["Audio"]): @override - def __load__(self) -> resources.Audio: + def __load__(self) -> Audio: return _load_client().audio -class ImagesProxy(LazyProxy[resources.Images]): +class AdminProxy(LazyProxy["Admin"]): + @override + def __load__(self) -> Admin: + return _load_client().admin + + +class EvalsProxy(LazyProxy["Evals"]): @override - def __load__(self) -> resources.Images: + def __load__(self) -> Evals: + return _load_client().evals + + +class ImagesProxy(LazyProxy["Images"]): + @override + def __load__(self) -> Images: return _load_client().images -class ModelsProxy(LazyProxy[resources.Models]): +class ModelsProxy(LazyProxy["Models"]): @override - def __load__(self) -> resources.Models: + def __load__(self) -> Models: return _load_client().models -class BatchesProxy(LazyProxy[resources.Batches]): +class SkillsProxy(LazyProxy["Skills"]): + @override + def __load__(self) -> Skills: + return _load_client().skills + + +class VideosProxy(LazyProxy["Videos"]): + @override + def __load__(self) -> Videos: + return _load_client().videos + + +class BatchesProxy(LazyProxy["Batches"]): @override - def __load__(self) -> resources.Batches: + def __load__(self) -> Batches: return _load_client().batches -class EmbeddingsProxy(LazyProxy[resources.Embeddings]): +class UploadsProxy(LazyProxy["Uploads"]): + @override + def __load__(self) -> Uploads: + return _load_client().uploads + + +class WebhooksProxy(LazyProxy["Webhooks"]): @override - def __load__(self) -> resources.Embeddings: + def __load__(self) -> Webhooks: + return _load_client().webhooks + + +class RealtimeProxy(LazyProxy["Realtime"]): + @override + def __load__(self) -> Realtime: + return _load_client().realtime + + +class ResponsesProxy(LazyProxy["Responses"]): + @override + def __load__(self) -> Responses: + return _load_client().responses + + +class EmbeddingsProxy(LazyProxy["Embeddings"]): + @override + def __load__(self) -> Embeddings: return _load_client().embeddings -class CompletionsProxy(LazyProxy[resources.Completions]): +class ContainersProxy(LazyProxy["Containers"]): + @override + def __load__(self) -> Containers: + return _load_client().containers + + +class CompletionsProxy(LazyProxy["Completions"]): @override - def __load__(self) -> resources.Completions: + def __load__(self) -> Completions: return _load_client().completions -class ModerationsProxy(LazyProxy[resources.Moderations]): +class ModerationsProxy(LazyProxy["Moderations"]): @override - def __load__(self) -> resources.Moderations: + def __load__(self) -> Moderations: return _load_client().moderations -class FineTuningProxy(LazyProxy[resources.FineTuning]): +class FineTuningProxy(LazyProxy["FineTuning"]): @override - def __load__(self) -> resources.FineTuning: + def __load__(self) -> FineTuning: return _load_client().fine_tuning -chat: resources.Chat = ChatProxy().__as_proxied__() -beta: resources.Beta = BetaProxy().__as_proxied__() -files: resources.Files = FilesProxy().__as_proxied__() -audio: resources.Audio = AudioProxy().__as_proxied__() -images: resources.Images = ImagesProxy().__as_proxied__() -models: resources.Models = ModelsProxy().__as_proxied__() -batches: resources.Batches = BatchesProxy().__as_proxied__() -embeddings: resources.Embeddings = EmbeddingsProxy().__as_proxied__() -completions: resources.Completions = CompletionsProxy().__as_proxied__() -moderations: resources.Moderations = ModerationsProxy().__as_proxied__() -fine_tuning: resources.FineTuning = FineTuningProxy().__as_proxied__() +class VectorStoresProxy(LazyProxy["VectorStores"]): + @override + def __load__(self) -> VectorStores: + return _load_client().vector_stores + + +class ConversationsProxy(LazyProxy["Conversations"]): + @override + def __load__(self) -> Conversations: + return _load_client().conversations + + +chat: Chat = ChatProxy().__as_proxied__() +beta: Beta = BetaProxy().__as_proxied__() +files: Files = FilesProxy().__as_proxied__() +audio: Audio = AudioProxy().__as_proxied__() +admin: Admin = AdminProxy().__as_proxied__() +evals: Evals = EvalsProxy().__as_proxied__() +images: Images = ImagesProxy().__as_proxied__() +models: Models = ModelsProxy().__as_proxied__() +skills: Skills = SkillsProxy().__as_proxied__() +videos: Videos = VideosProxy().__as_proxied__() +batches: Batches = BatchesProxy().__as_proxied__() +uploads: Uploads = UploadsProxy().__as_proxied__() +webhooks: Webhooks = WebhooksProxy().__as_proxied__() +realtime: Realtime = RealtimeProxy().__as_proxied__() +responses: Responses = ResponsesProxy().__as_proxied__() +embeddings: Embeddings = EmbeddingsProxy().__as_proxied__() +containers: Containers = ContainersProxy().__as_proxied__() +completions: Completions = CompletionsProxy().__as_proxied__() +moderations: Moderations = ModerationsProxy().__as_proxied__() +fine_tuning: FineTuning = FineTuningProxy().__as_proxied__() +vector_stores: VectorStores = VectorStoresProxy().__as_proxied__() +conversations: Conversations = ConversationsProxy().__as_proxied__() diff --git a/src/openai/_qs.py b/src/openai/_qs.py index 274320ca5e..4127c19c62 100644 --- a/src/openai/_qs.py +++ b/src/openai/_qs.py @@ -2,17 +2,13 @@ from typing import Any, List, Tuple, Union, Mapping, TypeVar from urllib.parse import parse_qs, urlencode -from typing_extensions import Literal, get_args +from typing_extensions import get_args -from ._types import NOT_GIVEN, NotGiven, NotGivenOr +from ._types import NotGiven, ArrayFormat, NestedFormat, not_given from ._utils import flatten _T = TypeVar("_T") - -ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] -NestedFormat = Literal["dots", "brackets"] - PrimitiveData = Union[str, int, float, bool, None] # this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"] # https://github.com/microsoft/pyright/issues/3555 @@ -41,8 +37,8 @@ def stringify( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> str: return urlencode( self.stringify_items( @@ -56,8 +52,8 @@ def stringify_items( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> list[tuple[str, str]]: opts = Options( qs=self, @@ -101,7 +97,10 @@ def _stringify_item( items.extend(self._stringify_item(key, item, opts)) return items elif array_format == "indices": - raise NotImplementedError("The array indices format is not supported yet") + items = [] + for i, item in enumerate(value): + items.extend(self._stringify_item(f"{key}[{i}]", item, opts)) + return items elif array_format == "brackets": items = [] key = key + "[]" @@ -143,8 +142,8 @@ def __init__( self, qs: Querystring = _qs, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> None: self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format diff --git a/src/openai/_response.py b/src/openai/_response.py index f9d91786f6..f286d38e6c 100644 --- a/src/openai/_response.py +++ b/src/openai/_response.py @@ -25,8 +25,8 @@ import pydantic from ._types import NoneType -from ._utils import is_given, extract_type_arg, is_annotated_type, extract_type_var_from_base -from ._models import BaseModel, is_basemodel +from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base +from ._models import BaseModel, is_basemodel, add_request_id from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type from ._exceptions import OpenAIError, APIResponseValidationError @@ -126,9 +126,17 @@ def __repr__(self) -> str: ) def _parse(self, *, to: type[_T] | None = None) -> R | _T: + cast_to = to if to is not None else self._cast_to + + # unwrap `TypeAlias('Name', T)` -> `T` + if is_type_alias_type(cast_to): + cast_to = cast_to.__value__ # type: ignore[unreachable] + # unwrap `Annotated[T, ...]` -> `T` - if to and is_annotated_type(to): - to = extract_type_arg(to, 0) + if cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) + + origin = get_origin(cast_to) or cast_to if self._is_sse_stream: if to: @@ -144,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -154,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -164,18 +174,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: return cast( R, stream_cls( - cast_to=self._cast_to, + cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) - cast_to = to if to is not None else self._cast_to - - # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(cast_to): - cast_to = extract_type_arg(cast_to, 0) - if cast_to is NoneType: return cast(R, None) @@ -192,7 +197,8 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == float: return cast(R, float(response.text)) - origin = get_origin(cast_to) or cast_to + if cast_to == bool: + return cast(R, response.text.lower() == "true") # handle the legacy binary response case if inspect.isclass(cast_to) and cast_to.__name__ == "HttpxBinaryResponseContent": @@ -211,7 +217,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") return cast(R, response) - if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + if ( + inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) + and not issubclass(origin, BaseModel) + and issubclass(origin, pydantic.BaseModel) + ): raise TypeError("Pydantic models must subclass our base model type, e.g. `from openai import BaseModel`") if ( @@ -228,7 +240,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: # split is required to handle cases where additional information is included # in the response, e.g. application/json; charset=utf-8 content_type, *_ = response.headers.get("content-type", "*").split(";") - if content_type != "application/json": + if not content_type.endswith("json"): if is_basemodel(cast_to): try: data = response.json() @@ -315,8 +327,11 @@ class MyModel(BaseModel): if is_given(self._options.post_parser): parsed = self._options.post_parser(parsed) + if isinstance(parsed, BaseModel): + add_request_id(parsed, self.request_id) + self._parsed_by_type[cache_key] = parsed - return parsed + return cast(R, parsed) def read(self) -> bytes: """Read and return the binary response content.""" @@ -419,8 +434,11 @@ class MyModel(BaseModel): if is_given(self._options.post_parser): parsed = self._options.post_parser(parsed) + if isinstance(parsed, BaseModel): + add_request_id(parsed, self.request_id) + self._parsed_by_type[cache_key] = parsed - return parsed + return cast(R, parsed) async def read(self) -> bytes: """Read and return the binary response content.""" diff --git a/src/openai/_send_queue.py b/src/openai/_send_queue.py new file mode 100644 index 0000000000..b35d0fbcba --- /dev/null +++ b/src/openai/_send_queue.py @@ -0,0 +1,90 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import typing +import threading + +from ._exceptions import WebSocketQueueFullError + + +class SendQueue: + """Bounded byte-size queue for outgoing WebSocket messages. + + Messages are stored as pre-serialized strings. The queue enforces a + maximum byte budget so that unbounded buffering cannot occur during + reconnection windows. + """ + + def __init__(self, max_bytes: int = 1_048_576) -> None: + self._queue: list[tuple[str, int]] = [] # (data, byte_length) + self._bytes: int = 0 + self._max_bytes = max_bytes + self._lock = threading.Lock() + + def enqueue(self, data: str) -> None: + """Append *data* to the queue. + + Raises :class:`WebSocketQueueFullError` if the message would + exceed the byte-size limit. + """ + byte_length = len(data.encode("utf-8")) + with self._lock: + if self._bytes + byte_length > self._max_bytes: + raise WebSocketQueueFullError("send queue is full, message discarded") + self._queue.append((data, byte_length)) + self._bytes += byte_length + + def flush_sync(self, send: typing.Callable[[str], object]) -> None: + """Send every queued message via *send*. + + If *send* raises, the failing message and all subsequent messages + are re-queued and the error is re-raised. + """ + with self._lock: + pending = list(self._queue) + self._queue.clear() + self._bytes = 0 + + for i, (data, _byte_length) in enumerate(pending): + try: + send(data) + except Exception: + with self._lock: + remaining = pending[i:] + self._queue = remaining + self._queue + self._bytes = sum(bl for _, bl in self._queue) + raise + + async def flush_async(self, send: typing.Callable[[str], typing.Awaitable[object]]) -> None: + """Async variant of :meth:`flush_sync`.""" + with self._lock: + pending = list(self._queue) + self._queue.clear() + self._bytes = 0 + + for i, (data, _byte_length) in enumerate(pending): + try: + await send(data) + except Exception: + with self._lock: + remaining = pending[i:] + self._queue = remaining + self._queue + self._bytes = sum(bl for _, bl in self._queue) + raise + + def drain(self) -> list[str]: + """Remove and return all queued messages.""" + with self._lock: + items = [data for data, _ in self._queue] + self._queue.clear() + self._bytes = 0 + return items + + def __len__(self) -> int: + with self._lock: + return len(self._queue) + + def __bool__(self) -> bool: + with self._lock: + return len(self._queue) > 0 diff --git a/src/openai/_streaming.py b/src/openai/_streaming.py index 0fda992cff..45c13cc11d 100644 --- a/src/openai/_streaming.py +++ b/src/openai/_streaming.py @@ -4,7 +4,7 @@ import json import inspect from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -14,6 +14,7 @@ if TYPE_CHECKING: from ._client import OpenAI, AsyncOpenAI + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -23,7 +24,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -32,10 +33,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: OpenAI, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -55,50 +58,56 @@ def __stream__(self) -> Iterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - for sse in iterator: - if sse.data.startswith("[DONE]"): - break - - if sse.event is None: - data = sse.json() - if is_mapping(data) and data.get("error"): - message = None - error = data.get("error") - if is_mapping(error): - message = error.get("message") - if not message or not isinstance(message, str): - message = "An error occurred during streaming" - - raise APIError( - message=message, - request=self.response.request, - body=data["error"], + try: + for sse in iterator: + if sse.data.startswith("[DONE]"): + break + + # we have to special case the Assistants `thread.` events since we won't have an "event" key in the data + if sse.event and sse.event.startswith("thread."): + data = sse.json() + + if sse.event == "error" and is_mapping(data) and data.get("error"): + message = None + error = data.get("error") + if is_mapping(error): + message = error.get("message") + if not message or not isinstance(message, str): + message = "An error occurred during streaming" + + raise APIError( + message=message, + request=self.response.request, + body=data["error"], + ) + + yield process_data(data={"data": data, "event": sse.event}, cast_to=cast_to, response=response) + else: + data = sse.json() + if is_mapping(data) and data.get("error"): + message = None + error = data.get("error") + if is_mapping(error): + message = error.get("message") + if not message or not isinstance(message, str): + message = "An error occurred during streaming" + + raise APIError( + message=message, + request=self.response.request, + body=data["error"], + ) + + yield process_data( + data={"data": data, "event": sse.event} + if self._options is not None and self._options.synthesize_event_and_data + else data, + cast_to=cast_to, + response=response, ) - - yield process_data(data=data, cast_to=cast_to, response=response) - - else: - data = sse.json() - - if sse.event == "error" and is_mapping(data) and data.get("error"): - message = None - error = data.get("error") - if is_mapping(error): - message = error.get("message") - if not message or not isinstance(message, str): - message = "An error occurred during streaming" - - raise APIError( - message=message, - request=self.response.request, - body=data["error"], - ) - - yield process_data(data={"data": data, "event": sse.event}, cast_to=cast_to, response=response) - - # Ensure the entire stream is consumed - for _sse in iterator: - ... + finally: + # Ensure the response is closed even if the consumer doesn't read all data + response.close() def __enter__(self) -> Self: return self @@ -124,7 +133,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -133,10 +142,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncOpenAI, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -157,50 +168,56 @@ async def __stream__(self) -> AsyncIterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - async for sse in iterator: - if sse.data.startswith("[DONE]"): - break - - if sse.event is None: - data = sse.json() - if is_mapping(data) and data.get("error"): - message = None - error = data.get("error") - if is_mapping(error): - message = error.get("message") - if not message or not isinstance(message, str): - message = "An error occurred during streaming" - - raise APIError( - message=message, - request=self.response.request, - body=data["error"], + try: + async for sse in iterator: + if sse.data.startswith("[DONE]"): + break + + # we have to special case the Assistants `thread.` events since we won't have an "event" key in the data + if sse.event and sse.event.startswith("thread."): + data = sse.json() + + if sse.event == "error" and is_mapping(data) and data.get("error"): + message = None + error = data.get("error") + if is_mapping(error): + message = error.get("message") + if not message or not isinstance(message, str): + message = "An error occurred during streaming" + + raise APIError( + message=message, + request=self.response.request, + body=data["error"], + ) + + yield process_data(data={"data": data, "event": sse.event}, cast_to=cast_to, response=response) + else: + data = sse.json() + if is_mapping(data) and data.get("error"): + message = None + error = data.get("error") + if is_mapping(error): + message = error.get("message") + if not message or not isinstance(message, str): + message = "An error occurred during streaming" + + raise APIError( + message=message, + request=self.response.request, + body=data["error"], + ) + + yield process_data( + data={"data": data, "event": sse.event} + if self._options is not None and self._options.synthesize_event_and_data + else data, + cast_to=cast_to, + response=response, ) - - yield process_data(data=data, cast_to=cast_to, response=response) - - else: - data = sse.json() - - if sse.event == "error" and is_mapping(data) and data.get("error"): - message = None - error = data.get("error") - if is_mapping(error): - message = error.get("message") - if not message or not isinstance(message, str): - message = "An error occurred during streaming" - - raise APIError( - message=message, - request=self.response.request, - body=data["error"], - ) - - yield process_data(data={"data": data, "event": sse.event}, cast_to=cast_to, response=response) - - # Ensure the entire stream is consumed - async for _sse in iterator: - ... + finally: + # Ensure the response is closed even if the consumer doesn't read all data + await response.aclose() async def __aenter__(self) -> Self: return self diff --git a/src/openai/_types.py b/src/openai/_types.py index 5611b2d38f..9936b00f73 100644 --- a/src/openai/_types.py +++ b/src/openai/_types.py @@ -13,17 +13,30 @@ Mapping, TypeVar, Callable, + Iterable, + Iterator, Optional, Sequence, + AsyncIterable, +) +from typing_extensions import ( + Set, + Literal, + Protocol, + TypeAlias, + TypedDict, + SupportsIndex, + overload, + override, + runtime_checkable, ) -from typing_extensions import Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable import httpx import pydantic from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport if TYPE_CHECKING: - from ._models import BaseModel + from ._models import BaseModel, SecurityOptions from ._response import APIResponse, AsyncAPIResponse from ._legacy_response import HttpxBinaryResponseContent @@ -35,6 +48,9 @@ ModelT = TypeVar("ModelT", bound=pydantic.BaseModel) _T = TypeVar("_T") +ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] +NestedFormat = Literal["dots", "brackets"] + # Approximates httpx internal ProxiesTypes and RequestFiles types # while adding support for `PathLike` instances @@ -46,6 +62,13 @@ else: Base64FileInput = Union[IO[bytes], PathLike] FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. + + +# Used for sending raw binary data / streaming data in request bodies +# e.g. for file uploads without multipart encoding +BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]] +AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]] + FileTypes = Union[ # file (or bytes) FileContent, @@ -101,23 +124,29 @@ class RequestOptions(TypedDict, total=False): params: Query extra_json: AnyMapping idempotency_key: str + follow_redirects: bool + security: SecurityOptions + synthesize_event_and_data: bool # Sentinel class used until PEP 0661 is accepted class NotGiven: """ - A sentinel singleton class used to distinguish omitted keyword arguments - from those passed in with the value None (which may have different behavior). + For parameters with a meaningful None value, we need to distinguish between + the user explicitly passing None, and the user not passing the parameter at + all. + + User code shouldn't need to use not_given directly. For example: ```py - def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ... + def create(timeout: Timeout | None | NotGiven = not_given): ... - get(timeout=1) # 1s timeout - get(timeout=None) # No timeout - get() # Default timeout behavior, which may not be statically known at the method definition. + create(timeout=1) # 1s timeout + create(timeout=None) # No timeout + create() # Default timeout behavior ``` """ @@ -129,13 +158,14 @@ def __repr__(self) -> str: return "NOT_GIVEN" -NotGivenOr = Union[_T, NotGiven] +not_given = NotGiven() +# for backwards compatibility: NOT_GIVEN = NotGiven() class Omit: - """In certain situations you need to be able to represent a case where a default value has - to be explicitly removed and `None` is not an appropriate substitute, for example: + """ + To explicitly omit something from being sent in a request, use `omit`. ```py # as the default `Content-Type` header is `application/json` that will be sent @@ -145,8 +175,8 @@ class Omit: # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' client.post(..., headers={"Content-Type": "multipart/form-data"}) - # instead you can remove the default `application/json` header by passing Omit - client.post(..., headers={"Content-Type": Omit()}) + # instead you can remove the default `application/json` header by passing omit + client.post(..., headers={"Content-Type": omit}) ``` """ @@ -154,6 +184,11 @@ def __bool__(self) -> Literal[False]: return False +omit = Omit() + +Omittable = Union[_T, Omit] + + @runtime_checkable class ModelBuilderProtocol(Protocol): @classmethod @@ -194,8 +229,8 @@ def get(self, __key: str) -> str | None: ... StrBytesIntFloat = Union[str, bytes, int, float] # Note: copied from Pydantic -# https://github.com/pydantic/pydantic/blob/32ea570bf96e84234d2992e1ddf40ab8a565925a/pydantic/main.py#L49 -IncEx: TypeAlias = "set[int] | set[str] | dict[int, Any] | dict[str, Any] | None" +# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79 +IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]] PostParser = Callable[[Any], Any] @@ -217,3 +252,28 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth + follow_redirects: bool + + +_T_co = TypeVar("_T_co", covariant=True) + + +if TYPE_CHECKING: + # This works because str.__contains__ does not accept object (either in typeshed or at runtime) + # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + # + # Note: index() and count() methods are intentionally omitted to allow pyright to properly + # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. + class SequenceNotStr(Protocol[_T_co]): + @overload + def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... + @overload + def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... + def __contains__(self, value: object, /) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T_co]: ... + def __reversed__(self) -> Iterator[_T_co]: ... +else: + # just point this to a normal `Sequence` at runtime to avoid having to special case + # deserializing our custom sequence type + SequenceNotStr = Sequence diff --git a/src/openai/_utils/__init__.py b/src/openai/_utils/__init__.py index 3efe66c8e8..bbd79691fa 100644 --- a/src/openai/_utils/__init__.py +++ b/src/openai/_utils/__init__.py @@ -1,3 +1,5 @@ +from ._logs import SensitiveHeadersFilter as SensitiveHeadersFilter +from ._path import path_template as path_template from ._sync import asyncify as asyncify from ._proxy import LazyProxy as LazyProxy from ._utils import ( @@ -6,10 +8,10 @@ is_list as is_list, is_given as is_given, is_tuple as is_tuple, + json_safe as json_safe, lru_cache as lru_cache, is_mapping as is_mapping, is_tuple_t as is_tuple_t, - parse_date as parse_date, is_iterable as is_iterable, is_sequence as is_sequence, coerce_float as coerce_float, @@ -22,14 +24,21 @@ coerce_boolean as coerce_boolean, coerce_integer as coerce_integer, file_from_path as file_from_path, - parse_datetime as parse_datetime, + is_azure_client as is_azure_client, strip_not_given as strip_not_given, - deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, maybe_coerce_float as maybe_coerce_float, get_required_header as get_required_header, maybe_coerce_boolean as maybe_coerce_boolean, maybe_coerce_integer as maybe_coerce_integer, + is_async_azure_client as is_async_azure_client, +) +from ._compat import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + is_typeddict as is_typeddict, + is_literal_type as is_literal_type, ) from ._typing import ( is_list_type as is_list_type, @@ -37,7 +46,9 @@ extract_type_arg as extract_type_arg, is_iterable_type as is_iterable_type, is_required_type as is_required_type, + is_sequence_type as is_sequence_type, is_annotated_type as is_annotated_type, + is_type_alias_type as is_type_alias_type, strip_annotated_type as strip_annotated_type, extract_type_var_from_base as extract_type_var_from_base, ) @@ -53,3 +64,4 @@ function_has_argument as function_has_argument, assert_signatures_in_sync as assert_signatures_in_sync, ) +from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime diff --git a/src/openai/_utils/_compat.py b/src/openai/_utils/_compat.py new file mode 100644 index 0000000000..2c70b299ce --- /dev/null +++ b/src/openai/_utils/_compat.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import sys +import typing_extensions +from typing import Any, Type, Union, Literal, Optional +from datetime import date, datetime +from typing_extensions import get_args as _get_args, get_origin as _get_origin + +from .._types import StrBytesIntFloat +from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime + +_LITERAL_TYPES = {Literal, typing_extensions.Literal} + + +def get_args(tp: type[Any]) -> tuple[Any, ...]: + return _get_args(tp) + + +def get_origin(tp: type[Any]) -> type[Any] | None: + return _get_origin(tp) + + +def is_union(tp: Optional[Type[Any]]) -> bool: + if sys.version_info < (3, 10): + return tp is Union # type: ignore[comparison-overlap] + else: + import types + + return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap] + + +def is_typeddict(tp: Type[Any]) -> bool: + return typing_extensions.is_typeddict(tp) + + +def is_literal_type(tp: Type[Any]) -> bool: + return get_origin(tp) in _LITERAL_TYPES + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + return _parse_date(value) + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + return _parse_datetime(value) diff --git a/src/openai/_utils/_datetime_parse.py b/src/openai/_utils/_datetime_parse.py new file mode 100644 index 0000000000..7cb9d9e668 --- /dev/null +++ b/src/openai/_utils/_datetime_parse.py @@ -0,0 +1,136 @@ +""" +This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py +without the Pydantic v1 specific errors. +""" + +from __future__ import annotations + +import re +from typing import Dict, Union, Optional +from datetime import date, datetime, timezone, timedelta + +from .._types import StrBytesIntFloat + +date_expr = r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})" +time_expr = ( + r"(?P\d{1,2}):(?P\d{1,2})" + r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?" + r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$" +) + +date_re = re.compile(f"{date_expr}$") +datetime_re = re.compile(f"{date_expr}[T ]{time_expr}") + + +EPOCH = datetime(1970, 1, 1) +# if greater than this, the number is in ms, if less than or equal it's in seconds +# (in seconds this is 11th October 2603, in ms it's 20th August 1970) +MS_WATERSHED = int(2e10) +# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9 +MAX_NUMBER = int(3e20) + + +def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]: + if isinstance(value, (int, float)): + return value + try: + return float(value) + except ValueError: + return None + except TypeError: + raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None + + +def _from_unix_seconds(seconds: Union[int, float]) -> datetime: + if seconds > MAX_NUMBER: + return datetime.max + elif seconds < -MAX_NUMBER: + return datetime.min + + while abs(seconds) > MS_WATERSHED: + seconds /= 1000 + dt = EPOCH + timedelta(seconds=seconds) + return dt.replace(tzinfo=timezone.utc) + + +def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]: + if value == "Z": + return timezone.utc + elif value is not None: + offset_mins = int(value[-2:]) if len(value) > 3 else 0 + offset = 60 * int(value[1:3]) + offset_mins + if value[0] == "-": + offset = -offset + return timezone(timedelta(minutes=offset)) + else: + return None + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + """ + Parse a datetime/int/float/string and return a datetime.datetime. + + This function supports time zone offsets. When the input contains one, + the output uses a timezone with a fixed offset from UTC. + + Raise ValueError if the input is well formatted but not a valid datetime. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, datetime): + return value + + number = _get_numeric(value, "datetime") + if number is not None: + return _from_unix_seconds(number) + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + + match = datetime_re.match(value) + if match is None: + raise ValueError("invalid datetime format") + + kw = match.groupdict() + if kw["microsecond"]: + kw["microsecond"] = kw["microsecond"].ljust(6, "0") + + tzinfo = _parse_timezone(kw.pop("tzinfo")) + kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None} + kw_["tzinfo"] = tzinfo + + return datetime(**kw_) # type: ignore + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + """ + Parse a date/int/float/string and return a datetime.date. + + Raise ValueError if the input is well formatted but not a valid date. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, date): + if isinstance(value, datetime): + return value.date() + else: + return value + + number = _get_numeric(value, "date") + if number is not None: + return _from_unix_seconds(number).date() + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + match = date_re.match(value) + if match is None: + raise ValueError("invalid date format") + + kw = {k: int(v) for k, v in match.groupdict().items()} + + try: + return date(**kw) + except ValueError: + raise ValueError("invalid date format") from None diff --git a/src/openai/_utils/_json.py b/src/openai/_utils/_json.py new file mode 100644 index 0000000000..60584214af --- /dev/null +++ b/src/openai/_utils/_json.py @@ -0,0 +1,35 @@ +import json +from typing import Any +from datetime import datetime +from typing_extensions import override + +import pydantic + +from .._compat import model_dump + + +def openapi_dumps(obj: Any) -> bytes: + """ + Serialize an object to UTF-8 encoded JSON bytes. + + Extends the standard json.dumps with support for additional types + commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. + """ + return json.dumps( + obj, + cls=_CustomEncoder, + # Uses the same defaults as httpx's JSON serialization + ensure_ascii=False, + separators=(",", ":"), + allow_nan=False, + ).encode() + + +class _CustomEncoder(json.JSONEncoder): + @override + def default(self, o: Any) -> Any: + if isinstance(o, datetime): + return o.isoformat() + if isinstance(o, pydantic.BaseModel): + return model_dump(o, exclude_unset=True, mode="json", by_alias=True) + return super().default(o) diff --git a/src/openai/_utils/_logs.py b/src/openai/_utils/_logs.py index e5113fd8c0..376946933c 100644 --- a/src/openai/_utils/_logs.py +++ b/src/openai/_utils/_logs.py @@ -1,10 +1,16 @@ import os import logging +from typing_extensions import override + +from ._utils import is_dict logger: logging.Logger = logging.getLogger("openai") httpx_logger: logging.Logger = logging.getLogger("httpx") +SENSITIVE_HEADERS = {"api-key", "authorization"} + + def _basic_config() -> None: # e.g. [2023-10-05 14:12:26 - openai._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK" logging.basicConfig( @@ -23,3 +29,14 @@ def setup_logging() -> None: _basic_config() logger.setLevel(logging.INFO) httpx_logger.setLevel(logging.INFO) + + +class SensitiveHeadersFilter(logging.Filter): + @override + def filter(self, record: logging.LogRecord) -> bool: + if is_dict(record.args) and "headers" in record.args and is_dict(record.args["headers"]): + headers = record.args["headers"] = {**record.args["headers"]} + for header in headers: + if str(header).lower() in SENSITIVE_HEADERS: + headers[header] = "" + return True diff --git a/src/openai/_utils/_path.py b/src/openai/_utils/_path.py new file mode 100644 index 0000000000..4d6e1e4cbc --- /dev/null +++ b/src/openai/_utils/_path.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import re +from typing import ( + Any, + Mapping, + Callable, +) +from urllib.parse import quote + +# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E). +_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$") + +_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}") + + +def _quote_path_segment_part(value: str) -> str: + """Percent-encode `value` for use in a URI path segment. + + Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + """ + # quote() already treats unreserved characters (letters, digits, and -._~) + # as safe, so we only need to add sub-delims, ':', and '@'. + # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted. + return quote(value, safe="!$&'()*+,;=:@") + + +def _quote_query_part(value: str) -> str: + """Percent-encode `value` for use in a URI query string. + + Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.4 + """ + return quote(value, safe="!$'()*+,;:@/?") + + +def _quote_fragment_part(value: str) -> str: + """Percent-encode `value` for use in a URI fragment. + + Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.5 + """ + return quote(value, safe="!$&'()*+,;=:@/?") + + +def _interpolate( + template: str, + values: Mapping[str, Any], + quoter: Callable[[str], str], +) -> str: + """Replace {name} placeholders in `template`, quoting each value with `quoter`. + + Placeholder names are looked up in `values`. + + Raises: + KeyError: If a placeholder is not found in `values`. + """ + # re.split with a capturing group returns alternating + # [text, name, text, name, ..., text] elements. + parts = _PLACEHOLDER_RE.split(template) + + for i in range(1, len(parts), 2): + name = parts[i] + if name not in values: + raise KeyError(f"a value for placeholder {{{name}}} was not provided") + val = values[name] + if val is None: + parts[i] = "null" + elif isinstance(val, bool): + parts[i] = "true" if val else "false" + else: + parts[i] = quoter(str(values[name])) + + return "".join(parts) + + +def path_template(template: str, /, **kwargs: Any) -> str: + """Interpolate {name} placeholders in `template` from keyword arguments. + + Args: + template: The template string containing {name} placeholders. + **kwargs: Keyword arguments to interpolate into the template. + + Returns: + The template with placeholders interpolated and percent-encoded. + + Safe characters for percent-encoding are dependent on the URI component. + Placeholders in path and fragment portions are percent-encoded where the `segment` + and `fragment` sets from RFC 3986 respectively are considered safe. + Placeholders in the query portion are percent-encoded where the `query` set from + RFC 3986 §3.3 is considered safe except for = and & characters. + + Raises: + KeyError: If a placeholder is not found in `kwargs`. + ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments). + """ + # Split the template into path, query, and fragment portions. + fragment_template: str | None = None + query_template: str | None = None + + rest = template + if "#" in rest: + rest, fragment_template = rest.split("#", 1) + if "?" in rest: + rest, query_template = rest.split("?", 1) + path_template = rest + + # Interpolate each portion with the appropriate quoting rules. + path_result = _interpolate(path_template, kwargs, _quote_path_segment_part) + + # Reject dot-segments (. and ..) in the final assembled path. The check + # runs after interpolation so that adjacent placeholders or a mix of static + # text and placeholders that together form a dot-segment are caught. + # Also reject percent-encoded dot-segments to protect against incorrectly + # implemented normalization in servers/proxies. + for segment in path_result.split("/"): + if _DOT_SEGMENT_RE.match(segment): + raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed") + + result = path_result + if query_template is not None: + result += "?" + _interpolate(query_template, kwargs, _quote_query_part) + if fragment_template is not None: + result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part) + + return result diff --git a/src/openai/_utils/_proxy.py b/src/openai/_utils/_proxy.py index ffd883e9dd..0f239a33c6 100644 --- a/src/openai/_utils/_proxy.py +++ b/src/openai/_utils/_proxy.py @@ -46,7 +46,10 @@ def __dir__(self) -> Iterable[str]: @property # type: ignore @override def __class__(self) -> type: # pyright: ignore - proxied = self.__get_proxied__() + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) if issubclass(type(proxied), LazyProxy): return type(proxied) return proxied.__class__ diff --git a/src/openai/_utils/_reflection.py b/src/openai/_utils/_reflection.py index 89aa712ac4..bdaca29e4a 100644 --- a/src/openai/_utils/_reflection.py +++ b/src/openai/_utils/_reflection.py @@ -15,6 +15,7 @@ def assert_signatures_in_sync( check_func: Callable[..., Any], *, exclude_params: set[str] = set(), + description: str = "", ) -> None: """Ensure that the signature of the second function matches the first.""" @@ -39,4 +40,6 @@ def assert_signatures_in_sync( continue if errors: - raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors)) + raise AssertionError( + f"{len(errors)} errors encountered when comparing signatures{description}:\n\n" + "\n\n".join(errors) + ) diff --git a/src/openai/_utils/_resources_proxy.py b/src/openai/_utils/_resources_proxy.py new file mode 100644 index 0000000000..e5b9ec7a37 --- /dev/null +++ b/src/openai/_utils/_resources_proxy.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Any +from typing_extensions import override + +from ._proxy import LazyProxy + + +class ResourcesProxy(LazyProxy[Any]): + """A proxy for the `openai.resources` module. + + This is used so that we can lazily import `openai.resources` only when + needed *and* so that users can just import `openai` and reference `openai.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("openai.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() diff --git a/src/openai/_utils/_sync.py b/src/openai/_utils/_sync.py index d0d810337e..f6027c183d 100644 --- a/src/openai/_utils/_sync.py +++ b/src/openai/_utils/_sync.py @@ -1,56 +1,49 @@ from __future__ import annotations +import asyncio import functools from typing import TypeVar, Callable, Awaitable from typing_extensions import ParamSpec import anyio +import sniffio import anyio.to_thread -from ._reflection import function_has_argument - T_Retval = TypeVar("T_Retval") T_ParamSpec = ParamSpec("T_ParamSpec") -# copied from `asyncer`, https://github.com/tiangolo/asyncer -def asyncify( - function: Callable[T_ParamSpec, T_Retval], - *, - cancellable: bool = False, - limiter: anyio.CapacityLimiter | None = None, -) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: +async def to_thread( + func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs +) -> T_Retval: + if sniffio.current_async_library() == "asyncio": + return await asyncio.to_thread(func, *args, **kwargs) + + return await anyio.to_thread.run_sync( + functools.partial(func, *args, **kwargs), + ) + + +# inspired by `asyncer`, https://github.com/tiangolo/asyncer +def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: """ Take a blocking function and create an async one that receives the same - positional and keyword arguments, and that when called, calls the original function - in a worker thread using `anyio.to_thread.run_sync()`. Internally, - `asyncer.asyncify()` uses the same `anyio.to_thread.run_sync()`, but it supports - keyword arguments additional to positional arguments and it adds better support for - autocompletion and inline errors for the arguments of the function called and the - return value. - - If the `cancellable` option is enabled and the task waiting for its completion is - cancelled, the thread will still run its course but its return value (or any raised - exception) will be ignored. + positional and keyword arguments. - Use it like this: + Usage: - ```Python - def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: - # Do work - return "Some result" + ```python + def blocking_func(arg1, arg2, kwarg1=None): + # blocking code + return result - result = await to_thread.asyncify(do_work)("spam", "ham", kwarg1="a", kwarg2="b") - print(result) + result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1) ``` ## Arguments `function`: a blocking regular callable (e.g. a function) - `cancellable`: `True` to allow cancellation of the operation - `limiter`: capacity limiter to use to limit the total amount of threads running - (if omitted, the default limiter is used) ## Return @@ -60,22 +53,6 @@ def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: """ async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval: - partial_f = functools.partial(function, *args, **kwargs) - - # In `v4.1.0` anyio added the `abandon_on_cancel` argument and deprecated the old - # `cancellable` argument, so we need to use the new `abandon_on_cancel` to avoid - # surfacing deprecation warnings. - if function_has_argument(anyio.to_thread.run_sync, "abandon_on_cancel"): - return await anyio.to_thread.run_sync( - partial_f, - abandon_on_cancel=cancellable, - limiter=limiter, - ) - - return await anyio.to_thread.run_sync( - partial_f, - cancellable=cancellable, - limiter=limiter, - ) + return await to_thread(function, *args, **kwargs) return wrapper diff --git a/src/openai/_utils/_transform.py b/src/openai/_utils/_transform.py index 47e262a515..414f38c340 100644 --- a/src/openai/_utils/_transform.py +++ b/src/openai/_utils/_transform.py @@ -5,27 +5,31 @@ import pathlib from typing import Any, Mapping, TypeVar, cast from datetime import date, datetime -from typing_extensions import Literal, get_args, override, get_type_hints +from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints import anyio import pydantic from ._utils import ( is_list, + is_given, + lru_cache, is_mapping, is_iterable, + is_sequence, ) from .._files import is_base64_file_input +from ._compat import get_origin, is_typeddict from ._typing import ( is_list_type, is_union_type, extract_type_arg, is_iterable_type, is_required_type, + is_sequence_type, is_annotated_type, strip_annotated_type, ) -from .._compat import model_dump, is_typeddict _T = TypeVar("_T") @@ -108,6 +112,7 @@ class Params(TypedDict, total=False): return cast(_T, transformed) +@lru_cache(maxsize=8096) def _get_annotated_type(type_: type) -> type | None: """If the given type is an `Annotated` type then it is returned, if not `None` is returned. @@ -126,7 +131,7 @@ def _get_annotated_type(type_: type) -> type | None: def _maybe_transform_key(key: str, type_: type) -> str: """Transform the given `data` based on the annotations provided in `type_`. - Note: this function only looks at `Annotated` types that contain `PropertInfo` metadata. + Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata. """ annotated_type = _get_annotated_type(type_) if annotated_type is None: @@ -142,6 +147,10 @@ def _maybe_transform_key(key: str, type_: type) -> str: return key +def _no_transform_needed(annotation: type) -> bool: + return annotation == float or annotation == int + + def _transform_recursive( data: object, *, @@ -160,20 +169,43 @@ def _transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return _transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] if is_union_type(stripped_type): @@ -186,7 +218,7 @@ def _transform_recursive( return data if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True) + return model_dump(data, exclude_unset=True, mode="json", exclude=getattr(data, "__api_exclude__", None)) annotated_type = _get_annotated_type(annotation) if annotated_type is None: @@ -235,6 +267,11 @@ def _transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include omitted values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -298,20 +335,43 @@ async def _async_transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return await _async_transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] if is_union_type(stripped_type): @@ -324,7 +384,7 @@ async def _async_transform_recursive( return data if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True) + return model_dump(data, exclude_unset=True, mode="json") annotated_type = _get_annotated_type(annotation) if annotated_type is None: @@ -373,6 +433,11 @@ async def _async_transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include omitted values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -380,3 +445,13 @@ async def _async_transform_typeddict( else: result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) return result + + +@lru_cache(maxsize=8096) +def get_type_hints( + obj: Any, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, +) -> dict[str, Any]: + return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) diff --git a/src/openai/_utils/_typing.py b/src/openai/_utils/_typing.py index c036991f04..193109f3ad 100644 --- a/src/openai/_utils/_typing.py +++ b/src/openai/_utils/_typing.py @@ -1,11 +1,21 @@ from __future__ import annotations +import sys +import typing +import typing_extensions from typing import Any, TypeVar, Iterable, cast from collections import abc as _c_abc -from typing_extensions import Required, Annotated, get_args, get_origin - +from typing_extensions import ( + TypeIs, + Required, + Annotated, + get_args, + get_origin, +) + +from ._utils import lru_cache from .._types import InheritsGeneric -from .._compat import is_union as _is_union +from ._compat import is_union as _is_union def is_annotated_type(typ: type) -> bool: @@ -16,6 +26,11 @@ def is_list_type(typ: type) -> bool: return (get_origin(typ) or typ) == list +def is_sequence_type(typ: type) -> bool: + origin = get_origin(typ) or typ + return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence + + def is_iterable_type(typ: type) -> bool: """If the given type is `typing.Iterable[T]`""" origin = get_origin(typ) or typ @@ -36,7 +51,28 @@ def is_typevar(typ: type) -> bool: return type(typ) == TypeVar # type: ignore +_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,) +if sys.version_info >= (3, 12): + _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) + + +def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: + """Return whether the provided argument is an instance of `TypeAliasType`. + + ```python + type Int = int + is_type_alias_type(Int) + # > True + Str = TypeAliasType("Str", str) + is_type_alias_type(Str) + # > True + ``` + """ + return isinstance(tp, _TYPE_ALIAS_TYPES) + + # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] +@lru_cache(maxsize=8096) def strip_annotated_type(typ: type) -> type: if is_required_type(typ) or is_annotated_type(typ): return strip_annotated_type(cast(type, get_args(typ)[0])) @@ -79,7 +115,7 @@ class MyResponse(Foo[_T]): ``` """ cls = cast(object, get_origin(typ) or typ) - if cls in generic_bases: + if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] # we're given the class directly return extract_type_arg(typ, index) diff --git a/src/openai/_utils/_utils.py b/src/openai/_utils/_utils.py index 0bba17caad..9f7401ca83 100644 --- a/src/openai/_utils/_utils.py +++ b/src/openai/_utils/_utils.py @@ -5,6 +5,7 @@ import inspect import functools from typing import ( + TYPE_CHECKING, Any, Tuple, Mapping, @@ -16,12 +17,12 @@ overload, ) from pathlib import Path -from typing_extensions import TypeGuard +from datetime import date, datetime +from typing_extensions import TypeGuard, get_args import sniffio -from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike -from .._compat import parse_date as parse_date, parse_datetime as parse_datetime +from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -29,6 +30,9 @@ _SequenceT = TypeVar("_SequenceT", bound=Sequence[object]) CallableT = TypeVar("CallableT", bound=Callable[..., Any]) +if TYPE_CHECKING: + from ..lib.azure import AzureOpenAI, AsyncAzureOpenAI + def flatten(t: Iterable[Iterable[_T]]) -> list[_T]: return [item for sublist in t for item in sublist] @@ -40,30 +44,50 @@ def extract_files( query: Mapping[str, object], *, paths: Sequence[Sequence[str]], + array_format: ArrayFormat = "brackets", ) -> list[tuple[str, FileTypes]]: """Recursively extract files from the given dictionary based on specified paths. A path may look like this ['foo', 'files', '', 'data']. + ``array_format`` controls how ```` segments contribute to the emitted + field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and + ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``). + Note: this mutates the given dictionary. """ files: list[tuple[str, FileTypes]] = [] for path in paths: - files.extend(_extract_items(query, path, index=0, flattened_key=None)) + files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format)) return files +def _array_suffix(array_format: ArrayFormat, array_index: int) -> str: + if array_format == "brackets": + return "[]" + if array_format == "indices": + return f"[{array_index}]" + if array_format == "repeat" or array_format == "comma": + # Both repeat the bare field name for each file part; there is no + # meaningful way to comma-join binary parts. + return "" + raise NotImplementedError( + f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" + ) + + def _extract_items( obj: object, path: Sequence[str], *, index: int, flattened_key: str | None, + array_format: ArrayFormat, ) -> list[tuple[str, FileTypes]]: try: key = path[index] except IndexError: - if isinstance(obj, NotGiven): + if not is_given(obj): # no value was provided - we can safely ignore return [] @@ -71,15 +95,26 @@ def _extract_items( from .._files import assert_is_file_content # We have exhausted the path, return the entry we found. - assert_is_file_content(obj, key=flattened_key) assert flattened_key is not None + + if is_list(obj): + files: list[tuple[str, FileTypes]] = [] + for array_index, entry in enumerate(obj): + suffix = _array_suffix(array_format, array_index) + emitted_key = (flattened_key + suffix) if flattened_key else suffix + assert_is_file_content(entry, key=emitted_key) + files.append((emitted_key, cast(FileTypes, entry))) + return files + + assert_is_file_content(obj, key=flattened_key) return [(flattened_key, cast(FileTypes, obj))] index += 1 if is_dict(obj): try: - # We are at the last entry in the path so we must remove the field - if (len(path)) == index: + # Remove the field if there are no more dict keys in the path, + # only "" traversal markers or end. + if all(p == "" for p in path[index:]): item = obj.pop(key) else: item = obj[key] @@ -97,6 +132,7 @@ def _extract_items( path, index=index, flattened_key=flattened_key, + array_format=array_format, ) elif is_list(obj): if key != "": @@ -108,9 +144,12 @@ def _extract_items( item, path, index=index, - flattened_key=flattened_key + "[]" if flattened_key is not None else "[]", + flattened_key=( + (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index) + ), + array_format=array_format, ) - for item in obj + for array_index, item in enumerate(obj) ] ) @@ -118,14 +157,14 @@ def _extract_items( return [] -def is_given(obj: NotGivenOr[_T]) -> TypeGuard[_T]: - return not isinstance(obj, NotGiven) +def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: + return not isinstance(obj, NotGiven) and not isinstance(obj, Omit) # Type safe methods for narrowing types with TypeVars. # The default narrowing for isinstance(obj, dict) is dict[unknown, unknown], # however this cause Pyright to rightfully report errors. As we know we don't -# care about the contained types we can safely use `object` in it's place. +# care about the contained types we can safely use `object` in its place. # # There are two separate functions defined, `is_*` and `is_*_t` for different use cases. # `is_*` is for when you're dealing with an unknown input @@ -168,21 +207,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]: return isinstance(obj, Iterable) -def deepcopy_minimal(item: _T) -> _T: - """Minimal reimplementation of copy.deepcopy() that will only copy certain object types: - - - mappings, e.g. `dict` - - list - - This is done for performance reasons. - """ - if is_mapping(item): - return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()}) - if is_list(item): - return cast(_T, [deepcopy_minimal(entry) for entry in item]) - return item - - # copied from https://github.com/Rapptz/RoboDanny def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: size = len(seq) @@ -395,3 +419,31 @@ def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]: maxsize=maxsize, ) return cast(Any, wrapper) # type: ignore[no-any-return] + + +def json_safe(data: object) -> object: + """Translates a mapping / sequence recursively in the same fashion + as `pydantic` v2's `model_dump(mode="json")`. + """ + if is_mapping(data): + return {json_safe(key): json_safe(value) for key, value in data.items()} + + if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)): + return [json_safe(item) for item in data] + + if isinstance(data, (datetime, date)): + return data.isoformat() + + return data + + +def is_azure_client(client: object) -> TypeGuard[AzureOpenAI]: + from ..lib.azure import AzureOpenAI + + return isinstance(client, AzureOpenAI) + + +def is_async_azure_client(client: object) -> TypeGuard[AsyncAzureOpenAI]: + from ..lib.azure import AsyncAzureOpenAI + + return isinstance(client, AsyncAzureOpenAI) diff --git a/src/openai/_version.py b/src/openai/_version.py index 1b29a53bb6..57fb9f9cca 100644 --- a/src/openai/_version.py +++ b/src/openai/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "openai" -__version__ = "1.45.1" # x-release-please-version +__version__ = "2.41.0" # x-release-please-version diff --git a/src/openai/auth/__init__.py b/src/openai/auth/__init__.py new file mode 100644 index 0000000000..367aa86b72 --- /dev/null +++ b/src/openai/auth/__init__.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from ._workload import ( + WorkloadIdentity as WorkloadIdentity, + SubjectTokenProvider as SubjectTokenProvider, + WorkloadIdentityAuth as WorkloadIdentityAuth, + gcp_id_token_provider as gcp_id_token_provider, + k8s_service_account_token_provider as k8s_service_account_token_provider, + azure_managed_identity_token_provider as azure_managed_identity_token_provider, +) + +__all__ = [ + "SubjectTokenProvider", + "WorkloadIdentity", + "WorkloadIdentityAuth", + "k8s_service_account_token_provider", + "azure_managed_identity_token_provider", + "gcp_id_token_provider", +] diff --git a/src/openai/auth/_workload.py b/src/openai/auth/_workload.py new file mode 100644 index 0000000000..6b13ededb2 --- /dev/null +++ b/src/openai/auth/_workload.py @@ -0,0 +1,309 @@ +from __future__ import annotations + +import time +import threading +from typing import Any, Callable, TypedDict, cast +from pathlib import Path +from typing_extensions import Literal, NotRequired + +import httpx + +from .._exceptions import OAuthError, OpenAIError, SubjectTokenProviderError +from .._utils._sync import to_thread + +TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" +DEFAULT_TOKEN_EXCHANGE_URL = "https://auth.openai.com/oauth/token" +DEFAULT_REFRESH_BUFFER_SECONDS = 1200 + +SUBJECT_TOKEN_TYPES = { + "jwt": "urn:ietf:params:oauth:token-type:jwt", + "id": "urn:ietf:params:oauth:token-type:id_token", +} + + +class SubjectTokenProvider(TypedDict): + token_type: Literal["jwt", "id"] + get_token: Callable[[], str] + + +class WorkloadIdentity(TypedDict): + """Identity provider resource id in WIFAPI.""" + + identity_provider_id: str + + """Service account id to bind the verified external identity to.""" + service_account_id: str + + """The provider configuration for obtaining the subject token.""" + provider: SubjectTokenProvider + + """Optional buffer time in seconds to refresh the OpenAI token before it expires. Defaults to 1200 seconds (20 minutes).""" + refresh_buffer_seconds: NotRequired[float] + + +def k8s_service_account_token_provider( + token_file_path: str | Path = "/var/run/secrets/kubernetes.io/serviceaccount/token", +) -> SubjectTokenProvider: + """ + Get a subject token provider for Kubernetes clusters with Workload Identity configured. + + Cloud providers typically mount the subject token as a file in the container. + + Args: + token_file_path: path to the mounted service account token file. Defaults to `/var/run/secrets/kubernetes.io/serviceaccount/token`. + """ + + def get_token() -> str: + try: + with open(token_file_path, "r") as f: + token = f.read().strip() + if not token: + raise SubjectTokenProviderError(f"The token file at {token_file_path} is empty.") + return token + except Exception as e: + raise SubjectTokenProviderError(f"Failed to read the token file at {token_file_path}: {e}") from e + + return {"token_type": "jwt", "get_token": get_token} + + +def azure_managed_identity_token_provider( + resource: str = "https://management.azure.com/", + *, + object_id: str | None = None, + client_id: str | None = None, + msi_res_id: str | None = None, + api_version: str = "2018-02-01", + timeout: float = 10.0, + http_client: httpx.Client | None = None, +) -> SubjectTokenProvider: + """ + Get a subject token provider for Azure Managed Identities. + + See: https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http + + Args: + resource: the resource URI to request a token for. Defaults to `https://management.azure.com/` (Azure Resource Manager). + object_id: the object ID of the managed identity to use, when multiple are assigned. + client_id: the client ID of the managed identity to use, when multiple are assigned. + msi_res_id: the ARM resource ID of the managed identity to use, when multiple are assigned. + api_version: the Azure IMDS API version. Defaults to `2018-02-01`. + timeout: the request timeout in seconds. Defaults to 10.0. + http_client: optional httpx.Client instance to use for requests. If not provided, a new client will be created for each request. + """ + + def get_token() -> str: + try: + url = "http://169.254.169.254/metadata/identity/oauth2/token" + params: dict[str, str] = {"api-version": api_version, "resource": resource} + if object_id is not None: + params["object_id"] = object_id + if client_id is not None: + params["client_id"] = client_id + if msi_res_id is not None: + params["msi_res_id"] = msi_res_id + + if http_client is not None: + response = http_client.get(url, params=params, headers={"Metadata": "true"}, timeout=timeout) + else: + with httpx.Client() as client: + response = client.get(url, params=params, headers={"Metadata": "true"}, timeout=timeout) + + if response.is_error: + raise SubjectTokenProviderError( + f"Failed to fetch Azure subject token from IMDS: HTTP {response.status_code}", + response=response, + ) + data = response.json() + token = data.get("access_token") + if not token: + raise SubjectTokenProviderError( + "Azure IMDS response did not include an access_token", response=response + ) + return cast(str, token) + except Exception as e: + raise SubjectTokenProviderError(f"Failed to fetch Azure subject token from IMDS: {e}") from e + + return {"token_type": "jwt", "get_token": get_token} + + +def gcp_id_token_provider( + audience: str = "https://api.openai.com/v1", + *, + timeout: float = 10.0, + http_client: httpx.Client | None = None, +) -> SubjectTokenProvider: + """ + Get a subject token provider for GCP VM instances using the instance metadata server. + + See: https://cloud.google.com/compute/docs/instances/verifying-instance-identity + + Args: + audience: the unique URI agreed upon by both the instance and the system verifying + the instance's identity. Defaults to `https://api.openai.com/v1`. + timeout: the request timeout in seconds. Defaults to 10.0. + http_client: optional httpx.Client instance to use for requests. If not provided, a new client will be created for each request. + """ + + def get_token() -> str: + try: + url = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity" + params = {"audience": audience} + + if http_client is not None: + response = http_client.get(url, params=params, headers={"Metadata-Flavor": "Google"}, timeout=timeout) + else: + with httpx.Client() as client: + response = client.get(url, params=params, headers={"Metadata-Flavor": "Google"}, timeout=timeout) + + if response.is_error: + raise SubjectTokenProviderError( + f"Failed to fetch GCP subject token from metadata server: HTTP {response.status_code}", + response=response, + ) + token = response.text.strip() + if not token: + raise SubjectTokenProviderError("GCP metadata server returned an empty token", response=response) + return token + except Exception as e: + raise SubjectTokenProviderError(f"Failed to fetch GCP subject token from metadata server: {e}") from e + + return {"token_type": "id", "get_token": get_token} + + +class WorkloadIdentityAuth: + def __init__( + self, + *, + workload_identity: WorkloadIdentity, + token_exchange_url: str = DEFAULT_TOKEN_EXCHANGE_URL, + ): + self.workload_identity = workload_identity + self.token_exchange_url = token_exchange_url + + self._cached_token: str | None = None + self._cached_token_expires_at_monotonic: float | None = None + self._cached_token_refresh_at_monotonic: float | None = None + self._refreshing: bool = False + self._lock = threading.Lock() + self._condition = threading.Condition(self._lock) + + def get_token(self) -> str: + with self._lock: + while self._refreshing and self._token_unusable(): + self._condition.wait() + + if not self._token_unusable() and not self._needs_refresh(): + return cast(str, self._cached_token) + + if self._refreshing: + while self._refreshing: + self._condition.wait() + token = self._cached_token # type: ignore[unreachable] + if self._token_unusable(): + raise RuntimeError("Token is unusable after refresh completed") + return cast(str, token) + + self._refreshing = True + + try: + self._perform_refresh() + with self._lock: + if self._token_unusable(): + raise RuntimeError("Token is unusable after refresh completed") + return cast(str, self._cached_token) + finally: + with self._lock: + self._refreshing = False + self._condition.notify_all() + + async def get_token_async(self) -> str: + return await to_thread(self.get_token) + + def invalidate_token(self) -> None: + with self._lock: + self._cached_token = None + self._cached_token_expires_at_monotonic = None + self._cached_token_refresh_at_monotonic = None + + def _perform_refresh(self) -> None: + token_data = self._fetch_token_from_exchange() + now = time.monotonic() + expires_in = token_data["expires_in"] + + with self._lock: + self._cached_token = token_data["access_token"] + self._cached_token_expires_at_monotonic = now + expires_in + self._cached_token_refresh_at_monotonic = now + self._refresh_delay_seconds(expires_in) + + def _fetch_token_from_exchange(self) -> dict[str, Any]: + subject_token = self._get_subject_token() + + token_type = self.workload_identity["provider"]["token_type"] + subject_token_type = SUBJECT_TOKEN_TYPES.get(token_type) + if subject_token_type is None: + raise OpenAIError( + f"Unsupported token type: {token_type!r}. Supported types: {', '.join(SUBJECT_TOKEN_TYPES.keys())}" + ) + + with httpx.Client() as client: + response = client.post( + self.token_exchange_url, + json={ + "grant_type": TOKEN_EXCHANGE_GRANT_TYPE, + "subject_token": subject_token, + "subject_token_type": subject_token_type, + "identity_provider_id": self.workload_identity["identity_provider_id"], + "service_account_id": self.workload_identity["service_account_id"], + }, + timeout=10.0, + ) + return self._handle_token_response(response) + + def _handle_token_response(self, response: httpx.Response) -> dict[str, Any]: + try: + body = response.json() if response.content else None + except ValueError: + body = None + + if response.status_code in (400, 401, 403): + raise OAuthError(response=response, body=body) + + if response.is_success: + if body is None: + raise OpenAIError("Token exchange succeeded but response body was empty") + access_token = body.get("access_token") + expires_in = body.get("expires_in") + if not isinstance(access_token, str) or not access_token: + raise OpenAIError("Token exchange response did not include a valid access_token") + if not isinstance(expires_in, (int, float)): + raise OpenAIError("Token exchange response did not include a valid expires_in") + return {"access_token": access_token, "expires_in": float(expires_in)} + + raise OpenAIError( + f"Token exchange failed with status {response.status_code}", + ) + + def _get_subject_token(self) -> str: + provider = self.workload_identity["provider"] + subject_token = provider["get_token"]() + if not subject_token: + raise OpenAIError("The workload identity provider returned an empty subject token") + return subject_token + + def _token_unusable(self) -> bool: + return self._cached_token is None or self._token_expired() + + def _token_expired(self) -> bool: + if self._cached_token_expires_at_monotonic is None: + return True + return time.monotonic() >= self._cached_token_expires_at_monotonic + + def _needs_refresh(self) -> bool: + if self._cached_token_refresh_at_monotonic is None: + return False + return time.monotonic() >= self._cached_token_refresh_at_monotonic + + def _refresh_delay_seconds(self, expires_in: float) -> float: + configured_buffer = self.workload_identity.get("refresh_buffer_seconds", DEFAULT_REFRESH_BUFFER_SECONDS) + effective_buffer = min(configured_buffer, expires_in / 2) + return max(expires_in - effective_buffer, 0.0) diff --git a/src/openai/cli/__init__.py b/src/openai/cli/__init__.py deleted file mode 100644 index d453d5e179..0000000000 --- a/src/openai/cli/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._cli import main as main diff --git a/src/openai/cli/_api/__init__.py b/src/openai/cli/_api/__init__.py deleted file mode 100644 index 56a0260a6d..0000000000 --- a/src/openai/cli/_api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._main import register_commands as register_commands diff --git a/src/openai/cli/_api/_main.py b/src/openai/cli/_api/_main.py deleted file mode 100644 index fe5a5e6fc0..0000000000 --- a/src/openai/cli/_api/_main.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import annotations - -from argparse import ArgumentParser - -from . import chat, audio, files, image, models, completions - - -def register_commands(parser: ArgumentParser) -> None: - subparsers = parser.add_subparsers(help="All API subcommands") - - chat.register(subparsers) - image.register(subparsers) - audio.register(subparsers) - files.register(subparsers) - models.register(subparsers) - completions.register(subparsers) diff --git a/src/openai/cli/_api/audio.py b/src/openai/cli/_api/audio.py deleted file mode 100644 index 269c67df28..0000000000 --- a/src/openai/cli/_api/audio.py +++ /dev/null @@ -1,108 +0,0 @@ -from __future__ import annotations - -import sys -from typing import TYPE_CHECKING, Any, Optional, cast -from argparse import ArgumentParser - -from .._utils import get_client, print_model -from ..._types import NOT_GIVEN -from .._models import BaseModel -from .._progress import BufferReader -from ...types.audio import Transcription - -if TYPE_CHECKING: - from argparse import _SubParsersAction - - -def register(subparser: _SubParsersAction[ArgumentParser]) -> None: - # transcriptions - sub = subparser.add_parser("audio.transcriptions.create") - - # Required - sub.add_argument("-m", "--model", type=str, default="whisper-1") - sub.add_argument("-f", "--file", type=str, required=True) - # Optional - sub.add_argument("--response-format", type=str) - sub.add_argument("--language", type=str) - sub.add_argument("-t", "--temperature", type=float) - sub.add_argument("--prompt", type=str) - sub.set_defaults(func=CLIAudio.transcribe, args_model=CLITranscribeArgs) - - # translations - sub = subparser.add_parser("audio.translations.create") - - # Required - sub.add_argument("-f", "--file", type=str, required=True) - # Optional - sub.add_argument("-m", "--model", type=str, default="whisper-1") - sub.add_argument("--response-format", type=str) - # TODO: doesn't seem to be supported by the API - # sub.add_argument("--language", type=str) - sub.add_argument("-t", "--temperature", type=float) - sub.add_argument("--prompt", type=str) - sub.set_defaults(func=CLIAudio.translate, args_model=CLITranslationArgs) - - -class CLITranscribeArgs(BaseModel): - model: str - file: str - response_format: Optional[str] = None - language: Optional[str] = None - temperature: Optional[float] = None - prompt: Optional[str] = None - - -class CLITranslationArgs(BaseModel): - model: str - file: str - response_format: Optional[str] = None - language: Optional[str] = None - temperature: Optional[float] = None - prompt: Optional[str] = None - - -class CLIAudio: - @staticmethod - def transcribe(args: CLITranscribeArgs) -> None: - with open(args.file, "rb") as file_reader: - buffer_reader = BufferReader(file_reader.read(), desc="Upload progress") - - model = cast( - "Transcription | str", - get_client().audio.transcriptions.create( - file=(args.file, buffer_reader), - model=args.model, - language=args.language or NOT_GIVEN, - temperature=args.temperature or NOT_GIVEN, - prompt=args.prompt or NOT_GIVEN, - # casts required because the API is typed for enums - # but we don't want to validate that here for forwards-compat - response_format=cast(Any, args.response_format), - ), - ) - if isinstance(model, str): - sys.stdout.write(model + "\n") - else: - print_model(model) - - @staticmethod - def translate(args: CLITranslationArgs) -> None: - with open(args.file, "rb") as file_reader: - buffer_reader = BufferReader(file_reader.read(), desc="Upload progress") - - model = cast( - "Transcription | str", - get_client().audio.translations.create( - file=(args.file, buffer_reader), - model=args.model, - temperature=args.temperature or NOT_GIVEN, - prompt=args.prompt or NOT_GIVEN, - # casts required because the API is typed for enums - # but we don't want to validate that here for forwards-compat - response_format=cast(Any, args.response_format), - ), - ) - if isinstance(model, str): - sys.stdout.write(model + "\n") - else: - print_model(model) diff --git a/src/openai/cli/_api/chat/__init__.py b/src/openai/cli/_api/chat/__init__.py deleted file mode 100644 index 87d971630a..0000000000 --- a/src/openai/cli/_api/chat/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING -from argparse import ArgumentParser - -from . import completions - -if TYPE_CHECKING: - from argparse import _SubParsersAction - - -def register(subparser: _SubParsersAction[ArgumentParser]) -> None: - completions.register(subparser) diff --git a/src/openai/cli/_api/chat/completions.py b/src/openai/cli/_api/chat/completions.py deleted file mode 100644 index c299741fe0..0000000000 --- a/src/openai/cli/_api/chat/completions.py +++ /dev/null @@ -1,156 +0,0 @@ -from __future__ import annotations - -import sys -from typing import TYPE_CHECKING, List, Optional, cast -from argparse import ArgumentParser -from typing_extensions import Literal, NamedTuple - -from ..._utils import get_client -from ..._models import BaseModel -from ...._streaming import Stream -from ....types.chat import ( - ChatCompletionRole, - ChatCompletionChunk, - CompletionCreateParams, -) -from ....types.chat.completion_create_params import ( - CompletionCreateParamsStreaming, - CompletionCreateParamsNonStreaming, -) - -if TYPE_CHECKING: - from argparse import _SubParsersAction - - -def register(subparser: _SubParsersAction[ArgumentParser]) -> None: - sub = subparser.add_parser("chat.completions.create") - - sub._action_groups.pop() - req = sub.add_argument_group("required arguments") - opt = sub.add_argument_group("optional arguments") - - req.add_argument( - "-g", - "--message", - action="append", - nargs=2, - metavar=("ROLE", "CONTENT"), - help="A message in `{role} {content}` format. Use this argument multiple times to add multiple messages.", - required=True, - ) - req.add_argument( - "-m", - "--model", - help="The model to use.", - required=True, - ) - - opt.add_argument( - "-n", - "--n", - help="How many completions to generate for the conversation.", - type=int, - ) - opt.add_argument("-M", "--max-tokens", help="The maximum number of tokens to generate.", type=int) - opt.add_argument( - "-t", - "--temperature", - help="""What sampling temperature to use. Higher values means the model will take more risks. Try 0.9 for more creative applications, and 0 (argmax sampling) for ones with a well-defined answer. - -Mutually exclusive with `top_p`.""", - type=float, - ) - opt.add_argument( - "-P", - "--top_p", - help="""An alternative to sampling with temperature, called nucleus sampling, where the considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10%% probability mass are considered. - - Mutually exclusive with `temperature`.""", - type=float, - ) - opt.add_argument( - "--stop", - help="A stop sequence at which to stop generating tokens for the message.", - ) - opt.add_argument("--stream", help="Stream messages as they're ready.", action="store_true") - sub.set_defaults(func=CLIChatCompletion.create, args_model=CLIChatCompletionCreateArgs) - - -class CLIMessage(NamedTuple): - role: ChatCompletionRole - content: str - - -class CLIChatCompletionCreateArgs(BaseModel): - message: List[CLIMessage] - model: str - n: Optional[int] = None - max_tokens: Optional[int] = None - temperature: Optional[float] = None - top_p: Optional[float] = None - stop: Optional[str] = None - stream: bool = False - - -class CLIChatCompletion: - @staticmethod - def create(args: CLIChatCompletionCreateArgs) -> None: - params: CompletionCreateParams = { - "model": args.model, - "messages": [ - {"role": cast(Literal["user"], message.role), "content": message.content} for message in args.message - ], - "n": args.n, - "temperature": args.temperature, - "top_p": args.top_p, - "stop": args.stop, - # type checkers are not good at inferring union types so we have to set stream afterwards - "stream": False, - } - if args.stream: - params["stream"] = args.stream # type: ignore - if args.max_tokens is not None: - params["max_tokens"] = args.max_tokens - - if args.stream: - return CLIChatCompletion._stream_create(cast(CompletionCreateParamsStreaming, params)) - - return CLIChatCompletion._create(cast(CompletionCreateParamsNonStreaming, params)) - - @staticmethod - def _create(params: CompletionCreateParamsNonStreaming) -> None: - completion = get_client().chat.completions.create(**params) - should_print_header = len(completion.choices) > 1 - for choice in completion.choices: - if should_print_header: - sys.stdout.write("===== Chat Completion {} =====\n".format(choice.index)) - - content = choice.message.content if choice.message.content is not None else "None" - sys.stdout.write(content) - - if should_print_header or not content.endswith("\n"): - sys.stdout.write("\n") - - sys.stdout.flush() - - @staticmethod - def _stream_create(params: CompletionCreateParamsStreaming) -> None: - # cast is required for mypy - stream = cast( # pyright: ignore[reportUnnecessaryCast] - Stream[ChatCompletionChunk], get_client().chat.completions.create(**params) - ) - for chunk in stream: - should_print_header = len(chunk.choices) > 1 - for choice in chunk.choices: - if should_print_header: - sys.stdout.write("===== Chat Completion {} =====\n".format(choice.index)) - - content = choice.delta.content or "" - sys.stdout.write(content) - - if should_print_header: - sys.stdout.write("\n") - - sys.stdout.flush() - - sys.stdout.write("\n") diff --git a/src/openai/cli/_api/completions.py b/src/openai/cli/_api/completions.py deleted file mode 100644 index cbdb35bf3a..0000000000 --- a/src/openai/cli/_api/completions.py +++ /dev/null @@ -1,173 +0,0 @@ -from __future__ import annotations - -import sys -from typing import TYPE_CHECKING, Optional, cast -from argparse import ArgumentParser -from functools import partial - -from openai.types.completion import Completion - -from .._utils import get_client -from ..._types import NOT_GIVEN, NotGivenOr -from ..._utils import is_given -from .._errors import CLIError -from .._models import BaseModel -from ..._streaming import Stream - -if TYPE_CHECKING: - from argparse import _SubParsersAction - - -def register(subparser: _SubParsersAction[ArgumentParser]) -> None: - sub = subparser.add_parser("completions.create") - - # Required - sub.add_argument( - "-m", - "--model", - help="The model to use", - required=True, - ) - - # Optional - sub.add_argument("-p", "--prompt", help="An optional prompt to complete from") - sub.add_argument("--stream", help="Stream tokens as they're ready.", action="store_true") - sub.add_argument("-M", "--max-tokens", help="The maximum number of tokens to generate", type=int) - sub.add_argument( - "-t", - "--temperature", - help="""What sampling temperature to use. Higher values means the model will take more risks. Try 0.9 for more creative applications, and 0 (argmax sampling) for ones with a well-defined answer. - -Mutually exclusive with `top_p`.""", - type=float, - ) - sub.add_argument( - "-P", - "--top_p", - help="""An alternative to sampling with temperature, called nucleus sampling, where the considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10%% probability mass are considered. - - Mutually exclusive with `temperature`.""", - type=float, - ) - sub.add_argument( - "-n", - "--n", - help="How many sub-completions to generate for each prompt.", - type=int, - ) - sub.add_argument( - "--logprobs", - help="Include the log probabilities on the `logprobs` most likely tokens, as well the chosen tokens. So for example, if `logprobs` is 10, the API will return a list of the 10 most likely tokens. If `logprobs` is 0, only the chosen tokens will have logprobs returned.", - type=int, - ) - sub.add_argument( - "--best_of", - help="Generates `best_of` completions server-side and returns the 'best' (the one with the highest log probability per token). Results cannot be streamed.", - type=int, - ) - sub.add_argument( - "--echo", - help="Echo back the prompt in addition to the completion", - action="store_true", - ) - sub.add_argument( - "--frequency_penalty", - help="Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", - type=float, - ) - sub.add_argument( - "--presence_penalty", - help="Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.", - type=float, - ) - sub.add_argument("--suffix", help="The suffix that comes after a completion of inserted text.") - sub.add_argument("--stop", help="A stop sequence at which to stop generating tokens.") - sub.add_argument( - "--user", - help="A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.", - ) - # TODO: add support for logit_bias - sub.set_defaults(func=CLICompletions.create, args_model=CLICompletionCreateArgs) - - -class CLICompletionCreateArgs(BaseModel): - model: str - stream: bool = False - - prompt: Optional[str] = None - n: NotGivenOr[int] = NOT_GIVEN - stop: NotGivenOr[str] = NOT_GIVEN - user: NotGivenOr[str] = NOT_GIVEN - echo: NotGivenOr[bool] = NOT_GIVEN - suffix: NotGivenOr[str] = NOT_GIVEN - best_of: NotGivenOr[int] = NOT_GIVEN - top_p: NotGivenOr[float] = NOT_GIVEN - logprobs: NotGivenOr[int] = NOT_GIVEN - max_tokens: NotGivenOr[int] = NOT_GIVEN - temperature: NotGivenOr[float] = NOT_GIVEN - presence_penalty: NotGivenOr[float] = NOT_GIVEN - frequency_penalty: NotGivenOr[float] = NOT_GIVEN - - -class CLICompletions: - @staticmethod - def create(args: CLICompletionCreateArgs) -> None: - if is_given(args.n) and args.n > 1 and args.stream: - raise CLIError("Can't stream completions with n>1 with the current CLI") - - make_request = partial( - get_client().completions.create, - n=args.n, - echo=args.echo, - stop=args.stop, - user=args.user, - model=args.model, - top_p=args.top_p, - prompt=args.prompt, - suffix=args.suffix, - best_of=args.best_of, - logprobs=args.logprobs, - max_tokens=args.max_tokens, - temperature=args.temperature, - presence_penalty=args.presence_penalty, - frequency_penalty=args.frequency_penalty, - ) - - if args.stream: - return CLICompletions._stream_create( - # mypy doesn't understand the `partial` function but pyright does - cast(Stream[Completion], make_request(stream=True)) # pyright: ignore[reportUnnecessaryCast] - ) - - return CLICompletions._create(make_request()) - - @staticmethod - def _create(completion: Completion) -> None: - should_print_header = len(completion.choices) > 1 - for choice in completion.choices: - if should_print_header: - sys.stdout.write("===== Completion {} =====\n".format(choice.index)) - - sys.stdout.write(choice.text) - - if should_print_header or not choice.text.endswith("\n"): - sys.stdout.write("\n") - - sys.stdout.flush() - - @staticmethod - def _stream_create(stream: Stream[Completion]) -> None: - for completion in stream: - should_print_header = len(completion.choices) > 1 - for choice in sorted(completion.choices, key=lambda c: c.index): - if should_print_header: - sys.stdout.write("===== Chat Completion {} =====\n".format(choice.index)) - - sys.stdout.write(choice.text) - - if should_print_header: - sys.stdout.write("\n") - - sys.stdout.flush() - - sys.stdout.write("\n") diff --git a/src/openai/cli/_api/files.py b/src/openai/cli/_api/files.py deleted file mode 100644 index 5f3631b284..0000000000 --- a/src/openai/cli/_api/files.py +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, cast -from argparse import ArgumentParser - -from .._utils import get_client, print_model -from .._models import BaseModel -from .._progress import BufferReader - -if TYPE_CHECKING: - from argparse import _SubParsersAction - - -def register(subparser: _SubParsersAction[ArgumentParser]) -> None: - sub = subparser.add_parser("files.create") - - sub.add_argument( - "-f", - "--file", - required=True, - help="File to upload", - ) - sub.add_argument( - "-p", - "--purpose", - help="Why are you uploading this file? (see https://platform.openai.com/docs/api-reference/ for purposes)", - required=True, - ) - sub.set_defaults(func=CLIFile.create, args_model=CLIFileCreateArgs) - - sub = subparser.add_parser("files.retrieve") - sub.add_argument("-i", "--id", required=True, help="The files ID") - sub.set_defaults(func=CLIFile.get, args_model=CLIFileCreateArgs) - - sub = subparser.add_parser("files.delete") - sub.add_argument("-i", "--id", required=True, help="The files ID") - sub.set_defaults(func=CLIFile.delete, args_model=CLIFileCreateArgs) - - sub = subparser.add_parser("files.list") - sub.set_defaults(func=CLIFile.list) - - -class CLIFileIDArgs(BaseModel): - id: str - - -class CLIFileCreateArgs(BaseModel): - file: str - purpose: str - - -class CLIFile: - @staticmethod - def create(args: CLIFileCreateArgs) -> None: - with open(args.file, "rb") as file_reader: - buffer_reader = BufferReader(file_reader.read(), desc="Upload progress") - - file = get_client().files.create( - file=(args.file, buffer_reader), - # casts required because the API is typed for enums - # but we don't want to validate that here for forwards-compat - purpose=cast(Any, args.purpose), - ) - print_model(file) - - @staticmethod - def get(args: CLIFileIDArgs) -> None: - file = get_client().files.retrieve(file_id=args.id) - print_model(file) - - @staticmethod - def delete(args: CLIFileIDArgs) -> None: - file = get_client().files.delete(file_id=args.id) - print_model(file) - - @staticmethod - def list() -> None: - files = get_client().files.list() - for file in files: - print_model(file) diff --git a/src/openai/cli/_api/image.py b/src/openai/cli/_api/image.py deleted file mode 100644 index 3e2a0a90f1..0000000000 --- a/src/openai/cli/_api/image.py +++ /dev/null @@ -1,139 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, cast -from argparse import ArgumentParser - -from .._utils import get_client, print_model -from ..._types import NOT_GIVEN, NotGiven, NotGivenOr -from .._models import BaseModel -from .._progress import BufferReader - -if TYPE_CHECKING: - from argparse import _SubParsersAction - - -def register(subparser: _SubParsersAction[ArgumentParser]) -> None: - sub = subparser.add_parser("images.generate") - sub.add_argument("-m", "--model", type=str) - sub.add_argument("-p", "--prompt", type=str, required=True) - sub.add_argument("-n", "--num-images", type=int, default=1) - sub.add_argument("-s", "--size", type=str, default="1024x1024", help="Size of the output image") - sub.add_argument("--response-format", type=str, default="url") - sub.set_defaults(func=CLIImage.create, args_model=CLIImageCreateArgs) - - sub = subparser.add_parser("images.edit") - sub.add_argument("-m", "--model", type=str) - sub.add_argument("-p", "--prompt", type=str, required=True) - sub.add_argument("-n", "--num-images", type=int, default=1) - sub.add_argument( - "-I", - "--image", - type=str, - required=True, - help="Image to modify. Should be a local path and a PNG encoded image.", - ) - sub.add_argument("-s", "--size", type=str, default="1024x1024", help="Size of the output image") - sub.add_argument("--response-format", type=str, default="url") - sub.add_argument( - "-M", - "--mask", - type=str, - required=False, - help="Path to a mask image. It should be the same size as the image you're editing and a RGBA PNG image. The Alpha channel acts as the mask.", - ) - sub.set_defaults(func=CLIImage.edit, args_model=CLIImageEditArgs) - - sub = subparser.add_parser("images.create_variation") - sub.add_argument("-m", "--model", type=str) - sub.add_argument("-n", "--num-images", type=int, default=1) - sub.add_argument( - "-I", - "--image", - type=str, - required=True, - help="Image to modify. Should be a local path and a PNG encoded image.", - ) - sub.add_argument("-s", "--size", type=str, default="1024x1024", help="Size of the output image") - sub.add_argument("--response-format", type=str, default="url") - sub.set_defaults(func=CLIImage.create_variation, args_model=CLIImageCreateVariationArgs) - - -class CLIImageCreateArgs(BaseModel): - prompt: str - num_images: int - size: str - response_format: str - model: NotGivenOr[str] = NOT_GIVEN - - -class CLIImageCreateVariationArgs(BaseModel): - image: str - num_images: int - size: str - response_format: str - model: NotGivenOr[str] = NOT_GIVEN - - -class CLIImageEditArgs(BaseModel): - image: str - num_images: int - size: str - response_format: str - prompt: str - mask: NotGivenOr[str] = NOT_GIVEN - model: NotGivenOr[str] = NOT_GIVEN - - -class CLIImage: - @staticmethod - def create(args: CLIImageCreateArgs) -> None: - image = get_client().images.generate( - model=args.model, - prompt=args.prompt, - n=args.num_images, - # casts required because the API is typed for enums - # but we don't want to validate that here for forwards-compat - size=cast(Any, args.size), - response_format=cast(Any, args.response_format), - ) - print_model(image) - - @staticmethod - def create_variation(args: CLIImageCreateVariationArgs) -> None: - with open(args.image, "rb") as file_reader: - buffer_reader = BufferReader(file_reader.read(), desc="Upload progress") - - image = get_client().images.create_variation( - model=args.model, - image=("image", buffer_reader), - n=args.num_images, - # casts required because the API is typed for enums - # but we don't want to validate that here for forwards-compat - size=cast(Any, args.size), - response_format=cast(Any, args.response_format), - ) - print_model(image) - - @staticmethod - def edit(args: CLIImageEditArgs) -> None: - with open(args.image, "rb") as file_reader: - buffer_reader = BufferReader(file_reader.read(), desc="Image upload progress") - - if isinstance(args.mask, NotGiven): - mask: NotGivenOr[BufferReader] = NOT_GIVEN - else: - with open(args.mask, "rb") as file_reader: - mask = BufferReader(file_reader.read(), desc="Mask progress") - - image = get_client().images.edit( - model=args.model, - prompt=args.prompt, - image=("image", buffer_reader), - n=args.num_images, - mask=("mask", mask) if not isinstance(mask, NotGiven) else mask, - # casts required because the API is typed for enums - # but we don't want to validate that here for forwards-compat - size=cast(Any, args.size), - response_format=cast(Any, args.response_format), - ) - print_model(image) diff --git a/src/openai/cli/_api/models.py b/src/openai/cli/_api/models.py deleted file mode 100644 index 017218fa6e..0000000000 --- a/src/openai/cli/_api/models.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING -from argparse import ArgumentParser - -from .._utils import get_client, print_model -from .._models import BaseModel - -if TYPE_CHECKING: - from argparse import _SubParsersAction - - -def register(subparser: _SubParsersAction[ArgumentParser]) -> None: - sub = subparser.add_parser("models.list") - sub.set_defaults(func=CLIModels.list) - - sub = subparser.add_parser("models.retrieve") - sub.add_argument("-i", "--id", required=True, help="The model ID") - sub.set_defaults(func=CLIModels.get, args_model=CLIModelIDArgs) - - sub = subparser.add_parser("models.delete") - sub.add_argument("-i", "--id", required=True, help="The model ID") - sub.set_defaults(func=CLIModels.delete, args_model=CLIModelIDArgs) - - -class CLIModelIDArgs(BaseModel): - id: str - - -class CLIModels: - @staticmethod - def get(args: CLIModelIDArgs) -> None: - model = get_client().models.retrieve(model=args.id) - print_model(model) - - @staticmethod - def delete(args: CLIModelIDArgs) -> None: - model = get_client().models.delete(model=args.id) - print_model(model) - - @staticmethod - def list() -> None: - models = get_client().models.list() - for model in models: - print_model(model) diff --git a/src/openai/cli/_cli.py b/src/openai/cli/_cli.py deleted file mode 100644 index 72e5c923bd..0000000000 --- a/src/openai/cli/_cli.py +++ /dev/null @@ -1,234 +0,0 @@ -from __future__ import annotations - -import sys -import logging -import argparse -from typing import Any, List, Type, Optional -from typing_extensions import ClassVar - -import httpx -import pydantic - -import openai - -from . import _tools -from .. import _ApiType, __version__ -from ._api import register_commands -from ._utils import can_use_http2 -from .._types import ProxiesDict -from ._errors import CLIError, display_error -from .._compat import PYDANTIC_V2, ConfigDict, model_parse -from .._models import BaseModel -from .._exceptions import APIError - -logger = logging.getLogger() -formatter = logging.Formatter("[%(asctime)s] %(message)s") -handler = logging.StreamHandler(sys.stderr) -handler.setFormatter(formatter) -logger.addHandler(handler) - - -class Arguments(BaseModel): - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict( - extra="ignore", - ) - else: - - class Config(pydantic.BaseConfig): # type: ignore - extra: Any = pydantic.Extra.ignore # type: ignore - - verbosity: int - version: Optional[str] = None - - api_key: Optional[str] - api_base: Optional[str] - organization: Optional[str] - proxy: Optional[List[str]] - api_type: Optional[_ApiType] = None - api_version: Optional[str] = None - - # azure - azure_endpoint: Optional[str] = None - azure_ad_token: Optional[str] = None - - # internal, set by subparsers to parse their specific args - args_model: Optional[Type[BaseModel]] = None - - # internal, used so that subparsers can forward unknown arguments - unknown_args: List[str] = [] - allow_unknown_args: bool = False - - -def _build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description=None, prog="openai") - parser.add_argument( - "-v", - "--verbose", - action="count", - dest="verbosity", - default=0, - help="Set verbosity.", - ) - parser.add_argument("-b", "--api-base", help="What API base url to use.") - parser.add_argument("-k", "--api-key", help="What API key to use.") - parser.add_argument("-p", "--proxy", nargs="+", help="What proxy to use.") - parser.add_argument( - "-o", - "--organization", - help="Which organization to run as (will use your default organization if not specified)", - ) - parser.add_argument( - "-t", - "--api-type", - type=str, - choices=("openai", "azure"), - help="The backend API to call, must be `openai` or `azure`", - ) - parser.add_argument( - "--api-version", - help="The Azure API version, e.g. 'https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning'", - ) - - # azure - parser.add_argument( - "--azure-endpoint", - help="The Azure endpoint, e.g. 'https://endpoint.openai.azure.com'", - ) - parser.add_argument( - "--azure-ad-token", - help="A token from Azure Active Directory, https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id", - ) - - # prints the package version - parser.add_argument( - "-V", - "--version", - action="version", - version="%(prog)s " + __version__, - ) - - def help() -> None: - parser.print_help() - - parser.set_defaults(func=help) - - subparsers = parser.add_subparsers() - sub_api = subparsers.add_parser("api", help="Direct API calls") - - register_commands(sub_api) - - sub_tools = subparsers.add_parser("tools", help="Client side tools for convenience") - _tools.register_commands(sub_tools, subparsers) - - return parser - - -def main() -> int: - try: - _main() - except (APIError, CLIError, pydantic.ValidationError) as err: - display_error(err) - return 1 - except KeyboardInterrupt: - sys.stderr.write("\n") - return 1 - return 0 - - -def _parse_args(parser: argparse.ArgumentParser) -> tuple[argparse.Namespace, Arguments, list[str]]: - # argparse by default will strip out the `--` but we want to keep it for unknown arguments - if "--" in sys.argv: - idx = sys.argv.index("--") - known_args = sys.argv[1:idx] - unknown_args = sys.argv[idx:] - else: - known_args = sys.argv[1:] - unknown_args = [] - - parsed, remaining_unknown = parser.parse_known_args(known_args) - - # append any remaining unknown arguments from the initial parsing - remaining_unknown.extend(unknown_args) - - args = model_parse(Arguments, vars(parsed)) - if not args.allow_unknown_args: - # we have to parse twice to ensure any unknown arguments - # result in an error if that behaviour is desired - parser.parse_args() - - return parsed, args, remaining_unknown - - -def _main() -> None: - parser = _build_parser() - parsed, args, unknown = _parse_args(parser) - - if args.verbosity != 0: - sys.stderr.write("Warning: --verbosity isn't supported yet\n") - - proxies: ProxiesDict = {} - if args.proxy is not None: - for proxy in args.proxy: - key = "https://" if proxy.startswith("https") else "http://" - if key in proxies: - raise CLIError(f"Multiple {key} proxies given - only the last one would be used") - - proxies[key] = proxy - - http_client = httpx.Client( - proxies=proxies or None, - http2=can_use_http2(), - ) - openai.http_client = http_client - - if args.organization: - openai.organization = args.organization - - if args.api_key: - openai.api_key = args.api_key - - if args.api_base: - openai.base_url = args.api_base - - # azure - if args.api_type is not None: - openai.api_type = args.api_type - - if args.azure_endpoint is not None: - openai.azure_endpoint = args.azure_endpoint - - if args.api_version is not None: - openai.api_version = args.api_version - - if args.azure_ad_token is not None: - openai.azure_ad_token = args.azure_ad_token - - try: - if args.args_model: - parsed.func( - model_parse( - args.args_model, - { - **{ - # we omit None values so that they can be defaulted to `NotGiven` - # and we'll strip it from the API request - key: value - for key, value in vars(parsed).items() - if value is not None - }, - "unknown_args": unknown, - }, - ) - ) - else: - parsed.func() - finally: - try: - http_client.close() - except Exception: - pass - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/src/openai/cli/_errors.py b/src/openai/cli/_errors.py deleted file mode 100644 index 7d0292dab2..0000000000 --- a/src/openai/cli/_errors.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import sys - -import pydantic - -from ._utils import Colors, organization_info -from .._exceptions import APIError, OpenAIError - - -class CLIError(OpenAIError): ... - - -class SilentCLIError(CLIError): ... - - -def display_error(err: CLIError | APIError | pydantic.ValidationError) -> None: - if isinstance(err, SilentCLIError): - return - - sys.stderr.write("{}{}Error:{} {}\n".format(organization_info(), Colors.FAIL, Colors.ENDC, err)) diff --git a/src/openai/cli/_models.py b/src/openai/cli/_models.py deleted file mode 100644 index 5583db2609..0000000000 --- a/src/openai/cli/_models.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Any -from typing_extensions import ClassVar - -import pydantic - -from .. import _models -from .._compat import PYDANTIC_V2, ConfigDict - - -class BaseModel(_models.BaseModel): - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict(extra="ignore", arbitrary_types_allowed=True) - else: - - class Config(pydantic.BaseConfig): # type: ignore - extra: Any = pydantic.Extra.ignore # type: ignore - arbitrary_types_allowed: bool = True diff --git a/src/openai/cli/_progress.py b/src/openai/cli/_progress.py deleted file mode 100644 index 8a7f2525de..0000000000 --- a/src/openai/cli/_progress.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import annotations - -import io -from typing import Callable -from typing_extensions import override - - -class CancelledError(Exception): - def __init__(self, msg: str) -> None: - self.msg = msg - super().__init__(msg) - - @override - def __str__(self) -> str: - return self.msg - - __repr__ = __str__ - - -class BufferReader(io.BytesIO): - def __init__(self, buf: bytes = b"", desc: str | None = None) -> None: - super().__init__(buf) - self._len = len(buf) - self._progress = 0 - self._callback = progress(len(buf), desc=desc) - - def __len__(self) -> int: - return self._len - - @override - def read(self, n: int | None = -1) -> bytes: - chunk = io.BytesIO.read(self, n) - self._progress += len(chunk) - - try: - self._callback(self._progress) - except Exception as e: # catches exception from the callback - raise CancelledError("The upload was cancelled: {}".format(e)) from e - - return chunk - - -def progress(total: float, desc: str | None) -> Callable[[float], None]: - import tqdm - - meter = tqdm.tqdm(total=total, unit_scale=True, desc=desc) - - def incr(progress: float) -> None: - meter.n = progress - if progress == total: - meter.close() - else: - meter.refresh() - - return incr - - -def MB(i: int) -> int: - return int(i // 1024**2) diff --git a/src/openai/cli/_tools/__init__.py b/src/openai/cli/_tools/__init__.py deleted file mode 100644 index 56a0260a6d..0000000000 --- a/src/openai/cli/_tools/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._main import register_commands as register_commands diff --git a/src/openai/cli/_tools/_main.py b/src/openai/cli/_tools/_main.py deleted file mode 100644 index bd6cda408f..0000000000 --- a/src/openai/cli/_tools/_main.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING -from argparse import ArgumentParser - -from . import migrate, fine_tunes - -if TYPE_CHECKING: - from argparse import _SubParsersAction - - -def register_commands(parser: ArgumentParser, subparser: _SubParsersAction[ArgumentParser]) -> None: - migrate.register(subparser) - - namespaced = parser.add_subparsers(title="Tools", help="Convenience client side tools") - - fine_tunes.register(namespaced) diff --git a/src/openai/cli/_tools/fine_tunes.py b/src/openai/cli/_tools/fine_tunes.py deleted file mode 100644 index 2128b88952..0000000000 --- a/src/openai/cli/_tools/fine_tunes.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import annotations - -import sys -from typing import TYPE_CHECKING -from argparse import ArgumentParser - -from .._models import BaseModel -from ...lib._validators import ( - get_validators, - write_out_file, - read_any_format, - apply_validators, - apply_necessary_remediation, -) - -if TYPE_CHECKING: - from argparse import _SubParsersAction - - -def register(subparser: _SubParsersAction[ArgumentParser]) -> None: - sub = subparser.add_parser("fine_tunes.prepare_data") - sub.add_argument( - "-f", - "--file", - required=True, - help="JSONL, JSON, CSV, TSV, TXT or XLSX file containing prompt-completion examples to be analyzed." - "This should be the local file path.", - ) - sub.add_argument( - "-q", - "--quiet", - required=False, - action="store_true", - help="Auto accepts all suggestions, without asking for user input. To be used within scripts.", - ) - sub.set_defaults(func=prepare_data, args_model=PrepareDataArgs) - - -class PrepareDataArgs(BaseModel): - file: str - - quiet: bool - - -def prepare_data(args: PrepareDataArgs) -> None: - sys.stdout.write("Analyzing...\n") - fname = args.file - auto_accept = args.quiet - df, remediation = read_any_format(fname) - apply_necessary_remediation(None, remediation) - - validators = get_validators() - - assert df is not None - - apply_validators( - df, - fname, - remediation, - validators, - auto_accept, - write_out_file_func=write_out_file, - ) diff --git a/src/openai/cli/_tools/migrate.py b/src/openai/cli/_tools/migrate.py deleted file mode 100644 index 7a0b0f90f6..0000000000 --- a/src/openai/cli/_tools/migrate.py +++ /dev/null @@ -1,164 +0,0 @@ -from __future__ import annotations - -import os -import sys -import shutil -import tarfile -import platform -import subprocess -from typing import TYPE_CHECKING, List -from pathlib import Path -from argparse import ArgumentParser - -import httpx - -from .._errors import CLIError, SilentCLIError -from .._models import BaseModel - -if TYPE_CHECKING: - from argparse import _SubParsersAction - - -def register(subparser: _SubParsersAction[ArgumentParser]) -> None: - sub = subparser.add_parser("migrate") - sub.set_defaults(func=migrate, args_model=MigrateArgs, allow_unknown_args=True) - - sub = subparser.add_parser("grit") - sub.set_defaults(func=grit, args_model=GritArgs, allow_unknown_args=True) - - -class GritArgs(BaseModel): - # internal - unknown_args: List[str] = [] - - -def grit(args: GritArgs) -> None: - grit_path = install() - - try: - subprocess.check_call([grit_path, *args.unknown_args]) - except subprocess.CalledProcessError: - # stdout and stderr are forwarded by subprocess so an error will already - # have been displayed - raise SilentCLIError() from None - - -class MigrateArgs(BaseModel): - # internal - unknown_args: List[str] = [] - - -def migrate(args: MigrateArgs) -> None: - grit_path = install() - - try: - subprocess.check_call([grit_path, "apply", "openai", *args.unknown_args]) - except subprocess.CalledProcessError: - # stdout and stderr are forwarded by subprocess so an error will already - # have been displayed - raise SilentCLIError() from None - - -# handles downloading the Grit CLI until they provide their own PyPi package - -KEYGEN_ACCOUNT = "custodian-dev" - - -def _cache_dir() -> Path: - xdg = os.environ.get("XDG_CACHE_HOME") - if xdg is not None: - return Path(xdg) - - return Path.home() / ".cache" - - -def _debug(message: str) -> None: - if not os.environ.get("DEBUG"): - return - - sys.stdout.write(f"[DEBUG]: {message}\n") - - -def install() -> Path: - """Installs the Grit CLI and returns the location of the binary""" - if sys.platform == "win32": - raise CLIError("Windows is not supported yet in the migration CLI") - - _debug("Using Grit installer from GitHub") - - platform = "apple-darwin" if sys.platform == "darwin" else "unknown-linux-gnu" - - dir_name = _cache_dir() / "openai-python" - install_dir = dir_name / ".install" - target_dir = install_dir / "bin" - - target_path = target_dir / "marzano" - temp_file = target_dir / "marzano.tmp" - - if target_path.exists(): - _debug(f"{target_path} already exists") - sys.stdout.flush() - return target_path - - _debug(f"Using Grit CLI path: {target_path}") - - target_dir.mkdir(parents=True, exist_ok=True) - - if temp_file.exists(): - temp_file.unlink() - - arch = _get_arch() - _debug(f"Using architecture {arch}") - - file_name = f"marzano-{arch}-{platform}" - download_url = f"https://github.com/getgrit/gritql/releases/latest/download/{file_name}.tar.gz" - - sys.stdout.write(f"Downloading Grit CLI from {download_url}\n") - with httpx.Client() as client: - download_response = client.get(download_url, follow_redirects=True) - if download_response.status_code != 200: - raise CLIError(f"Failed to download Grit CLI from {download_url}") - with open(temp_file, "wb") as file: - for chunk in download_response.iter_bytes(): - file.write(chunk) - - unpacked_dir = target_dir / "cli-bin" - unpacked_dir.mkdir(parents=True, exist_ok=True) - - with tarfile.open(temp_file, "r:gz") as archive: - if sys.version_info >= (3, 12): - archive.extractall(unpacked_dir, filter="data") - else: - archive.extractall(unpacked_dir) - - _move_files_recursively(unpacked_dir, target_dir) - - shutil.rmtree(unpacked_dir) - os.remove(temp_file) - os.chmod(target_path, 0o755) - - sys.stdout.flush() - - return target_path - - -def _move_files_recursively(source_dir: Path, target_dir: Path) -> None: - for item in source_dir.iterdir(): - if item.is_file(): - item.rename(target_dir / item.name) - elif item.is_dir(): - _move_files_recursively(item, target_dir) - - -def _get_arch() -> str: - architecture = platform.machine().lower() - - # Map the architecture names to Grit equivalents - arch_map = { - "x86_64": "x86_64", - "amd64": "x86_64", - "armv7l": "aarch64", - "arm64": "aarch64", - } - - return arch_map.get(architecture, architecture) diff --git a/src/openai/cli/_utils.py b/src/openai/cli/_utils.py deleted file mode 100644 index 673eed613c..0000000000 --- a/src/openai/cli/_utils.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import annotations - -import sys - -import openai - -from .. import OpenAI, _load_client -from .._compat import model_json -from .._models import BaseModel - - -class Colors: - HEADER = "\033[95m" - OKBLUE = "\033[94m" - OKGREEN = "\033[92m" - WARNING = "\033[93m" - FAIL = "\033[91m" - ENDC = "\033[0m" - BOLD = "\033[1m" - UNDERLINE = "\033[4m" - - -def get_client() -> OpenAI: - return _load_client() - - -def organization_info() -> str: - organization = openai.organization - if organization is not None: - return "[organization={}] ".format(organization) - - return "" - - -def print_model(model: BaseModel) -> None: - sys.stdout.write(model_json(model, indent=2) + "\n") - - -def can_use_http2() -> bool: - try: - import h2 # type: ignore # noqa - except ImportError: - return False - - return True diff --git a/src/openai/helpers/__init__.py b/src/openai/helpers/__init__.py new file mode 100644 index 0000000000..ab3044da59 --- /dev/null +++ b/src/openai/helpers/__init__.py @@ -0,0 +1,4 @@ +from .microphone import Microphone +from .local_audio_player import LocalAudioPlayer + +__all__ = ["Microphone", "LocalAudioPlayer"] diff --git a/src/openai/helpers/local_audio_player.py b/src/openai/helpers/local_audio_player.py new file mode 100644 index 0000000000..8f12c27a56 --- /dev/null +++ b/src/openai/helpers/local_audio_player.py @@ -0,0 +1,165 @@ +# mypy: ignore-errors +from __future__ import annotations + +import queue +import asyncio +from typing import Any, Union, Callable, AsyncGenerator, cast +from typing_extensions import TYPE_CHECKING + +from .. import _legacy_response +from .._extras import numpy as np, sounddevice as sd +from .._response import StreamedBinaryAPIResponse, AsyncStreamedBinaryAPIResponse + +if TYPE_CHECKING: + import numpy.typing as npt + +SAMPLE_RATE = 24000 + + +class LocalAudioPlayer: + def __init__( + self, + should_stop: Union[Callable[[], bool], None] = None, + ): + self.channels = 1 + self.dtype = np.float32 + self.should_stop = should_stop + + async def _tts_response_to_buffer( + self, + response: Union[ + _legacy_response.HttpxBinaryResponseContent, + AsyncStreamedBinaryAPIResponse, + StreamedBinaryAPIResponse, + ], + ) -> npt.NDArray[np.float32]: + chunks: list[bytes] = [] + if isinstance(response, _legacy_response.HttpxBinaryResponseContent) or isinstance( + response, StreamedBinaryAPIResponse + ): + for chunk in response.iter_bytes(chunk_size=1024): + if chunk: + chunks.append(chunk) + else: + async for chunk in response.iter_bytes(chunk_size=1024): + if chunk: + chunks.append(chunk) + + audio_bytes = b"".join(chunks) + audio_np = np.frombuffer(audio_bytes, dtype=np.int16).astype(np.float32) / 32767.0 + audio_np = audio_np.reshape(-1, 1) + return audio_np + + async def play( + self, + input: Union[ + npt.NDArray[np.int16], + npt.NDArray[np.float32], + _legacy_response.HttpxBinaryResponseContent, + AsyncStreamedBinaryAPIResponse, + StreamedBinaryAPIResponse, + ], + ) -> None: + audio_content: npt.NDArray[np.float32] + if isinstance(input, np.ndarray): + if input.dtype == np.int16 and self.dtype == np.float32: + audio_content = (input.astype(np.float32) / 32767.0).reshape(-1, self.channels) + elif input.dtype == np.float32: + audio_content = cast("npt.NDArray[np.float32]", input) + else: + raise ValueError(f"Unsupported dtype: {input.dtype}") + else: + audio_content = await self._tts_response_to_buffer(input) + + loop = asyncio.get_event_loop() + event = asyncio.Event() + idx = 0 + + def callback( + outdata: npt.NDArray[np.float32], + frame_count: int, + _time_info: Any, + _status: Any, + ): + nonlocal idx + + remainder = len(audio_content) - idx + if remainder == 0 or (callable(self.should_stop) and self.should_stop()): + loop.call_soon_threadsafe(event.set) + raise sd.CallbackStop + valid_frames = frame_count if remainder >= frame_count else remainder + outdata[:valid_frames] = audio_content[idx : idx + valid_frames] + outdata[valid_frames:] = 0 + idx += valid_frames + + stream = sd.OutputStream( + samplerate=SAMPLE_RATE, + callback=callback, + dtype=audio_content.dtype, + channels=audio_content.shape[1], + ) + with stream: + await event.wait() + + async def play_stream( + self, + buffer_stream: AsyncGenerator[Union[npt.NDArray[np.float32], npt.NDArray[np.int16], None], None], + ) -> None: + loop = asyncio.get_event_loop() + event = asyncio.Event() + buffer_queue: queue.Queue[Union[npt.NDArray[np.float32], npt.NDArray[np.int16], None]] = queue.Queue(maxsize=50) + + async def buffer_producer(): + async for buffer in buffer_stream: + if buffer is None: + break + await loop.run_in_executor(None, buffer_queue.put, buffer) + await loop.run_in_executor(None, buffer_queue.put, None) # Signal completion + + def callback( + outdata: npt.NDArray[np.float32], + frame_count: int, + _time_info: Any, + _status: Any, + ): + nonlocal current_buffer, buffer_pos + + frames_written = 0 + while frames_written < frame_count: + if current_buffer is None or buffer_pos >= len(current_buffer): + try: + current_buffer = buffer_queue.get(timeout=0.1) + if current_buffer is None: + loop.call_soon_threadsafe(event.set) + raise sd.CallbackStop + buffer_pos = 0 + + if current_buffer.dtype == np.int16 and self.dtype == np.float32: + current_buffer = (current_buffer.astype(np.float32) / 32767.0).reshape(-1, self.channels) + + except queue.Empty: + outdata[frames_written:] = 0 + return + + remaining_frames = len(current_buffer) - buffer_pos + frames_to_write = min(frame_count - frames_written, remaining_frames) + outdata[frames_written : frames_written + frames_to_write] = current_buffer[ + buffer_pos : buffer_pos + frames_to_write + ] + buffer_pos += frames_to_write + frames_written += frames_to_write + + current_buffer = None + buffer_pos = 0 + + producer_task = asyncio.create_task(buffer_producer()) + + with sd.OutputStream( + samplerate=SAMPLE_RATE, + channels=self.channels, + dtype=self.dtype, + callback=callback, + ): + await event.wait() + + await producer_task diff --git a/src/openai/helpers/microphone.py b/src/openai/helpers/microphone.py new file mode 100644 index 0000000000..62a6d8d8a9 --- /dev/null +++ b/src/openai/helpers/microphone.py @@ -0,0 +1,100 @@ +# mypy: ignore-errors +from __future__ import annotations + +import io +import time +import wave +import asyncio +from typing import Any, Type, Union, Generic, TypeVar, Callable, overload +from typing_extensions import TYPE_CHECKING, Literal + +from .._types import FileTypes, FileContent +from .._extras import numpy as np, sounddevice as sd + +if TYPE_CHECKING: + import numpy.typing as npt + +SAMPLE_RATE = 24000 + +DType = TypeVar("DType", bound=np.generic) + + +class Microphone(Generic[DType]): + def __init__( + self, + channels: int = 1, + dtype: Type[DType] = np.int16, + should_record: Union[Callable[[], bool], None] = None, + timeout: Union[float, None] = None, + ): + self.channels = channels + self.dtype = dtype + self.should_record = should_record + self.buffer_chunks = [] + self.timeout = timeout + self.has_record_function = callable(should_record) + + def _ndarray_to_wav(self, audio_data: npt.NDArray[DType]) -> FileTypes: + buffer: FileContent = io.BytesIO() + with wave.open(buffer, "w") as wav_file: + wav_file.setnchannels(self.channels) + wav_file.setsampwidth(np.dtype(self.dtype).itemsize) + wav_file.setframerate(SAMPLE_RATE) + wav_file.writeframes(audio_data.tobytes()) + buffer.seek(0) + return ("audio.wav", buffer, "audio/wav") + + @overload + async def record(self, return_ndarray: Literal[True]) -> npt.NDArray[DType]: ... + + @overload + async def record(self, return_ndarray: Literal[False]) -> FileTypes: ... + + @overload + async def record(self, return_ndarray: None = ...) -> FileTypes: ... + + async def record(self, return_ndarray: Union[bool, None] = False) -> Union[npt.NDArray[DType], FileTypes]: + loop = asyncio.get_event_loop() + event = asyncio.Event() + self.buffer_chunks: list[npt.NDArray[DType]] = [] + start_time = time.perf_counter() + + def callback( + indata: npt.NDArray[DType], + _frame_count: int, + _time_info: Any, + _status: Any, + ): + execution_time = time.perf_counter() - start_time + reached_recording_timeout = execution_time > self.timeout if self.timeout is not None else False + if reached_recording_timeout: + loop.call_soon_threadsafe(event.set) + raise sd.CallbackStop + + should_be_recording = self.should_record() if callable(self.should_record) else True + if not should_be_recording: + loop.call_soon_threadsafe(event.set) + raise sd.CallbackStop + + self.buffer_chunks.append(indata.copy()) + + stream = sd.InputStream( + callback=callback, + dtype=self.dtype, + samplerate=SAMPLE_RATE, + channels=self.channels, + ) + with stream: + await event.wait() + + # Concatenate all chunks into a single buffer, handle empty case + concatenated_chunks: npt.NDArray[DType] = ( + np.concatenate(self.buffer_chunks, axis=0) + if len(self.buffer_chunks) > 0 + else np.array([], dtype=self.dtype) + ) + + if return_ndarray: + return concatenated_chunks + else: + return self._ndarray_to_wav(concatenated_chunks) diff --git a/src/openai/lib/_parsing/__init__.py b/src/openai/lib/_parsing/__init__.py index 4d454c3a20..08591f43f4 100644 --- a/src/openai/lib/_parsing/__init__.py +++ b/src/openai/lib/_parsing/__init__.py @@ -6,7 +6,6 @@ validate_input_tools as validate_input_tools, parse_chat_completion as parse_chat_completion, get_input_tool_by_name as get_input_tool_by_name, - solve_response_format_t as solve_response_format_t, parse_function_tool_arguments as parse_function_tool_arguments, type_to_response_format_param as type_to_response_format_param, ) diff --git a/src/openai/lib/_parsing/_completions.py b/src/openai/lib/_parsing/_completions.py index f1fa9f2b55..7a1bded1de 100644 --- a/src/openai/lib/_parsing/_completions.py +++ b/src/openai/lib/_parsing/_completions.py @@ -1,15 +1,16 @@ from __future__ import annotations import json +import logging from typing import TYPE_CHECKING, Any, Iterable, cast from typing_extensions import TypeVar, TypeGuard, assert_never import pydantic from .._tools import PydanticFunctionTool -from ..._types import NOT_GIVEN, NotGiven +from ..._types import Omit, omit from ..._utils import is_dict, is_given -from ..._compat import PYDANTIC_V2, model_parse_json +from ..._compat import PYDANTIC_V1, model_parse_json from ..._models import construct_type_unchecked from .._pydantic import is_basemodel_type, to_strict_json_schema, is_dataclass_like_type from ...types.chat import ( @@ -19,14 +20,15 @@ ParsedChatCompletion, ChatCompletionMessage, ParsedFunctionToolCall, - ChatCompletionToolParam, ParsedChatCompletionMessage, + ChatCompletionToolUnionParam, + ChatCompletionFunctionToolParam, completion_create_params, ) from ..._exceptions import LengthFinishReasonError, ContentFilterFinishReasonError from ...types.shared_params import FunctionDefinition from ...types.chat.completion_create_params import ResponseFormat as ResponseFormatParam -from ...types.chat.chat_completion_message_tool_call import Function +from ...types.chat.chat_completion_message_function_tool_call import Function ResponseFormatT = TypeVar( "ResponseFormatT", @@ -35,30 +37,56 @@ ) _default_response_format: None = None +log: logging.Logger = logging.getLogger("openai.lib.parsing") + + +def is_strict_chat_completion_tool_param( + tool: ChatCompletionToolUnionParam, +) -> TypeGuard[ChatCompletionFunctionToolParam]: + """Check if the given tool is a strict ChatCompletionFunctionToolParam.""" + if not tool["type"] == "function": + return False + if tool["function"].get("strict") is not True: + return False + + return True + + +def select_strict_chat_completion_tools( + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, +) -> Iterable[ChatCompletionFunctionToolParam] | Omit: + """Select only the strict ChatCompletionFunctionToolParams from the given tools.""" + if not is_given(tools): + return omit + + return [t for t in tools if is_strict_chat_completion_tool_param(t)] + def validate_input_tools( - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, -) -> None: + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, +) -> Iterable[ChatCompletionFunctionToolParam] | Omit: if not is_given(tools): - return + return omit for tool in tools: if tool["type"] != "function": raise ValueError( - f'Currently only `function` tool types support auto-parsing; Received `{tool["type"]}`', + f"Currently only `function` tool types support auto-parsing; Received `{tool['type']}`", ) strict = tool["function"].get("strict") if strict is not True: raise ValueError( - f'`{tool["function"]["name"]}` is not strict. Only `strict` function tools can be auto-parsed' + f"`{tool['function']['name']}` is not strict. Only `strict` function tools can be auto-parsed" ) + return cast(Iterable[ChatCompletionFunctionToolParam], tools) + def parse_chat_completion( *, - response_format: type[ResponseFormatT] | completion_create_params.ResponseFormat | NotGiven, - input_tools: Iterable[ChatCompletionToolParam] | NotGiven, + response_format: type[ResponseFormatT] | completion_create_params.ResponseFormat | Omit, + input_tools: Iterable[ChatCompletionToolUnionParam] | Omit, chat_completion: ChatCompletion | ParsedChatCompletion[object], ) -> ParsedChatCompletion[ResponseFormatT]: if is_given(input_tools): @@ -95,6 +123,14 @@ def parse_chat_completion( type_=ParsedFunctionToolCall, ) ) + elif tool_call.type == "custom": + # warn user that custom tool calls are not callable here + log.warning( + "Custom tool calls are not callable. Ignoring tool call: %s - %s", + tool_call.id, + tool_call.custom.name, + stacklevel=2, + ) elif TYPE_CHECKING: # type: ignore[unreachable] assert_never(tool_call) else: @@ -102,7 +138,7 @@ def parse_chat_completion( choices.append( construct_type_unchecked( - type_=cast(Any, ParsedChoice)[solve_response_format_t(response_format)], + type_=ParsedChoice[ResponseFormatT], value={ **choice.to_dict(), "message": { @@ -111,31 +147,30 @@ def parse_chat_completion( response_format=response_format, message=message, ), - "tool_calls": tool_calls, + "tool_calls": tool_calls if tool_calls else None, }, }, ) ) - return cast( - ParsedChatCompletion[ResponseFormatT], - construct_type_unchecked( - type_=cast(Any, ParsedChatCompletion)[solve_response_format_t(response_format)], - value={ - **chat_completion.to_dict(), - "choices": choices, - }, - ), + return construct_type_unchecked( + type_=ParsedChatCompletion[ResponseFormatT], + value={ + **chat_completion.to_dict(), + "choices": choices, + }, ) -def get_input_tool_by_name(*, input_tools: list[ChatCompletionToolParam], name: str) -> ChatCompletionToolParam | None: - return next((t for t in input_tools if t.get("function", {}).get("name") == name), None) +def get_input_tool_by_name( + *, input_tools: list[ChatCompletionToolUnionParam], name: str +) -> ChatCompletionFunctionToolParam | None: + return next((t for t in input_tools if t["type"] == "function" and t.get("function", {}).get("name") == name), None) def parse_function_tool_arguments( - *, input_tools: list[ChatCompletionToolParam], function: Function | ParsedFunction -) -> object: + *, input_tools: list[ChatCompletionToolUnionParam], function: Function | ParsedFunction +) -> object | None: input_tool = get_input_tool_by_name(input_tools=input_tools, name=function.name) if not input_tool: return None @@ -149,38 +184,24 @@ def parse_function_tool_arguments( if not input_fn.get("strict"): return None - return json.loads(function.arguments) + return json.loads(function.arguments) # type: ignore[no-any-return] def maybe_parse_content( *, - response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven, + response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, message: ChatCompletionMessage | ParsedChatCompletionMessage[object], ) -> ResponseFormatT | None: - if has_rich_response_format(response_format) and message.content is not None and not message.refusal: + if has_rich_response_format(response_format) and message.content and not message.refusal: return _parse_content(response_format, message.content) return None -def solve_response_format_t( - response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven, -) -> type[ResponseFormatT]: - """Return the runtime type for the given response format. - - If no response format is given, or if we won't auto-parse the response format - then we default to `None`. - """ - if has_rich_response_format(response_format): - return response_format - - return cast("type[ResponseFormatT]", _default_response_format) - - def has_parseable_input( *, - response_format: type | ResponseFormatParam | NotGiven, - input_tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, + response_format: type | ResponseFormatParam | Omit, + input_tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, ) -> bool: if has_rich_response_format(response_format): return True @@ -193,7 +214,7 @@ def has_parseable_input( def has_rich_response_format( - response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven, + response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, ) -> TypeGuard[type[ResponseFormatT]]: if not is_given(response_format): return False @@ -208,7 +229,10 @@ def is_response_format_param(response_format: object) -> TypeGuard[ResponseForma return is_dict(response_format) -def is_parseable_tool(input_tool: ChatCompletionToolParam) -> bool: +def is_parseable_tool(input_tool: ChatCompletionToolUnionParam) -> bool: + if input_tool["type"] != "function": + return False + input_fn = cast(object, input_tool.get("function")) if isinstance(input_fn, PydanticFunctionTool): return True @@ -221,7 +245,7 @@ def _parse_content(response_format: type[ResponseFormatT], content: str) -> Resp return cast(ResponseFormatT, model_parse_json(response_format, content)) if is_dataclass_like_type(response_format): - if not PYDANTIC_V2: + if PYDANTIC_V1: raise TypeError(f"Non BaseModel types are only supported with Pydantic v2 - {response_format}") return pydantic.TypeAdapter(response_format).validate_json(content) @@ -230,10 +254,10 @@ def _parse_content(response_format: type[ResponseFormatT], content: str) -> Resp def type_to_response_format_param( - response_format: type | completion_create_params.ResponseFormat | NotGiven, -) -> ResponseFormatParam | NotGiven: + response_format: type | completion_create_params.ResponseFormat | Omit, +) -> ResponseFormatParam | Omit: if not is_given(response_format): - return NOT_GIVEN + return omit if is_response_format_param(response_format): return response_format diff --git a/src/openai/lib/_parsing/_responses.py b/src/openai/lib/_parsing/_responses.py new file mode 100644 index 0000000000..232718cef6 --- /dev/null +++ b/src/openai/lib/_parsing/_responses.py @@ -0,0 +1,185 @@ +from __future__ import annotations + +import json +from typing import TYPE_CHECKING, List, Iterable, cast +from typing_extensions import TypeVar, assert_never + +import pydantic + +from .._tools import ResponsesPydanticFunctionTool +from ..._types import Omit +from ..._utils import is_given +from ..._compat import PYDANTIC_V1, model_parse_json +from ..._models import construct_type_unchecked +from .._pydantic import is_basemodel_type, is_dataclass_like_type +from ._completions import type_to_response_format_param +from ...types.responses import ( + Response, + ToolParam, + ParsedContent, + ParsedResponse, + FunctionToolParam, + ParsedResponseOutputItem, + ParsedResponseOutputText, + ResponseFunctionToolCall, + ParsedResponseOutputMessage, + ResponseFormatTextConfigParam, + ParsedResponseFunctionToolCall, +) +from ...types.chat.completion_create_params import ResponseFormat + +TextFormatT = TypeVar( + "TextFormatT", + # if it isn't given then we don't do any parsing + default=None, +) + + +def type_to_text_format_param(type_: type) -> ResponseFormatTextConfigParam: + response_format_dict = type_to_response_format_param(type_) + assert is_given(response_format_dict) + response_format_dict = cast(ResponseFormat, response_format_dict) # pyright: ignore[reportUnnecessaryCast] + assert response_format_dict["type"] == "json_schema" + assert "schema" in response_format_dict["json_schema"] + + return { + "type": "json_schema", + "strict": True, + "name": response_format_dict["json_schema"]["name"], + "schema": response_format_dict["json_schema"]["schema"], + } + + +def parse_response( + *, + text_format: type[TextFormatT] | Omit, + input_tools: Iterable[ToolParam] | Omit | None, + response: Response | ParsedResponse[object], +) -> ParsedResponse[TextFormatT]: + output_list: List[ParsedResponseOutputItem[TextFormatT]] = [] + + for output in response.output: + if output.type == "message": + content_list: List[ParsedContent[TextFormatT]] = [] + for item in output.content: + if item.type != "output_text": + content_list.append(item) + continue + + content_list.append( + construct_type_unchecked( + type_=ParsedResponseOutputText[TextFormatT], + value={ + **item.to_dict(), + "parsed": parse_text(item.text, text_format=text_format), + }, + ) + ) + + output_list.append( + construct_type_unchecked( + type_=ParsedResponseOutputMessage[TextFormatT], + value={ + **output.to_dict(), + "content": content_list, + }, + ) + ) + elif output.type == "function_call": + output_list.append( + construct_type_unchecked( + type_=ParsedResponseFunctionToolCall, + value={ + **output.to_dict(), + "parsed_arguments": parse_function_tool_arguments( + input_tools=input_tools, function_call=output + ), + }, + ) + ) + elif ( + output.type == "computer_call" + or output.type == "file_search_call" + or output.type == "web_search_call" + or output.type == "tool_search_call" + or output.type == "tool_search_output" + or output.type == "additional_tools" + or output.type == "reasoning" + or output.type == "compaction" + or output.type == "mcp_call" + or output.type == "mcp_approval_request" + or output.type == "mcp_approval_response" + or output.type == "image_generation_call" + or output.type == "code_interpreter_call" + or output.type == "local_shell_call" + or output.type == "local_shell_call_output" + or output.type == "shell_call" + or output.type == "shell_call_output" + or output.type == "apply_patch_call" + or output.type == "apply_patch_call_output" + or output.type == "mcp_list_tools" + or output.type == "exec" + or output.type == "custom_tool_call" + or output.type == "function_call_output" + or output.type == "computer_call_output" + or output.type == "custom_tool_call_output" + ): + output_list.append(output) + elif TYPE_CHECKING: # type: ignore + assert_never(output) + else: + output_list.append(output) + + return construct_type_unchecked( + type_=ParsedResponse[TextFormatT], + value={ + **response.to_dict(), + "output": output_list, + }, + ) + + +def parse_text(text: str, text_format: type[TextFormatT] | Omit) -> TextFormatT | None: + if not is_given(text_format): + return None + + if is_basemodel_type(text_format): + return cast(TextFormatT, model_parse_json(text_format, text)) + + if is_dataclass_like_type(text_format): + if PYDANTIC_V1: + raise TypeError(f"Non BaseModel types are only supported with Pydantic v2 - {text_format}") + + return pydantic.TypeAdapter(text_format).validate_json(text) + + raise TypeError(f"Unable to automatically parse response format type {text_format}") + + +def get_input_tool_by_name(*, input_tools: Iterable[ToolParam], name: str) -> FunctionToolParam | None: + for tool in input_tools: + if tool["type"] == "function" and tool.get("name") == name: + return tool + + return None + + +def parse_function_tool_arguments( + *, + input_tools: Iterable[ToolParam] | Omit | None, + function_call: ParsedResponseFunctionToolCall | ResponseFunctionToolCall, +) -> object: + if input_tools is None or not is_given(input_tools): + return None + + input_tool = get_input_tool_by_name(input_tools=input_tools, name=function_call.name) + if not input_tool: + return None + + tool = cast(object, input_tool) + if isinstance(tool, ResponsesPydanticFunctionTool): + return model_parse_json(tool.model, function_call.arguments) + + if not input_tool.get("strict"): + return None + + return json.loads(function_call.arguments) diff --git a/src/openai/lib/_pydantic.py b/src/openai/lib/_pydantic.py index 22c7a1f3cd..3cfe224cb1 100644 --- a/src/openai/lib/_pydantic.py +++ b/src/openai/lib/_pydantic.py @@ -8,7 +8,7 @@ from .._types import NOT_GIVEN from .._utils import is_dict as _is_dict, is_list -from .._compat import PYDANTIC_V2, model_json_schema +from .._compat import PYDANTIC_V1, model_json_schema _T = TypeVar("_T") @@ -16,7 +16,7 @@ def to_strict_json_schema(model: type[pydantic.BaseModel] | pydantic.TypeAdapter[Any]) -> dict[str, Any]: if inspect.isclass(model) and is_basemodel_type(model): schema = model_json_schema(model) - elif PYDANTIC_V2 and isinstance(model, pydantic.TypeAdapter): + elif (not PYDANTIC_V1) and isinstance(model, pydantic.TypeAdapter): schema = model.json_schema() else: raise TypeError(f"Non BaseModel types are only supported with Pydantic v2 - {model}") @@ -108,6 +108,9 @@ def _ensure_strict_json_schema( # properties from the json schema take priority over the ones on the `$ref` json_schema.update({**resolved, **json_schema}) json_schema.pop("$ref") + # Since the schema expanded from `$ref` might not have `additionalProperties: false` applied, + # we call `_ensure_strict_json_schema` again to fix the inlined schema and ensure it's valid. + return _ensure_strict_json_schema(json_schema, path=path, root=root) return json_schema @@ -127,6 +130,8 @@ def resolve_ref(*, root: dict[str, object], ref: str) -> object: def is_basemodel_type(typ: type) -> TypeGuard[type[pydantic.BaseModel]]: + if not inspect.isclass(typ): + return False return issubclass(typ, pydantic.BaseModel) diff --git a/src/openai/lib/_realtime.py b/src/openai/lib/_realtime.py new file mode 100644 index 0000000000..3771b52986 --- /dev/null +++ b/src/openai/lib/_realtime.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +import json +from typing_extensions import override + +import httpx + +from openai import _legacy_response +from openai._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from openai._utils import maybe_transform, async_maybe_transform +from openai._base_client import make_request_options +from openai.resources.realtime.calls import Calls, AsyncCalls +from openai.types.realtime.realtime_session_create_request_param import RealtimeSessionCreateRequestParam + +__all__ = ["_Calls", "_AsyncCalls"] + + +# Custom code to override the `create` method to have correct behavior with +# application/sdp and multipart/form-data. +# Ideally we can cutover to the generated code this overrides eventually and remove this. +class _Calls(Calls): + @override + def create( + self, + *, + sdp: str, + session: RealtimeSessionCreateRequestParam | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + if session is omit: + extra_headers = {"Accept": "application/sdp", "Content-Type": "application/sdp", **(extra_headers or {})} + return self._post( + "/realtime/calls", + content=sdp.encode("utf-8"), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, timeout=timeout), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + extra_headers = {"Accept": "application/sdp", "Content-Type": "multipart/form-data", **(extra_headers or {})} + session_payload = maybe_transform(session, RealtimeSessionCreateRequestParam) + files = [ + ("sdp", (None, sdp.encode("utf-8"), "application/sdp")), + ("session", (None, json.dumps(session_payload).encode("utf-8"), "application/json")), + ] + return self._post( + "/realtime/calls", + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + +class _AsyncCalls(AsyncCalls): + @override + async def create( + self, + *, + sdp: str, + session: RealtimeSessionCreateRequestParam | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + if session is omit: + extra_headers = {"Accept": "application/sdp", "Content-Type": "application/sdp", **(extra_headers or {})} + return await self._post( + "/realtime/calls", + content=sdp.encode("utf-8"), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, timeout=timeout), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + extra_headers = {"Accept": "application/sdp", "Content-Type": "multipart/form-data", **(extra_headers or {})} + session_payload = await async_maybe_transform(session, RealtimeSessionCreateRequestParam) + files = [ + ("sdp", (None, sdp.encode("utf-8"), "application/sdp")), + ("session", (None, json.dumps(session_payload).encode("utf-8"), "application/json")), + ] + return await self._post( + "/realtime/calls", + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) diff --git a/src/openai/lib/_tools.py b/src/openai/lib/_tools.py index 8478ed676c..4070ad63bb 100644 --- a/src/openai/lib/_tools.py +++ b/src/openai/lib/_tools.py @@ -5,8 +5,9 @@ import pydantic from ._pydantic import to_strict_json_schema -from ..types.chat import ChatCompletionToolParam +from ..types.chat import ChatCompletionFunctionToolParam from ..types.shared_params import FunctionDefinition +from ..types.responses.function_tool_param import FunctionToolParam as ResponsesFunctionToolParam class PydanticFunctionTool(Dict[str, Any]): @@ -25,12 +26,23 @@ def cast(self) -> FunctionDefinition: return cast(FunctionDefinition, self) +class ResponsesPydanticFunctionTool(Dict[str, Any]): + model: type[pydantic.BaseModel] + + def __init__(self, tool: ResponsesFunctionToolParam, model: type[pydantic.BaseModel]) -> None: + super().__init__(tool) + self.model = model + + def cast(self) -> ResponsesFunctionToolParam: + return cast(ResponsesFunctionToolParam, self) + + def pydantic_function_tool( model: type[pydantic.BaseModel], *, name: str | None = None, # inferred from class name by default description: str | None = None, # inferred from class docstring by default -) -> ChatCompletionToolParam: +) -> ChatCompletionFunctionToolParam: if description is None: # note: we intentionally don't use `.getdoc()` to avoid # including pydantic's docstrings diff --git a/src/openai/lib/azure.py b/src/openai/lib/azure.py index ef64137de4..4fcae24788 100644 --- a/src/openai/lib/azure.py +++ b/src/openai/lib/azure.py @@ -7,11 +7,12 @@ import httpx -from .._types import NOT_GIVEN, Omit, Timeout, NotGiven +from ..auth import WorkloadIdentity +from .._types import NOT_GIVEN, Omit, Query, Headers, Timeout, NotGiven from .._utils import is_given, is_mapping from .._client import OpenAI, AsyncOpenAI from .._compat import model_copy -from .._models import FinalRequestOptions +from .._models import SecurityOptions, FinalRequestOptions from .._streaming import Stream, AsyncStream from .._exceptions import OpenAIError from .._base_client import DEFAULT_MAX_RETRIES, BaseClient @@ -25,6 +26,7 @@ "/audio/translations", "/audio/speech", "/images/generations", + "/images/edits", ] ) @@ -41,6 +43,15 @@ API_KEY_SENTINEL = "".join(["<", "missing API key", ">"]) +def _has_header(headers: Headers, header: str) -> bool: + header = header.lower() + return any(key.lower() == header for key in headers) + + +def _has_auth_header(headers: Headers) -> bool: + return _has_header(headers, "Authorization") or _has_header(headers, "api-key") + + class MutuallyExclusiveAuthError(OpenAIError): def __init__(self) -> None: super().__init__( @@ -49,17 +60,40 @@ def __init__(self) -> None: class BaseAzureClient(BaseClient[_HttpxClientT, _DefaultStreamT]): + _azure_endpoint: httpx.URL | None + _azure_deployment: str | None + @override def _build_request( self, options: FinalRequestOptions, + *, + retries_taken: int = 0, ) -> httpx.Request: if options.url in _deployments_endpoints and is_mapping(options.json_data): model = options.json_data.get("model") - if model is not None and not "/deployments" in str(self.base_url): + if model is not None and "/deployments" not in str(self.base_url.path): options.url = f"/deployments/{model}{options.url}" - return super()._build_request(options) + return super()._build_request(options, retries_taken=retries_taken) + + @override + def _prepare_url(self, url: str) -> httpx.URL: + """Adjust the URL if the client was configured with an Azure endpoint + deployment + and the API feature being called is **not** a deployments-based endpoint + (i.e. requires /deployments/deployment-name in the URL path). + """ + if self._azure_deployment and self._azure_endpoint and url not in _deployments_endpoints: + merge_url = httpx.URL(url) + if merge_url.is_relative_url: + merge_raw_path = ( + self._azure_endpoint.raw_path.rstrip(b"/") + b"/openai/" + merge_url.raw_path.lstrip(b"/") + ) + return self._azure_endpoint.copy_with(raw_path=merge_raw_path) + + return merge_url + + return super()._prepare_url(url) class AzureOpenAI(BaseAzureClient[httpx.Client, Stream[Any]], OpenAI): @@ -70,16 +104,20 @@ def __init__( azure_endpoint: str, azure_deployment: str | None = None, api_version: str | None = None, - api_key: str | None = None, + api_key: str | Callable[[], str] | None = None, + admin_api_key: str | None = None, azure_ad_token: str | None = None, azure_ad_token_provider: AzureADTokenProvider | None = None, organization: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, http_client: httpx.Client | None = None, _strict_response_validation: bool = False, + _enforce_credentials: bool = True, ) -> None: ... @overload @@ -88,16 +126,20 @@ def __init__( *, azure_deployment: str | None = None, api_version: str | None = None, - api_key: str | None = None, + api_key: str | Callable[[], str] | None = None, + admin_api_key: str | None = None, azure_ad_token: str | None = None, azure_ad_token_provider: AzureADTokenProvider | None = None, organization: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, http_client: httpx.Client | None = None, _strict_response_validation: bool = False, + _enforce_credentials: bool = True, ) -> None: ... @overload @@ -106,16 +148,20 @@ def __init__( *, base_url: str, api_version: str | None = None, - api_key: str | None = None, + api_key: str | Callable[[], str] | None = None, + admin_api_key: str | None = None, azure_ad_token: str | None = None, azure_ad_token_provider: AzureADTokenProvider | None = None, organization: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, http_client: httpx.Client | None = None, _strict_response_validation: bool = False, + _enforce_credentials: bool = True, ) -> None: ... def __init__( @@ -124,11 +170,16 @@ def __init__( api_version: str | None = None, azure_endpoint: str | None = None, azure_deployment: str | None = None, - api_key: str | None = None, + api_key: str | Callable[[], str] | None = None, + admin_api_key: str | None = None, + # workload_identity is not functional in the Azure client + workload_identity: WorkloadIdentity | None = None, # noqa: ARG002 azure_ad_token: str | None = None, azure_ad_token_provider: AzureADTokenProvider | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, base_url: str | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, @@ -136,6 +187,7 @@ def __init__( default_query: Mapping[str, object] | None = None, http_client: httpx.Client | None = None, _strict_response_validation: bool = False, + _enforce_credentials: bool = True, ) -> None: """Construct a new synchronous azure openai client instance. @@ -154,8 +206,8 @@ def __init__( azure_ad_token_provider: A function that returns an Azure Active Directory token, will be invoked on every request. - azure_deployment: A model deployment, if given sets the base client URL to include `/deployments/{azure_deployment}`. - Note: this means you won't be able to use non-deployment endpoints. Not supported with Assistants APIs. + azure_deployment: A model deployment, if given with `azure_endpoint`, sets the base client URL to include `/deployments/{azure_deployment}`. + Not supported with Assistants APIs. """ if api_key is None: api_key = os.environ.get("AZURE_OPENAI_API_KEY") @@ -163,7 +215,7 @@ def __init__( if azure_ad_token is None: azure_ad_token = os.environ.get("AZURE_OPENAI_AD_TOKEN") - if api_key is None and azure_ad_token is None and azure_ad_token_provider is None: + if _enforce_credentials and api_key is None and azure_ad_token is None and azure_ad_token_provider is None: raise OpenAIError( "Missing credentials. Please pass one of `api_key`, `azure_ad_token`, `azure_ad_token_provider`, or the `AZURE_OPENAI_API_KEY` or `AZURE_OPENAI_AD_TOKEN` environment variables." ) @@ -191,9 +243,9 @@ def __init__( ) if azure_deployment is not None: - base_url = f"{azure_endpoint}/openai/deployments/{azure_deployment}" + base_url = f"{azure_endpoint.rstrip('/')}/openai/deployments/{azure_deployment}" else: - base_url = f"{azure_endpoint}/openai" + base_url = f"{azure_endpoint.rstrip('/')}/openai" else: if azure_endpoint is not None: raise ValueError("base_url and azure_endpoint are mutually exclusive") @@ -204,27 +256,37 @@ def __init__( super().__init__( api_key=api_key, + admin_api_key=admin_api_key, organization=organization, project=project, + webhook_secret=webhook_secret, base_url=base_url, timeout=timeout, max_retries=max_retries, default_headers=default_headers, default_query=default_query, http_client=http_client, + websocket_base_url=websocket_base_url, _strict_response_validation=_strict_response_validation, + _enforce_credentials=_enforce_credentials, ) self._api_version = api_version self._azure_ad_token = azure_ad_token self._azure_ad_token_provider = azure_ad_token_provider + self._azure_deployment = azure_deployment if azure_endpoint else None + self._azure_endpoint = httpx.URL(azure_endpoint) if azure_endpoint else None @override def copy( self, *, - api_key: str | None = None, + api_key: str | Callable[[], str] | None = None, + admin_api_key: str | None = None, + workload_identity: WorkloadIdentity | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, api_version: str | None = None, azure_ad_token: str | None = None, azure_ad_token_provider: AzureADTokenProvider | None = None, @@ -236,6 +298,7 @@ def copy( set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, set_default_query: Mapping[str, object] | None = None, + _enforce_credentials: bool | None = None, _extra_kwargs: Mapping[str, Any] = {}, ) -> Self: """ @@ -243,8 +306,12 @@ def copy( """ return super().copy( api_key=api_key, + admin_api_key=admin_api_key, + workload_identity=workload_identity, organization=organization, project=project, + webhook_secret=webhook_secret, + websocket_base_url=websocket_base_url, base_url=base_url, timeout=timeout, http_client=http_client, @@ -253,6 +320,7 @@ def copy( set_default_headers=set_default_headers, default_query=default_query, set_default_query=set_default_query, + _enforce_credentials=_enforce_credentials, _extra_kwargs={ "api_version": api_version or self._api_version, "azure_ad_token": azure_ad_token or self._azure_ad_token, @@ -278,6 +346,25 @@ def _get_azure_ad_token(self) -> str | None: return None + @override + def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: # noqa: ARG002 + if self._azure_ad_token is not None: + return {"Authorization": f"Bearer {self._azure_ad_token}"} + + if self.api_key and self.api_key != API_KEY_SENTINEL: + return {"api-key": self.api_key} + + return {} + + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if _has_auth_header(headers) or _has_auth_header(custom_headers): + return + + raise TypeError( + '"Could not resolve authentication method. Expected either api_key, azure_ad_token or azure_ad_token_provider to be set. Or for one of the `Authorization` or `api-key` headers to be explicitly supplied or omitted"' + ) + @override def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: headers: dict[str, str | Omit] = {**options.headers} if is_given(options.headers) else {} @@ -287,17 +374,44 @@ def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: azure_ad_token = self._get_azure_ad_token() if azure_ad_token is not None: - if headers.get("Authorization") is None: + if not _has_header(headers, "Authorization"): headers["Authorization"] = f"Bearer {azure_ad_token}" - elif self.api_key is not API_KEY_SENTINEL: - if headers.get("api-key") is None: + elif self.api_key and self.api_key != API_KEY_SENTINEL: + if not _has_header(headers, "api-key"): headers["api-key"] = self.api_key + elif _has_auth_header(headers) or _has_auth_header(self.default_headers): + pass else: # should never be hit raise ValueError("Unable to handle auth") return options + def _configure_realtime(self, model: str, extra_query: Query) -> tuple[httpx.URL, dict[str, str]]: + auth_headers = {} + query = { + **extra_query, + "api-version": self._api_version, + "deployment": self._azure_deployment or model, + } + if self.api_key and self.api_key != "": + auth_headers = {"api-key": self.api_key} + else: + token = self._get_azure_ad_token() + if token: + auth_headers = {"Authorization": f"Bearer {token}"} + + if self.websocket_base_url is not None: + base_url = httpx.URL(self.websocket_base_url) + merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime" + realtime_url = base_url.copy_with(raw_path=merge_raw_path) + else: + base_url = self._prepare_url("/realtime") + realtime_url = base_url.copy_with(scheme="wss") + + url = realtime_url.copy_with(params={**query}) + return url, auth_headers + class AsyncAzureOpenAI(BaseAzureClient[httpx.AsyncClient, AsyncStream[Any]], AsyncOpenAI): @overload @@ -307,17 +421,21 @@ def __init__( azure_endpoint: str, azure_deployment: str | None = None, api_version: str | None = None, - api_key: str | None = None, + api_key: str | Callable[[], Awaitable[str]] | None = None, + admin_api_key: str | None = None, azure_ad_token: str | None = None, azure_ad_token_provider: AsyncAzureADTokenProvider | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, http_client: httpx.AsyncClient | None = None, _strict_response_validation: bool = False, + _enforce_credentials: bool = True, ) -> None: ... @overload @@ -326,17 +444,21 @@ def __init__( *, azure_deployment: str | None = None, api_version: str | None = None, - api_key: str | None = None, + api_key: str | Callable[[], Awaitable[str]] | None = None, + admin_api_key: str | None = None, azure_ad_token: str | None = None, azure_ad_token_provider: AsyncAzureADTokenProvider | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, http_client: httpx.AsyncClient | None = None, _strict_response_validation: bool = False, + _enforce_credentials: bool = True, ) -> None: ... @overload @@ -345,17 +467,21 @@ def __init__( *, base_url: str, api_version: str | None = None, - api_key: str | None = None, + api_key: str | Callable[[], Awaitable[str]] | None = None, + admin_api_key: str | None = None, azure_ad_token: str | None = None, azure_ad_token_provider: AsyncAzureADTokenProvider | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, http_client: httpx.AsyncClient | None = None, _strict_response_validation: bool = False, + _enforce_credentials: bool = True, ) -> None: ... def __init__( @@ -364,18 +490,24 @@ def __init__( azure_endpoint: str | None = None, azure_deployment: str | None = None, api_version: str | None = None, - api_key: str | None = None, + api_key: str | Callable[[], Awaitable[str]] | None = None, + admin_api_key: str | None = None, + # workload_identity is not functional in the Azure client + workload_identity: WorkloadIdentity | None = None, # noqa: ARG002 azure_ad_token: str | None = None, azure_ad_token_provider: AsyncAzureADTokenProvider | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, base_url: str | None = None, + websocket_base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, http_client: httpx.AsyncClient | None = None, _strict_response_validation: bool = False, + _enforce_credentials: bool = True, ) -> None: """Construct a new asynchronous azure openai client instance. @@ -394,8 +526,8 @@ def __init__( azure_ad_token_provider: A function that returns an Azure Active Directory token, will be invoked on every request. - azure_deployment: A model deployment, if given sets the base client URL to include `/deployments/{azure_deployment}`. - Note: this means you won't be able to use non-deployment endpoints. Not supported with Assistants APIs. + azure_deployment: A model deployment, if given with `azure_endpoint`, sets the base client URL to include `/deployments/{azure_deployment}`. + Not supported with Assistants APIs. """ if api_key is None: api_key = os.environ.get("AZURE_OPENAI_API_KEY") @@ -403,7 +535,7 @@ def __init__( if azure_ad_token is None: azure_ad_token = os.environ.get("AZURE_OPENAI_AD_TOKEN") - if api_key is None and azure_ad_token is None and azure_ad_token_provider is None: + if _enforce_credentials and api_key is None and azure_ad_token is None and azure_ad_token_provider is None: raise OpenAIError( "Missing credentials. Please pass one of `api_key`, `azure_ad_token`, `azure_ad_token_provider`, or the `AZURE_OPENAI_API_KEY` or `AZURE_OPENAI_AD_TOKEN` environment variables." ) @@ -431,9 +563,9 @@ def __init__( ) if azure_deployment is not None: - base_url = f"{azure_endpoint}/openai/deployments/{azure_deployment}" + base_url = f"{azure_endpoint.rstrip('/')}/openai/deployments/{azure_deployment}" else: - base_url = f"{azure_endpoint}/openai" + base_url = f"{azure_endpoint.rstrip('/')}/openai" else: if azure_endpoint is not None: raise ValueError("base_url and azure_endpoint are mutually exclusive") @@ -444,27 +576,37 @@ def __init__( super().__init__( api_key=api_key, + admin_api_key=admin_api_key, organization=organization, project=project, + webhook_secret=webhook_secret, base_url=base_url, timeout=timeout, max_retries=max_retries, default_headers=default_headers, default_query=default_query, http_client=http_client, + websocket_base_url=websocket_base_url, _strict_response_validation=_strict_response_validation, + _enforce_credentials=_enforce_credentials, ) self._api_version = api_version self._azure_ad_token = azure_ad_token self._azure_ad_token_provider = azure_ad_token_provider + self._azure_deployment = azure_deployment if azure_endpoint else None + self._azure_endpoint = httpx.URL(azure_endpoint) if azure_endpoint else None @override def copy( self, *, - api_key: str | None = None, + api_key: str | Callable[[], Awaitable[str]] | None = None, + admin_api_key: str | None = None, + workload_identity: WorkloadIdentity | None = None, organization: str | None = None, project: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, api_version: str | None = None, azure_ad_token: str | None = None, azure_ad_token_provider: AsyncAzureADTokenProvider | None = None, @@ -476,6 +618,7 @@ def copy( set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, set_default_query: Mapping[str, object] | None = None, + _enforce_credentials: bool | None = None, _extra_kwargs: Mapping[str, Any] = {}, ) -> Self: """ @@ -483,8 +626,12 @@ def copy( """ return super().copy( api_key=api_key, + admin_api_key=admin_api_key, + workload_identity=workload_identity, organization=organization, project=project, + webhook_secret=webhook_secret, + websocket_base_url=websocket_base_url, base_url=base_url, timeout=timeout, http_client=http_client, @@ -493,6 +640,7 @@ def copy( set_default_headers=set_default_headers, default_query=default_query, set_default_query=set_default_query, + _enforce_credentials=_enforce_credentials, _extra_kwargs={ "api_version": api_version or self._api_version, "azure_ad_token": azure_ad_token or self._azure_ad_token, @@ -520,6 +668,25 @@ async def _get_azure_ad_token(self) -> str | None: return None + @override + def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: # noqa: ARG002 + if self._azure_ad_token is not None: + return {"Authorization": f"Bearer {self._azure_ad_token}"} + + if self.api_key and self.api_key != API_KEY_SENTINEL: + return {"api-key": self.api_key} + + return {} + + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if _has_auth_header(headers) or _has_auth_header(custom_headers): + return + + raise TypeError( + '"Could not resolve authentication method. Expected either api_key, azure_ad_token or azure_ad_token_provider to be set. Or for one of the `Authorization` or `api-key` headers to be explicitly supplied or omitted"' + ) + @override async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: headers: dict[str, str | Omit] = {**options.headers} if is_given(options.headers) else {} @@ -529,13 +696,40 @@ async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOp azure_ad_token = await self._get_azure_ad_token() if azure_ad_token is not None: - if headers.get("Authorization") is None: + if not _has_header(headers, "Authorization"): headers["Authorization"] = f"Bearer {azure_ad_token}" - elif self.api_key is not API_KEY_SENTINEL: - if headers.get("api-key") is None: + elif self.api_key and self.api_key != API_KEY_SENTINEL: + if not _has_header(headers, "api-key"): headers["api-key"] = self.api_key + elif _has_auth_header(headers) or _has_auth_header(self.default_headers): + pass else: # should never be hit raise ValueError("Unable to handle auth") return options + + async def _configure_realtime(self, model: str, extra_query: Query) -> tuple[httpx.URL, dict[str, str]]: + auth_headers = {} + query = { + **extra_query, + "api-version": self._api_version, + "deployment": self._azure_deployment or model, + } + if self.api_key and self.api_key != "": + auth_headers = {"api-key": self.api_key} + else: + token = await self._get_azure_ad_token() + if token: + auth_headers = {"Authorization": f"Bearer {token}"} + + if self.websocket_base_url is not None: + base_url = httpx.URL(self.websocket_base_url) + merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime" + realtime_url = base_url.copy_with(raw_path=merge_raw_path) + else: + base_url = self._prepare_url("/realtime") + realtime_url = base_url.copy_with(scheme="wss") + + url = realtime_url.copy_with(params={**query}) + return url, auth_headers diff --git a/src/openai/lib/bedrock.py b/src/openai/lib/bedrock.py new file mode 100644 index 0000000000..266a2e9358 --- /dev/null +++ b/src/openai/lib/bedrock.py @@ -0,0 +1,446 @@ +from __future__ import annotations + +import os +import re +import inspect +from typing import Any, Mapping, Callable, Awaitable, cast +from typing_extensions import Self, override + +import httpx + +from ..auth import WorkloadIdentity +from .._types import NOT_GIVEN, Timeout, NotGiven +from .._utils import is_given +from .._client import OpenAI, AsyncOpenAI +from .._models import SecurityOptions, FinalRequestOptions +from .._exceptions import OpenAIError +from .._base_client import DEFAULT_MAX_RETRIES + +BedrockTokenProvider = Callable[[], str] +AsyncBedrockTokenProvider = Callable[[], "str | Awaitable[str]"] + + +def _normalize_bedrock_base_url(base_url: str | httpx.URL) -> httpx.URL: + """Normalize a Bedrock Responses URL variant back to the provider API root.""" + url = httpx.URL(base_url) + path = url.path.rstrip("/") + responses_match = re.search(r"/responses(?:/.*)?$", path) + if responses_match is not None: + path = path[: responses_match.start()] + + return url.copy_with(path=path or "/") + + +def _resolve_bedrock_base_url(base_url: str | httpx.URL | None, aws_region: str | None) -> httpx.URL: + """Resolve Bedrock base URL precedence from explicit, env, then region config.""" + if isinstance(base_url, str) and not base_url.strip(): + base_url = None + + if base_url is None: + env_base_url = os.environ.get("AWS_BEDROCK_BASE_URL") + if env_base_url is not None and env_base_url.strip(): + base_url = env_base_url + + if base_url is None: + region = aws_region or os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION") + if region is None or not region.strip(): + raise OpenAIError( + "Must provide one of the `base_url` or `aws_region` arguments, or set the " + "`AWS_BEDROCK_BASE_URL`, `AWS_REGION`, or `AWS_DEFAULT_REGION` environment variable." + ) + + base_url = f"https://bedrock-mantle.{region}.api.aws/openai/v1" + + return _normalize_bedrock_base_url(base_url) + + +def _uses_region_derived_bedrock_base_url(base_url: str | httpx.URL | None) -> bool: + if isinstance(base_url, str) and not base_url.strip(): + base_url = None + + if base_url is not None: + return False + + env_base_url = os.environ.get("AWS_BEDROCK_BASE_URL") + return env_base_url is None or not env_base_url.strip() + + +def _bedrock_token_provider(provider: BedrockTokenProvider) -> BedrockTokenProvider: + """Adapt a sync Bedrock token provider to the base client's api_key callback.""" + + def get_token() -> str: + token = cast(object, provider()) + if not isinstance(token, str) or not token: + raise ValueError(f"Expected `bedrock_token_provider` argument to return a string but it returned {token}") + + return token + + return get_token + + +def _async_bedrock_token_provider(provider: AsyncBedrockTokenProvider) -> Callable[[], Awaitable[str]]: + """Adapt a sync or async Bedrock token provider to the async api_key callback.""" + + async def get_token() -> str: + token = cast(object, provider()) + if inspect.isawaitable(token): + token = await token + + if not isinstance(token, str) or not token: + raise ValueError(f"Expected `bedrock_token_provider` argument to return a string but it returned {token}") + + return token + + return get_token + + +class BedrockOpenAI(OpenAI): + """API client for Amazon Bedrock's OpenAI-compatible endpoint.""" + + _bedrock_token_provider: BedrockTokenProvider | None + _uses_region_derived_base_url: bool + aws_region: str | None + + def __init__( + self, + *, + api_key: str | None = None, + bedrock_token_provider: BedrockTokenProvider | None = None, + aws_region: str | None = None, + organization: str | None = None, + project: str | None = None, + webhook_secret: str | None = None, + base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + max_retries: int = DEFAULT_MAX_RETRIES, + default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + http_client: httpx.Client | None = None, + _strict_response_validation: bool = False, + _enforce_credentials: bool = True, + ) -> None: + """Construct a new synchronous Amazon Bedrock client instance. + + This automatically infers the following arguments from their corresponding environment variables if they are not provided: + - `api_key` from `AWS_BEARER_TOKEN_BEDROCK` + - `aws_region` from `AWS_REGION` or `AWS_DEFAULT_REGION` when `base_url` and `AWS_BEDROCK_BASE_URL` are not set + - `base_url` from `AWS_BEDROCK_BASE_URL` + + `bedrock_token_provider` is invoked before each request when provided. + """ + if api_key is None and bedrock_token_provider is None: + api_key = os.environ.get("AWS_BEARER_TOKEN_BEDROCK") + + if callable(cast(object, api_key)): + raise OpenAIError("Pass refreshable Bedrock credentials via `bedrock_token_provider`, not `api_key`.") + + if api_key is not None and bedrock_token_provider is not None: + raise OpenAIError("The `api_key` and `bedrock_token_provider` arguments are mutually exclusive.") + + if _enforce_credentials and not api_key and bedrock_token_provider is None: + raise OpenAIError( + "Missing credentials. Please pass an `api_key` or `bedrock_token_provider`, or set the " + "`AWS_BEARER_TOKEN_BEDROCK` environment variable." + ) + + self._bedrock_token_provider = bedrock_token_provider + self._uses_region_derived_base_url = _uses_region_derived_bedrock_base_url(base_url) + self.aws_region = aws_region + + super().__init__( + api_key=_bedrock_token_provider(bedrock_token_provider) + if bedrock_token_provider is not None + else api_key or "", + admin_api_key="", + organization=organization, + project=project, + webhook_secret=webhook_secret, + base_url=_resolve_bedrock_base_url(base_url, aws_region), + websocket_base_url=websocket_base_url, + timeout=timeout, + max_retries=max_retries, + default_headers=default_headers, + default_query=default_query, + http_client=http_client, + _strict_response_validation=_strict_response_validation, + _enforce_credentials=False, + ) + + @override + def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: + if security.get("bearer_auth", False) or security.get("admin_api_key_auth", False): + return self._bearer_auth + + return {} + + @override + def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: + if ( + self._api_key_provider is not None + and options.security.get("admin_api_key_auth", False) + and not options.security.get("bearer_auth", False) + ): + self._refresh_api_key() + + return super()._prepare_options(options) + + @override + def copy( + self, + *, + api_key: str | BedrockTokenProvider | None = None, + admin_api_key: str | None = None, + workload_identity: WorkloadIdentity | None = None, + bedrock_token_provider: BedrockTokenProvider | None = None, + aws_region: str | None = None, + organization: str | None = None, + project: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, + base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + http_client: httpx.Client | None = None, + max_retries: int | NotGiven = NOT_GIVEN, + default_headers: Mapping[str, str] | None = None, + set_default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + set_default_query: Mapping[str, object] | None = None, + _enforce_credentials: bool | None = None, + _extra_kwargs: Mapping[str, Any] = {}, + ) -> Self: + if default_headers is not None and set_default_headers is not None: + raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive") + + if default_query is not None and set_default_query is not None: + raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive") + + if callable(api_key): + raise OpenAIError("Pass refreshable Bedrock credentials via `bedrock_token_provider`, not `api_key`.") + + if admin_api_key is not None or workload_identity is not None: + raise OpenAIError("BedrockOpenAI only supports Bedrock bearer token authentication.") + + if api_key is not None and bedrock_token_provider is not None: + raise OpenAIError("The `api_key` and `bedrock_token_provider` arguments are mutually exclusive.") + + headers = self._custom_headers + if default_headers is not None: + headers = {**headers, **default_headers} + elif set_default_headers is not None: + headers = set_default_headers + + params = self._custom_query + if default_query is not None: + params = {**params, **default_query} + elif set_default_query is not None: + params = set_default_query + + if api_key is not None: + next_token_provider = None + elif bedrock_token_provider is not None: + next_token_provider = bedrock_token_provider + else: + next_token_provider = self._bedrock_token_provider + + next_api_key = api_key if api_key is not None else (None if next_token_provider is not None else self.api_key) + next_base_url = base_url + if next_base_url is None and not (aws_region is not None and self._uses_region_derived_base_url): + next_base_url = self.base_url + + return self.__class__( + api_key=next_api_key, + bedrock_token_provider=next_token_provider, + aws_region=aws_region if aws_region is not None else self.aws_region, + organization=organization if organization is not None else self.organization, + project=project if project is not None else self.project, + webhook_secret=webhook_secret if webhook_secret is not None else self.webhook_secret, + websocket_base_url=websocket_base_url if websocket_base_url is not None else self.websocket_base_url, + base_url=next_base_url, + timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, + http_client=http_client or self._client, + max_retries=max_retries if is_given(max_retries) else self.max_retries, + default_headers=headers, + default_query=params, + _enforce_credentials=True if _enforce_credentials is None else _enforce_credentials, + **_extra_kwargs, + ) + + with_options = copy + + +class AsyncBedrockOpenAI(AsyncOpenAI): + """Async API client for Amazon Bedrock's OpenAI-compatible endpoint.""" + + _bedrock_token_provider: AsyncBedrockTokenProvider | None + _uses_region_derived_base_url: bool + aws_region: str | None + + def __init__( + self, + *, + api_key: str | None = None, + bedrock_token_provider: AsyncBedrockTokenProvider | None = None, + aws_region: str | None = None, + organization: str | None = None, + project: str | None = None, + webhook_secret: str | None = None, + base_url: str | httpx.URL | None = None, + websocket_base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + max_retries: int = DEFAULT_MAX_RETRIES, + default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + http_client: httpx.AsyncClient | None = None, + _strict_response_validation: bool = False, + _enforce_credentials: bool = True, + ) -> None: + """Construct a new asynchronous Amazon Bedrock client instance. + + This automatically infers the following arguments from their corresponding environment variables if they are not provided: + - `api_key` from `AWS_BEARER_TOKEN_BEDROCK` + - `aws_region` from `AWS_REGION` or `AWS_DEFAULT_REGION` when `base_url` and `AWS_BEDROCK_BASE_URL` are not set + - `base_url` from `AWS_BEDROCK_BASE_URL` + + `bedrock_token_provider` is invoked before each request when provided. + """ + if api_key is None and bedrock_token_provider is None: + api_key = os.environ.get("AWS_BEARER_TOKEN_BEDROCK") + + if callable(cast(object, api_key)): + raise OpenAIError("Pass refreshable Bedrock credentials via `bedrock_token_provider`, not `api_key`.") + + if api_key is not None and bedrock_token_provider is not None: + raise OpenAIError("The `api_key` and `bedrock_token_provider` arguments are mutually exclusive.") + + if _enforce_credentials and not api_key and bedrock_token_provider is None: + raise OpenAIError( + "Missing credentials. Please pass an `api_key` or `bedrock_token_provider`, or set the " + "`AWS_BEARER_TOKEN_BEDROCK` environment variable." + ) + + self._bedrock_token_provider = bedrock_token_provider + self._uses_region_derived_base_url = _uses_region_derived_bedrock_base_url(base_url) + self.aws_region = aws_region + + super().__init__( + api_key=( + _async_bedrock_token_provider(bedrock_token_provider) + if bedrock_token_provider is not None + else api_key or "" + ), + admin_api_key="", + organization=organization, + project=project, + webhook_secret=webhook_secret, + base_url=_resolve_bedrock_base_url(base_url, aws_region), + websocket_base_url=websocket_base_url, + timeout=timeout, + max_retries=max_retries, + default_headers=default_headers, + default_query=default_query, + http_client=http_client, + _strict_response_validation=_strict_response_validation, + _enforce_credentials=False, + ) + + @override + def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: + if security.get("bearer_auth", False) or security.get("admin_api_key_auth", False): + return self._bearer_auth + + return {} + + @override + async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: + if ( + self._api_key_provider is not None + and options.security.get("admin_api_key_auth", False) + and not options.security.get("bearer_auth", False) + ): + await self._refresh_api_key() + + return await super()._prepare_options(options) + + @override + def copy( + self, + *, + api_key: str | AsyncBedrockTokenProvider | None = None, + admin_api_key: str | None = None, + workload_identity: WorkloadIdentity | None = None, + bedrock_token_provider: AsyncBedrockTokenProvider | None = None, + aws_region: str | None = None, + organization: str | None = None, + project: str | None = None, + webhook_secret: str | None = None, + websocket_base_url: str | httpx.URL | None = None, + base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + http_client: httpx.AsyncClient | None = None, + max_retries: int | NotGiven = NOT_GIVEN, + default_headers: Mapping[str, str] | None = None, + set_default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + set_default_query: Mapping[str, object] | None = None, + _enforce_credentials: bool | None = None, + _extra_kwargs: Mapping[str, Any] = {}, + ) -> Self: + if default_headers is not None and set_default_headers is not None: + raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive") + + if default_query is not None and set_default_query is not None: + raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive") + + if callable(api_key): + raise OpenAIError("Pass refreshable Bedrock credentials via `bedrock_token_provider`, not `api_key`.") + + if admin_api_key is not None or workload_identity is not None: + raise OpenAIError("AsyncBedrockOpenAI only supports Bedrock bearer token authentication.") + + if api_key is not None and bedrock_token_provider is not None: + raise OpenAIError("The `api_key` and `bedrock_token_provider` arguments are mutually exclusive.") + + headers = self._custom_headers + if default_headers is not None: + headers = {**headers, **default_headers} + elif set_default_headers is not None: + headers = set_default_headers + + params = self._custom_query + if default_query is not None: + params = {**params, **default_query} + elif set_default_query is not None: + params = set_default_query + + if api_key is not None: + next_token_provider = None + elif bedrock_token_provider is not None: + next_token_provider = bedrock_token_provider + else: + next_token_provider = self._bedrock_token_provider + + next_api_key = api_key if api_key is not None else (None if next_token_provider is not None else self.api_key) + next_base_url = base_url + if next_base_url is None and not (aws_region is not None and self._uses_region_derived_base_url): + next_base_url = self.base_url + + return self.__class__( + api_key=next_api_key, + bedrock_token_provider=next_token_provider, + aws_region=aws_region if aws_region is not None else self.aws_region, + organization=organization if organization is not None else self.organization, + project=project if project is not None else self.project, + webhook_secret=webhook_secret if webhook_secret is not None else self.webhook_secret, + websocket_base_url=websocket_base_url if websocket_base_url is not None else self.websocket_base_url, + base_url=next_base_url, + timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, + http_client=http_client or self._client, + max_retries=max_retries if is_given(max_retries) else self.max_retries, + default_headers=headers, + default_query=params, + _enforce_credentials=True if _enforce_credentials is None else _enforce_credentials, + **_extra_kwargs, + ) + + with_options = copy diff --git a/src/openai/lib/streaming/_assistants.py b/src/openai/lib/streaming/_assistants.py index 7445f9a96d..6efb3ca3f1 100644 --- a/src/openai/lib/streaming/_assistants.py +++ b/src/openai/lib/streaming/_assistants.py @@ -8,6 +8,7 @@ import httpx from ..._utils import is_dict, is_list, consume_sync_iterator, consume_async_iterator +from ..._compat import model_dump from ..._models import construct_type from ..._streaming import Stream, AsyncStream from ...types.beta import AssistantStreamEvent @@ -242,7 +243,7 @@ def on_text_delta(self, delta: TextDelta, snapshot: Text) -> None: on_text_delta(TextDelta(value=" solution"), Text(value="The solution")), on_text_delta(TextDelta(value=" to"), Text(value="The solution to")), on_text_delta(TextDelta(value=" the"), Text(value="The solution to the")), - on_text_delta(TextDelta(value=" equation"), Text(value="The solution to the equivalent")), + on_text_delta(TextDelta(value=" equation"), Text(value="The solution to the equation")), """ def on_text_done(self, text: Text) -> None: @@ -906,11 +907,11 @@ def accumulate_run_step( merged = accumulate_delta( cast( "dict[object, object]", - snapshot.model_dump(exclude_unset=True), + model_dump(snapshot, exclude_unset=True, warnings=False), ), cast( "dict[object, object]", - data.delta.model_dump(exclude_unset=True), + model_dump(data.delta, exclude_unset=True, warnings=False), ), ) run_step_snapshots[snapshot.id] = cast(RunStep, construct_type(type_=RunStep, value=merged)) @@ -948,7 +949,7 @@ def accumulate_event( construct_type( # mypy doesn't allow Content for some reason type_=cast(Any, MessageContent), - value=content_delta.model_dump(exclude_unset=True), + value=model_dump(content_delta, exclude_unset=True, warnings=False), ), ), ) @@ -957,11 +958,11 @@ def accumulate_event( merged = accumulate_delta( cast( "dict[object, object]", - block.model_dump(exclude_unset=True), + model_dump(block, exclude_unset=True, warnings=False), ), cast( "dict[object, object]", - content_delta.model_dump(exclude_unset=True), + model_dump(content_delta, exclude_unset=True, warnings=False), ), ) current_message_snapshot.content[content_delta.index] = cast( diff --git a/src/openai/lib/streaming/chat/__init__.py b/src/openai/lib/streaming/chat/__init__.py index 5881c39b9a..dfa3f3f2e3 100644 --- a/src/openai/lib/streaming/chat/__init__.py +++ b/src/openai/lib/streaming/chat/__init__.py @@ -21,6 +21,7 @@ from ._completions import ( ChatCompletionStream as ChatCompletionStream, AsyncChatCompletionStream as AsyncChatCompletionStream, + ChatCompletionStreamState as ChatCompletionStreamState, ChatCompletionStreamManager as ChatCompletionStreamManager, AsyncChatCompletionStreamManager as AsyncChatCompletionStreamManager, ) diff --git a/src/openai/lib/streaming/chat/_completions.py b/src/openai/lib/streaming/chat/_completions.py index a4b0f856f7..5f072cafbd 100644 --- a/src/openai/lib/streaming/chat/_completions.py +++ b/src/openai/lib/streaming/chat/_completions.py @@ -23,7 +23,7 @@ FunctionToolCallArgumentsDeltaEvent, ) from .._deltas import accumulate_delta -from ...._types import NOT_GIVEN, NotGiven +from ...._types import Omit, IncEx, omit from ...._utils import is_given, consume_sync_iterator, consume_async_iterator from ...._compat import model_dump from ...._models import build, construct_type @@ -33,11 +33,10 @@ maybe_parse_content, parse_chat_completion, get_input_tool_by_name, - solve_response_format_t, parse_function_tool_arguments, ) from ...._streaming import Stream, AsyncStream -from ....types.chat import ChatCompletionChunk, ParsedChatCompletion, ChatCompletionToolParam +from ....types.chat import ChatCompletionChunk, ParsedChatCompletion, ChatCompletionToolUnionParam from ...._exceptions import LengthFinishReasonError, ContentFilterFinishReasonError from ....types.chat.chat_completion import ChoiceLogprobs from ....types.chat.chat_completion_chunk import Choice as ChoiceChunk @@ -57,8 +56,8 @@ def __init__( self, *, raw_stream: Stream[ChatCompletionChunk], - response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven, - input_tools: Iterable[ChatCompletionToolParam] | NotGiven, + response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, + input_tools: Iterable[ChatCompletionToolUnionParam] | Omit, ) -> None: self._raw_stream = raw_stream self._response = raw_stream.response @@ -113,6 +112,8 @@ def current_completion_snapshot(self) -> ParsedChatCompletionSnapshot: def __stream__(self) -> Iterator[ChatCompletionStreamEvent[ResponseFormatT]]: for sse_event in self._raw_stream: + if not _is_valid_chat_completion_chunk_weak(sse_event): + continue events_to_fire = self._state.handle_chunk(sse_event) for event in events_to_fire: yield event @@ -126,7 +127,7 @@ class ChatCompletionStreamManager(Generic[ResponseFormatT]): Usage: ```py - with client.beta.chat.completions.stream(...) as stream: + with client.chat.completions.stream(...) as stream: for event in stream: ... ``` @@ -136,8 +137,8 @@ def __init__( self, api_request: Callable[[], Stream[ChatCompletionChunk]], *, - response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven, - input_tools: Iterable[ChatCompletionToolParam] | NotGiven, + response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, + input_tools: Iterable[ChatCompletionToolUnionParam] | Omit, ) -> None: self.__stream: ChatCompletionStream[ResponseFormatT] | None = None self.__api_request = api_request @@ -178,8 +179,8 @@ def __init__( self, *, raw_stream: AsyncStream[ChatCompletionChunk], - response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven, - input_tools: Iterable[ChatCompletionToolParam] | NotGiven, + response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, + input_tools: Iterable[ChatCompletionToolUnionParam] | Omit, ) -> None: self._raw_stream = raw_stream self._response = raw_stream.response @@ -234,6 +235,8 @@ def current_completion_snapshot(self) -> ParsedChatCompletionSnapshot: async def __stream__(self) -> AsyncIterator[ChatCompletionStreamEvent[ResponseFormatT]]: async for sse_event in self._raw_stream: + if not _is_valid_chat_completion_chunk_weak(sse_event): + continue events_to_fire = self._state.handle_chunk(sse_event) for event in events_to_fire: yield event @@ -247,7 +250,7 @@ class AsyncChatCompletionStreamManager(Generic[ResponseFormatT]): Usage: ```py - async with client.beta.chat.completions.stream(...) as stream: + async with client.chat.completions.stream(...) as stream: for event in stream: ... ``` @@ -257,8 +260,8 @@ def __init__( self, api_request: Awaitable[AsyncStream[ChatCompletionChunk]], *, - response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven, - input_tools: Iterable[ChatCompletionToolParam] | NotGiven, + response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, + input_tools: Iterable[ChatCompletionToolUnionParam] | Omit, ) -> None: self.__stream: AsyncChatCompletionStream[ResponseFormatT] | None = None self.__api_request = api_request @@ -287,20 +290,45 @@ async def __aexit__( class ChatCompletionStreamState(Generic[ResponseFormatT]): + """Helper class for manually accumulating `ChatCompletionChunk`s into a final `ChatCompletion` object. + + This is useful in cases where you can't always use the `.stream()` method, e.g. + + ```py + from openai.lib.streaming.chat import ChatCompletionStreamState + + state = ChatCompletionStreamState() + + stream = client.chat.completions.create(..., stream=True) + for chunk in response: + state.handle_chunk(chunk) + + # can also access the accumulated `ChatCompletion` mid-stream + state.current_completion_snapshot + + print(state.get_final_completion()) + ``` + """ + def __init__( self, *, - input_tools: Iterable[ChatCompletionToolParam] | NotGiven, - response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven, + input_tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + response_format: type[ResponseFormatT] | ResponseFormatParam | Omit = omit, ) -> None: self.__current_completion_snapshot: ParsedChatCompletionSnapshot | None = None self.__choice_event_states: list[ChoiceEventState] = [] self._input_tools = [tool for tool in input_tools] if is_given(input_tools) else [] self._response_format = response_format - self._rich_response_format: type | NotGiven = response_format if inspect.isclass(response_format) else NOT_GIVEN + self._rich_response_format: type | Omit = response_format if inspect.isclass(response_format) else omit def get_final_completion(self) -> ParsedChatCompletion[ResponseFormatT]: + """Parse the final completion object. + + Note this does not provide any guarantees that the stream has actually finished, you must + only call this method when the stream is finished. + """ return parse_chat_completion( chat_completion=self.current_completion_snapshot, response_format=self._rich_response_format, @@ -312,8 +340,8 @@ def current_completion_snapshot(self) -> ParsedChatCompletionSnapshot: assert self.__current_completion_snapshot is not None return self.__current_completion_snapshot - def handle_chunk(self, chunk: ChatCompletionChunk) -> list[ChatCompletionStreamEvent[ResponseFormatT]]: - """Accumulate a new chunk into the snapshot and returns a list of events to yield.""" + def handle_chunk(self, chunk: ChatCompletionChunk) -> Iterable[ChatCompletionStreamEvent[ResponseFormatT]]: + """Accumulate a new chunk into the snapshot and returns an iterable of events to yield.""" self.__current_completion_snapshot = self._accumulate_chunk(chunk) return self._build_events( @@ -352,13 +380,17 @@ def _accumulate_chunk(self, chunk: ChatCompletionChunk) -> ParsedChatCompletionS # we don't want to serialise / deserialise our custom properties # as they won't appear in the delta and we don't want to have to # continuosly reparse the content - exclude={ - "parsed": True, - "tool_calls": { - idx: {"function": {"parsed_arguments": True}} - for idx, _ in enumerate(choice_snapshot.message.tool_calls or []) + exclude=cast( + # cast required as mypy isn't smart enough to infer `True` here to `Literal[True]` + IncEx, + { + "parsed": True, + "tool_calls": { + idx: {"function": {"parsed_arguments": True}} + for idx, _ in enumerate(choice_snapshot.message.tool_calls or []) + }, }, - }, + ), ), ), cast("dict[object, object]", choice.delta.to_dict()), @@ -405,6 +437,8 @@ def _accumulate_chunk(self, chunk: ChatCompletionChunk) -> ParsedChatCompletionS choice_snapshot.message.content and not choice_snapshot.message.refusal and is_given(self._rich_response_format) + # partial parsing fails on white-space + and choice_snapshot.message.content.lstrip() ): choice_snapshot.message.parsed = from_json( bytes(choice_snapshot.message.content, "utf-8"), @@ -549,7 +583,7 @@ def _build_events( class ChoiceEventState: - def __init__(self, *, input_tools: list[ChatCompletionToolParam]) -> None: + def __init__(self, *, input_tools: list[ChatCompletionToolUnionParam]) -> None: self._input_tools = input_tools self._content_done = False @@ -564,7 +598,7 @@ def get_done_events( *, choice_chunk: ChoiceChunk, choice_snapshot: ParsedChoiceSnapshot, - response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven, + response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, ) -> list[ChatCompletionStreamEvent[ResponseFormatT]]: events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]] = [] @@ -604,7 +638,7 @@ def _content_done_events( self, *, choice_snapshot: ParsedChoiceSnapshot, - response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven, + response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, ) -> list[ChatCompletionStreamEvent[ResponseFormatT]]: events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]] = [] @@ -628,7 +662,7 @@ def _content_done_events( # type variable, e.g. `ContentDoneEvent[MyModelType]` cast( # pyright: ignore[reportUnnecessaryCast] "type[ContentDoneEvent[ResponseFormatT]]", - cast(Any, ContentDoneEvent)[solve_response_format_t(response_format)], + cast(Any, ContentDoneEvent), ), type="content.done", content=choice_snapshot.message.content, @@ -724,3 +758,12 @@ def _convert_initial_chunk_into_snapshot(chunk: ChatCompletionChunk) -> ParsedCh }, ), ) + + +def _is_valid_chat_completion_chunk_weak(sse_event: ChatCompletionChunk) -> bool: + # Although the _raw_stream is always supposed to contain only objects adhering to ChatCompletionChunk schema, + # this is broken by the Azure OpenAI in case of Asynchronous Filter enabled. + # An easy filter is to check for the "object" property: + # - should be "chat.completion.chunk" for a ChatCompletionChunk; + # - is an empty string for Asynchronous Filter events. + return sse_event.object == "chat.completion.chunk" # type: ignore # pylance reports this as a useless check diff --git a/src/openai/lib/streaming/responses/__init__.py b/src/openai/lib/streaming/responses/__init__.py new file mode 100644 index 0000000000..ff073633bf --- /dev/null +++ b/src/openai/lib/streaming/responses/__init__.py @@ -0,0 +1,13 @@ +from ._events import ( + ResponseTextDoneEvent as ResponseTextDoneEvent, + ResponseTextDeltaEvent as ResponseTextDeltaEvent, + ResponseFunctionCallArgumentsDeltaEvent as ResponseFunctionCallArgumentsDeltaEvent, +) +from ._responses import ( + ResponseStream as ResponseStream, + AsyncResponseStream as AsyncResponseStream, + ResponseStreamEvent as ResponseStreamEvent, + ResponseStreamState as ResponseStreamState, + ResponseStreamManager as ResponseStreamManager, + AsyncResponseStreamManager as AsyncResponseStreamManager, +) diff --git a/src/openai/lib/streaming/responses/_events.py b/src/openai/lib/streaming/responses/_events.py new file mode 100644 index 0000000000..bdc47b834a --- /dev/null +++ b/src/openai/lib/streaming/responses/_events.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +from typing import Optional +from typing_extensions import Union, Generic, TypeVar, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._compat import GenericModel +from ....types.responses import ( + ParsedResponse, + ResponseErrorEvent, + ResponseFailedEvent, + ResponseQueuedEvent, + ResponseCreatedEvent, + ResponseTextDoneEvent as RawResponseTextDoneEvent, + ResponseAudioDoneEvent, + ResponseCompletedEvent as RawResponseCompletedEvent, + ResponseTextDeltaEvent as RawResponseTextDeltaEvent, + ResponseAudioDeltaEvent, + ResponseIncompleteEvent, + ResponseInProgressEvent, + ResponseRefusalDoneEvent, + ResponseRefusalDeltaEvent, + ResponseMcpCallFailedEvent, + ResponseOutputItemDoneEvent, + ResponseContentPartDoneEvent, + ResponseOutputItemAddedEvent, + ResponseContentPartAddedEvent, + ResponseMcpCallCompletedEvent, + ResponseMcpCallInProgressEvent, + ResponseMcpListToolsFailedEvent, + ResponseAudioTranscriptDoneEvent, + ResponseAudioTranscriptDeltaEvent, + ResponseMcpCallArgumentsDoneEvent, + ResponseImageGenCallCompletedEvent, + ResponseMcpCallArgumentsDeltaEvent, + ResponseMcpListToolsCompletedEvent, + ResponseImageGenCallGeneratingEvent, + ResponseImageGenCallInProgressEvent, + ResponseMcpListToolsInProgressEvent, + ResponseWebSearchCallCompletedEvent, + ResponseWebSearchCallSearchingEvent, + ResponseCustomToolCallInputDoneEvent, + ResponseFileSearchCallCompletedEvent, + ResponseFileSearchCallSearchingEvent, + ResponseWebSearchCallInProgressEvent, + ResponseCustomToolCallInputDeltaEvent, + ResponseFileSearchCallInProgressEvent, + ResponseImageGenCallPartialImageEvent, + ResponseReasoningSummaryPartDoneEvent, + ResponseReasoningSummaryTextDoneEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseOutputTextAnnotationAddedEvent, + ResponseReasoningSummaryPartAddedEvent, + ResponseReasoningSummaryTextDeltaEvent, + ResponseFunctionCallArgumentsDeltaEvent as RawResponseFunctionCallArgumentsDeltaEvent, + ResponseCodeInterpreterCallCodeDoneEvent, + ResponseCodeInterpreterCallCodeDeltaEvent, + ResponseCodeInterpreterCallCompletedEvent, + ResponseCodeInterpreterCallInProgressEvent, + ResponseCodeInterpreterCallInterpretingEvent, +) +from ....types.responses.response_reasoning_text_done_event import ResponseReasoningTextDoneEvent +from ....types.responses.response_reasoning_text_delta_event import ResponseReasoningTextDeltaEvent + +TextFormatT = TypeVar( + "TextFormatT", + # if it isn't given then we don't do any parsing + default=None, +) + + +class ResponseTextDeltaEvent(RawResponseTextDeltaEvent): + snapshot: str + + +class ResponseTextDoneEvent(RawResponseTextDoneEvent, GenericModel, Generic[TextFormatT]): + parsed: Optional[TextFormatT] = None + + +class ResponseFunctionCallArgumentsDeltaEvent(RawResponseFunctionCallArgumentsDeltaEvent): + snapshot: str + + +class ResponseCompletedEvent(RawResponseCompletedEvent, GenericModel, Generic[TextFormatT]): + response: ParsedResponse[TextFormatT] # type: ignore[assignment] + + +ResponseStreamEvent: TypeAlias = Annotated[ + Union[ + # wrappers with snapshots added on + ResponseTextDeltaEvent, + ResponseTextDoneEvent[TextFormatT], + ResponseFunctionCallArgumentsDeltaEvent, + ResponseCompletedEvent[TextFormatT], + # the same as the non-accumulated API + ResponseAudioDeltaEvent, + ResponseAudioDoneEvent, + ResponseAudioTranscriptDeltaEvent, + ResponseAudioTranscriptDoneEvent, + ResponseCodeInterpreterCallCodeDeltaEvent, + ResponseCodeInterpreterCallCodeDoneEvent, + ResponseCodeInterpreterCallCompletedEvent, + ResponseCodeInterpreterCallInProgressEvent, + ResponseCodeInterpreterCallInterpretingEvent, + ResponseContentPartAddedEvent, + ResponseContentPartDoneEvent, + ResponseCreatedEvent, + ResponseErrorEvent, + ResponseFileSearchCallCompletedEvent, + ResponseFileSearchCallInProgressEvent, + ResponseFileSearchCallSearchingEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseInProgressEvent, + ResponseFailedEvent, + ResponseIncompleteEvent, + ResponseOutputItemAddedEvent, + ResponseOutputItemDoneEvent, + ResponseRefusalDeltaEvent, + ResponseRefusalDoneEvent, + ResponseTextDoneEvent, + ResponseWebSearchCallCompletedEvent, + ResponseWebSearchCallInProgressEvent, + ResponseWebSearchCallSearchingEvent, + ResponseReasoningSummaryPartAddedEvent, + ResponseReasoningSummaryPartDoneEvent, + ResponseReasoningSummaryTextDeltaEvent, + ResponseReasoningSummaryTextDoneEvent, + ResponseImageGenCallCompletedEvent, + ResponseImageGenCallInProgressEvent, + ResponseImageGenCallGeneratingEvent, + ResponseImageGenCallPartialImageEvent, + ResponseMcpCallCompletedEvent, + ResponseMcpCallArgumentsDeltaEvent, + ResponseMcpCallArgumentsDoneEvent, + ResponseMcpCallFailedEvent, + ResponseMcpCallInProgressEvent, + ResponseMcpListToolsCompletedEvent, + ResponseMcpListToolsFailedEvent, + ResponseMcpListToolsInProgressEvent, + ResponseOutputTextAnnotationAddedEvent, + ResponseQueuedEvent, + ResponseReasoningTextDeltaEvent, + ResponseReasoningTextDoneEvent, + ResponseCustomToolCallInputDeltaEvent, + ResponseCustomToolCallInputDoneEvent, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/lib/streaming/responses/_responses.py b/src/openai/lib/streaming/responses/_responses.py new file mode 100644 index 0000000000..6975a9260d --- /dev/null +++ b/src/openai/lib/streaming/responses/_responses.py @@ -0,0 +1,372 @@ +from __future__ import annotations + +import inspect +from types import TracebackType +from typing import Any, List, Generic, Iterable, Awaitable, cast +from typing_extensions import Self, Callable, Iterator, AsyncIterator + +from ._types import ParsedResponseSnapshot +from ._events import ( + ResponseStreamEvent, + ResponseTextDoneEvent, + ResponseCompletedEvent, + ResponseTextDeltaEvent, + ResponseFunctionCallArgumentsDeltaEvent, +) +from ...._types import Omit, omit +from ...._utils import is_given, consume_sync_iterator, consume_async_iterator +from ...._models import build, construct_type_unchecked +from ...._streaming import Stream, AsyncStream +from ....types.responses import ParsedResponse, ResponseStreamEvent as RawResponseStreamEvent +from ..._parsing._responses import TextFormatT, parse_text, parse_response +from ....types.responses.tool_param import ToolParam +from ....types.responses.parsed_response import ( + ParsedContent, + ParsedResponseOutputMessage, + ParsedResponseFunctionToolCall, +) + + +class ResponseStream(Generic[TextFormatT]): + def __init__( + self, + *, + raw_stream: Stream[RawResponseStreamEvent], + text_format: type[TextFormatT] | Omit, + input_tools: Iterable[ToolParam] | Omit, + starting_after: int | None, + ) -> None: + self._raw_stream = raw_stream + self._response = raw_stream.response + self._iterator = self.__stream__() + self._state = ResponseStreamState(text_format=text_format, input_tools=input_tools) + self._starting_after = starting_after + + def __next__(self) -> ResponseStreamEvent[TextFormatT]: + return self._iterator.__next__() + + def __iter__(self) -> Iterator[ResponseStreamEvent[TextFormatT]]: + for item in self._iterator: + yield item + + def __enter__(self) -> Self: + return self + + def __stream__(self) -> Iterator[ResponseStreamEvent[TextFormatT]]: + for sse_event in self._raw_stream: + events_to_fire = self._state.handle_event(sse_event) + for event in events_to_fire: + if self._starting_after is None or event.sequence_number > self._starting_after: + yield event + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def close(self) -> None: + """ + Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + self._response.close() + + def get_final_response(self) -> ParsedResponse[TextFormatT]: + """Waits until the stream has been read to completion and returns + the accumulated `ParsedResponse` object. + """ + self.until_done() + response = self._state._completed_response + if not response: + raise RuntimeError("Didn't receive a `response.completed` event.") + + return response + + def until_done(self) -> Self: + """Blocks until the stream has been consumed.""" + consume_sync_iterator(self) + return self + + +class ResponseStreamManager(Generic[TextFormatT]): + def __init__( + self, + api_request: Callable[[], Stream[RawResponseStreamEvent]], + *, + text_format: type[TextFormatT] | Omit, + input_tools: Iterable[ToolParam] | Omit, + starting_after: int | None, + ) -> None: + self.__stream: ResponseStream[TextFormatT] | None = None + self.__api_request = api_request + self.__text_format = text_format + self.__input_tools = input_tools + self.__starting_after = starting_after + + def __enter__(self) -> ResponseStream[TextFormatT]: + raw_stream = self.__api_request() + + self.__stream = ResponseStream( + raw_stream=raw_stream, + text_format=self.__text_format, + input_tools=self.__input_tools, + starting_after=self.__starting_after, + ) + + return self.__stream + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__stream is not None: + self.__stream.close() + + +class AsyncResponseStream(Generic[TextFormatT]): + def __init__( + self, + *, + raw_stream: AsyncStream[RawResponseStreamEvent], + text_format: type[TextFormatT] | Omit, + input_tools: Iterable[ToolParam] | Omit, + starting_after: int | None, + ) -> None: + self._raw_stream = raw_stream + self._response = raw_stream.response + self._iterator = self.__stream__() + self._state = ResponseStreamState(text_format=text_format, input_tools=input_tools) + self._starting_after = starting_after + + async def __anext__(self) -> ResponseStreamEvent[TextFormatT]: + return await self._iterator.__anext__() + + async def __aiter__(self) -> AsyncIterator[ResponseStreamEvent[TextFormatT]]: + async for item in self._iterator: + yield item + + async def __stream__(self) -> AsyncIterator[ResponseStreamEvent[TextFormatT]]: + async for sse_event in self._raw_stream: + events_to_fire = self._state.handle_event(sse_event) + for event in events_to_fire: + if self._starting_after is None or event.sequence_number > self._starting_after: + yield event + + async def __aenter__(self) -> Self: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.close() + + async def close(self) -> None: + """ + Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + await self._response.aclose() + + async def get_final_response(self) -> ParsedResponse[TextFormatT]: + """Waits until the stream has been read to completion and returns + the accumulated `ParsedResponse` object. + """ + await self.until_done() + response = self._state._completed_response + if not response: + raise RuntimeError("Didn't receive a `response.completed` event.") + + return response + + async def until_done(self) -> Self: + """Blocks until the stream has been consumed.""" + await consume_async_iterator(self) + return self + + +class AsyncResponseStreamManager(Generic[TextFormatT]): + def __init__( + self, + api_request: Awaitable[AsyncStream[RawResponseStreamEvent]], + *, + text_format: type[TextFormatT] | Omit, + input_tools: Iterable[ToolParam] | Omit, + starting_after: int | None, + ) -> None: + self.__stream: AsyncResponseStream[TextFormatT] | None = None + self.__api_request = api_request + self.__text_format = text_format + self.__input_tools = input_tools + self.__starting_after = starting_after + + async def __aenter__(self) -> AsyncResponseStream[TextFormatT]: + raw_stream = await self.__api_request + + self.__stream = AsyncResponseStream( + raw_stream=raw_stream, + text_format=self.__text_format, + input_tools=self.__input_tools, + starting_after=self.__starting_after, + ) + + return self.__stream + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__stream is not None: + await self.__stream.close() + + +class ResponseStreamState(Generic[TextFormatT]): + def __init__( + self, + *, + input_tools: Iterable[ToolParam] | Omit, + text_format: type[TextFormatT] | Omit, + ) -> None: + self.__current_snapshot: ParsedResponseSnapshot | None = None + self._completed_response: ParsedResponse[TextFormatT] | None = None + self._input_tools = [tool for tool in input_tools] if is_given(input_tools) else [] + self._text_format = text_format + self._rich_text_format: type | Omit = text_format if inspect.isclass(text_format) else omit + + def handle_event(self, event: RawResponseStreamEvent) -> List[ResponseStreamEvent[TextFormatT]]: + self.__current_snapshot = snapshot = self.accumulate_event(event) + + events: List[ResponseStreamEvent[TextFormatT]] = [] + + if event.type == "response.output_text.delta": + output = snapshot.output[event.output_index] + assert output.type == "message" + + content = output.content[event.content_index] + assert content.type == "output_text" + + events.append( + build( + ResponseTextDeltaEvent, + content_index=event.content_index, + delta=event.delta, + item_id=event.item_id, + output_index=event.output_index, + sequence_number=event.sequence_number, + logprobs=event.logprobs, + type="response.output_text.delta", + snapshot=content.text, + ) + ) + elif event.type == "response.output_text.done": + output = snapshot.output[event.output_index] + assert output.type == "message" + + content = output.content[event.content_index] + assert content.type == "output_text" + + events.append( + build( + ResponseTextDoneEvent[TextFormatT], + content_index=event.content_index, + item_id=event.item_id, + output_index=event.output_index, + sequence_number=event.sequence_number, + logprobs=event.logprobs, + type="response.output_text.done", + text=event.text, + parsed=parse_text(event.text, text_format=self._text_format), + ) + ) + elif event.type == "response.function_call_arguments.delta": + output = snapshot.output[event.output_index] + assert output.type == "function_call" + + events.append( + build( + ResponseFunctionCallArgumentsDeltaEvent, + delta=event.delta, + item_id=event.item_id, + output_index=event.output_index, + sequence_number=event.sequence_number, + type="response.function_call_arguments.delta", + snapshot=output.arguments, + ) + ) + + elif event.type == "response.completed": + response = self._completed_response + assert response is not None + + events.append( + build( + ResponseCompletedEvent, + sequence_number=event.sequence_number, + type="response.completed", + response=response, + ) + ) + else: + events.append(event) + + return events + + def accumulate_event(self, event: RawResponseStreamEvent) -> ParsedResponseSnapshot: + snapshot = self.__current_snapshot + if snapshot is None: + return self._create_initial_response(event) + + if event.type == "response.output_item.added": + if event.item.type == "function_call": + snapshot.output.append( + construct_type_unchecked( + type_=cast(Any, ParsedResponseFunctionToolCall), value=event.item.to_dict() + ) + ) + elif event.item.type == "message": + snapshot.output.append( + construct_type_unchecked(type_=cast(Any, ParsedResponseOutputMessage), value=event.item.to_dict()) + ) + else: + snapshot.output.append(event.item) + elif event.type == "response.content_part.added": + output = snapshot.output[event.output_index] + if output.type == "message": + output.content.append( + construct_type_unchecked(type_=cast(Any, ParsedContent), value=event.part.to_dict()) + ) + elif event.type == "response.output_text.delta": + output = snapshot.output[event.output_index] + if output.type == "message": + content = output.content[event.content_index] + assert content.type == "output_text" + content.text += event.delta + elif event.type == "response.function_call_arguments.delta": + output = snapshot.output[event.output_index] + if output.type == "function_call": + output.arguments += event.delta + elif event.type == "response.completed": + self._completed_response = parse_response( + text_format=self._text_format, + response=event.response, + input_tools=self._input_tools, + ) + + return snapshot + + def _create_initial_response(self, event: RawResponseStreamEvent) -> ParsedResponseSnapshot: + if event.type != "response.created": + raise RuntimeError(f"Expected to have received `response.created` before `{event.type}`") + + return construct_type_unchecked(type_=ParsedResponseSnapshot, value=event.response.to_dict()) diff --git a/src/openai/lib/streaming/responses/_types.py b/src/openai/lib/streaming/responses/_types.py new file mode 100644 index 0000000000..6d3fd90e40 --- /dev/null +++ b/src/openai/lib/streaming/responses/_types.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from typing_extensions import TypeAlias + +from ....types.responses import ParsedResponse + +ParsedResponseSnapshot: TypeAlias = ParsedResponse[object] +"""Snapshot type representing an in-progress accumulation of +a `ParsedResponse` object. +""" diff --git a/src/openai/pagination.py b/src/openai/pagination.py index 8293638269..6cf0c8e7f5 100644 --- a/src/openai/pagination.py +++ b/src/openai/pagination.py @@ -5,7 +5,16 @@ from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage -__all__ = ["SyncPage", "AsyncPage", "SyncCursorPage", "AsyncCursorPage"] +__all__ = [ + "SyncPage", + "AsyncPage", + "SyncCursorPage", + "AsyncCursorPage", + "SyncConversationCursorPage", + "AsyncConversationCursorPage", + "SyncNextCursorPage", + "AsyncNextCursorPage", +] _T = TypeVar("_T") @@ -61,6 +70,7 @@ def next_page_info(self) -> None: class SyncCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]): data: List[_T] + has_more: Optional[bool] = None @override def _get_page_items(self) -> List[_T]: @@ -69,6 +79,14 @@ def _get_page_items(self) -> List[_T]: return [] return data + @override + def has_next_page(self) -> bool: + has_more = self.has_more + if has_more is not None and has_more is False: + return False + + return super().has_next_page() + @override def next_page_info(self) -> Optional[PageInfo]: data = self.data @@ -85,6 +103,7 @@ def next_page_info(self) -> Optional[PageInfo]: class AsyncCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): data: List[_T] + has_more: Optional[bool] = None @override def _get_page_items(self) -> List[_T]: @@ -93,6 +112,14 @@ def _get_page_items(self) -> List[_T]: return [] return data + @override + def has_next_page(self) -> bool: + has_more = self.has_more + if has_more is not None and has_more is False: + return False + + return super().has_next_page() + @override def next_page_info(self) -> Optional[PageInfo]: data = self.data @@ -105,3 +132,119 @@ def next_page_info(self) -> Optional[PageInfo]: return None return PageInfo(params={"after": item.id}) + + +class SyncConversationCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + data: List[_T] + has_more: Optional[bool] = None + last_id: Optional[str] = None + + @override + def _get_page_items(self) -> List[_T]: + data = self.data + if not data: + return [] + return data + + @override + def has_next_page(self) -> bool: + has_more = self.has_more + if has_more is not None and has_more is False: + return False + + return super().has_next_page() + + @override + def next_page_info(self) -> Optional[PageInfo]: + last_id = self.last_id + if not last_id: + return None + + return PageInfo(params={"after": last_id}) + + +class AsyncConversationCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + data: List[_T] + has_more: Optional[bool] = None + last_id: Optional[str] = None + + @override + def _get_page_items(self) -> List[_T]: + data = self.data + if not data: + return [] + return data + + @override + def has_next_page(self) -> bool: + has_more = self.has_more + if has_more is not None and has_more is False: + return False + + return super().has_next_page() + + @override + def next_page_info(self) -> Optional[PageInfo]: + last_id = self.last_id + if not last_id: + return None + + return PageInfo(params={"after": last_id}) + + +class SyncNextCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + data: List[_T] + has_more: Optional[bool] = None + next: Optional[str] = None + + @override + def _get_page_items(self) -> List[_T]: + data = self.data + if not data: + return [] + return data + + @override + def has_next_page(self) -> bool: + has_more = self.has_more + if has_more is not None and has_more is False: + return False + + return super().has_next_page() + + @override + def next_page_info(self) -> Optional[PageInfo]: + next = self.next + if not next: + return None + + return PageInfo(params={"after": next}) + + +class AsyncNextCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + data: List[_T] + has_more: Optional[bool] = None + next: Optional[str] = None + + @override + def _get_page_items(self) -> List[_T]: + data = self.data + if not data: + return [] + return data + + @override + def has_next_page(self) -> bool: + has_more = self.has_more + if has_more is not None and has_more is False: + return False + + return super().has_next_page() + + @override + def next_page_info(self) -> Optional[PageInfo]: + next = self.next + if not next: + return None + + return PageInfo(params={"after": next}) diff --git a/src/openai/resources/__init__.py b/src/openai/resources/__init__.py index e2cc1c4b0c..e4905152c0 100644 --- a/src/openai/resources/__init__.py +++ b/src/openai/resources/__init__.py @@ -16,6 +16,14 @@ ChatWithStreamingResponse, AsyncChatWithStreamingResponse, ) +from .admin import ( + Admin, + AsyncAdmin, + AdminWithRawResponse, + AsyncAdminWithRawResponse, + AdminWithStreamingResponse, + AsyncAdminWithStreamingResponse, +) from .audio import ( Audio, AsyncAudio, @@ -24,6 +32,14 @@ AudioWithStreamingResponse, AsyncAudioWithStreamingResponse, ) +from .evals import ( + Evals, + AsyncEvals, + EvalsWithRawResponse, + AsyncEvalsWithRawResponse, + EvalsWithStreamingResponse, + AsyncEvalsWithStreamingResponse, +) from .files import ( Files, AsyncFiles, @@ -48,6 +64,22 @@ ModelsWithStreamingResponse, AsyncModelsWithStreamingResponse, ) +from .skills import ( + Skills, + AsyncSkills, + SkillsWithRawResponse, + AsyncSkillsWithRawResponse, + SkillsWithStreamingResponse, + AsyncSkillsWithStreamingResponse, +) +from .videos import ( + Videos, + AsyncVideos, + VideosWithRawResponse, + AsyncVideosWithRawResponse, + VideosWithStreamingResponse, + AsyncVideosWithStreamingResponse, +) from .batches import ( Batches, AsyncBatches, @@ -64,6 +96,14 @@ UploadsWithStreamingResponse, AsyncUploadsWithStreamingResponse, ) +from .containers import ( + Containers, + AsyncContainers, + ContainersWithRawResponse, + AsyncContainersWithRawResponse, + ContainersWithStreamingResponse, + AsyncContainersWithStreamingResponse, +) from .embeddings import ( Embeddings, AsyncEmbeddings, @@ -96,6 +136,14 @@ ModerationsWithStreamingResponse, AsyncModerationsWithStreamingResponse, ) +from .vector_stores import ( + VectorStores, + AsyncVectorStores, + VectorStoresWithRawResponse, + AsyncVectorStoresWithRawResponse, + VectorStoresWithStreamingResponse, + AsyncVectorStoresWithStreamingResponse, +) __all__ = [ "Completions", @@ -152,6 +200,12 @@ "AsyncFineTuningWithRawResponse", "FineTuningWithStreamingResponse", "AsyncFineTuningWithStreamingResponse", + "VectorStores", + "AsyncVectorStores", + "VectorStoresWithRawResponse", + "AsyncVectorStoresWithRawResponse", + "VectorStoresWithStreamingResponse", + "AsyncVectorStoresWithStreamingResponse", "Beta", "AsyncBeta", "BetaWithRawResponse", @@ -170,4 +224,34 @@ "AsyncUploadsWithRawResponse", "UploadsWithStreamingResponse", "AsyncUploadsWithStreamingResponse", + "Admin", + "AsyncAdmin", + "AdminWithRawResponse", + "AsyncAdminWithRawResponse", + "AdminWithStreamingResponse", + "AsyncAdminWithStreamingResponse", + "Evals", + "AsyncEvals", + "EvalsWithRawResponse", + "AsyncEvalsWithRawResponse", + "EvalsWithStreamingResponse", + "AsyncEvalsWithStreamingResponse", + "Containers", + "AsyncContainers", + "ContainersWithRawResponse", + "AsyncContainersWithRawResponse", + "ContainersWithStreamingResponse", + "AsyncContainersWithStreamingResponse", + "Skills", + "AsyncSkills", + "SkillsWithRawResponse", + "AsyncSkillsWithRawResponse", + "SkillsWithStreamingResponse", + "AsyncSkillsWithStreamingResponse", + "Videos", + "AsyncVideos", + "VideosWithRawResponse", + "AsyncVideosWithRawResponse", + "VideosWithStreamingResponse", + "AsyncVideosWithStreamingResponse", ] diff --git a/src/openai/resources/admin/__init__.py b/src/openai/resources/admin/__init__.py new file mode 100644 index 0000000000..730c20540c --- /dev/null +++ b/src/openai/resources/admin/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .admin import ( + Admin, + AsyncAdmin, + AdminWithRawResponse, + AsyncAdminWithRawResponse, + AdminWithStreamingResponse, + AsyncAdminWithStreamingResponse, +) +from .organization import ( + Organization, + AsyncOrganization, + OrganizationWithRawResponse, + AsyncOrganizationWithRawResponse, + OrganizationWithStreamingResponse, + AsyncOrganizationWithStreamingResponse, +) + +__all__ = [ + "Organization", + "AsyncOrganization", + "OrganizationWithRawResponse", + "AsyncOrganizationWithRawResponse", + "OrganizationWithStreamingResponse", + "AsyncOrganizationWithStreamingResponse", + "Admin", + "AsyncAdmin", + "AdminWithRawResponse", + "AsyncAdminWithRawResponse", + "AdminWithStreamingResponse", + "AsyncAdminWithStreamingResponse", +] diff --git a/src/openai/resources/admin/admin.py b/src/openai/resources/admin/admin.py new file mode 100644 index 0000000000..3a3e288c6a --- /dev/null +++ b/src/openai/resources/admin/admin.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from .organization.organization import ( + Organization, + AsyncOrganization, + OrganizationWithRawResponse, + AsyncOrganizationWithRawResponse, + OrganizationWithStreamingResponse, + AsyncOrganizationWithStreamingResponse, +) + +__all__ = ["Admin", "AsyncAdmin"] + + +class Admin(SyncAPIResource): + @cached_property + def organization(self) -> Organization: + return Organization(self._client) + + @cached_property + def with_raw_response(self) -> AdminWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AdminWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AdminWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AdminWithStreamingResponse(self) + + +class AsyncAdmin(AsyncAPIResource): + @cached_property + def organization(self) -> AsyncOrganization: + return AsyncOrganization(self._client) + + @cached_property + def with_raw_response(self) -> AsyncAdminWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncAdminWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAdminWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncAdminWithStreamingResponse(self) + + +class AdminWithRawResponse: + def __init__(self, admin: Admin) -> None: + self._admin = admin + + @cached_property + def organization(self) -> OrganizationWithRawResponse: + return OrganizationWithRawResponse(self._admin.organization) + + +class AsyncAdminWithRawResponse: + def __init__(self, admin: AsyncAdmin) -> None: + self._admin = admin + + @cached_property + def organization(self) -> AsyncOrganizationWithRawResponse: + return AsyncOrganizationWithRawResponse(self._admin.organization) + + +class AdminWithStreamingResponse: + def __init__(self, admin: Admin) -> None: + self._admin = admin + + @cached_property + def organization(self) -> OrganizationWithStreamingResponse: + return OrganizationWithStreamingResponse(self._admin.organization) + + +class AsyncAdminWithStreamingResponse: + def __init__(self, admin: AsyncAdmin) -> None: + self._admin = admin + + @cached_property + def organization(self) -> AsyncOrganizationWithStreamingResponse: + return AsyncOrganizationWithStreamingResponse(self._admin.organization) diff --git a/src/openai/resources/admin/organization/__init__.py b/src/openai/resources/admin/organization/__init__.py new file mode 100644 index 0000000000..d87fbbc81d --- /dev/null +++ b/src/openai/resources/admin/organization/__init__.py @@ -0,0 +1,173 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from .usage import ( + Usage, + AsyncUsage, + UsageWithRawResponse, + AsyncUsageWithRawResponse, + UsageWithStreamingResponse, + AsyncUsageWithStreamingResponse, +) +from .users import ( + Users, + AsyncUsers, + UsersWithRawResponse, + AsyncUsersWithRawResponse, + UsersWithStreamingResponse, + AsyncUsersWithStreamingResponse, +) +from .groups import ( + Groups, + AsyncGroups, + GroupsWithRawResponse, + AsyncGroupsWithRawResponse, + GroupsWithStreamingResponse, + AsyncGroupsWithStreamingResponse, +) +from .invites import ( + Invites, + AsyncInvites, + InvitesWithRawResponse, + AsyncInvitesWithRawResponse, + InvitesWithStreamingResponse, + AsyncInvitesWithStreamingResponse, +) +from .projects import ( + Projects, + AsyncProjects, + ProjectsWithRawResponse, + AsyncProjectsWithRawResponse, + ProjectsWithStreamingResponse, + AsyncProjectsWithStreamingResponse, +) +from .audit_logs import ( + AuditLogs, + AsyncAuditLogs, + AuditLogsWithRawResponse, + AsyncAuditLogsWithRawResponse, + AuditLogsWithStreamingResponse, + AsyncAuditLogsWithStreamingResponse, +) +from .certificates import ( + Certificates, + AsyncCertificates, + CertificatesWithRawResponse, + AsyncCertificatesWithRawResponse, + CertificatesWithStreamingResponse, + AsyncCertificatesWithStreamingResponse, +) +from .organization import ( + Organization, + AsyncOrganization, + OrganizationWithRawResponse, + AsyncOrganizationWithRawResponse, + OrganizationWithStreamingResponse, + AsyncOrganizationWithStreamingResponse, +) +from .spend_alerts import ( + SpendAlerts, + AsyncSpendAlerts, + SpendAlertsWithRawResponse, + AsyncSpendAlertsWithRawResponse, + SpendAlertsWithStreamingResponse, + AsyncSpendAlertsWithStreamingResponse, +) +from .admin_api_keys import ( + AdminAPIKeys, + AsyncAdminAPIKeys, + AdminAPIKeysWithRawResponse, + AsyncAdminAPIKeysWithRawResponse, + AdminAPIKeysWithStreamingResponse, + AsyncAdminAPIKeysWithStreamingResponse, +) +from .data_retention import ( + DataRetention, + AsyncDataRetention, + DataRetentionWithRawResponse, + AsyncDataRetentionWithRawResponse, + DataRetentionWithStreamingResponse, + AsyncDataRetentionWithStreamingResponse, +) + +__all__ = [ + "AuditLogs", + "AsyncAuditLogs", + "AuditLogsWithRawResponse", + "AsyncAuditLogsWithRawResponse", + "AuditLogsWithStreamingResponse", + "AsyncAuditLogsWithStreamingResponse", + "AdminAPIKeys", + "AsyncAdminAPIKeys", + "AdminAPIKeysWithRawResponse", + "AsyncAdminAPIKeysWithRawResponse", + "AdminAPIKeysWithStreamingResponse", + "AsyncAdminAPIKeysWithStreamingResponse", + "Usage", + "AsyncUsage", + "UsageWithRawResponse", + "AsyncUsageWithRawResponse", + "UsageWithStreamingResponse", + "AsyncUsageWithStreamingResponse", + "Invites", + "AsyncInvites", + "InvitesWithRawResponse", + "AsyncInvitesWithRawResponse", + "InvitesWithStreamingResponse", + "AsyncInvitesWithStreamingResponse", + "Users", + "AsyncUsers", + "UsersWithRawResponse", + "AsyncUsersWithRawResponse", + "UsersWithStreamingResponse", + "AsyncUsersWithStreamingResponse", + "Groups", + "AsyncGroups", + "GroupsWithRawResponse", + "AsyncGroupsWithRawResponse", + "GroupsWithStreamingResponse", + "AsyncGroupsWithStreamingResponse", + "Roles", + "AsyncRoles", + "RolesWithRawResponse", + "AsyncRolesWithRawResponse", + "RolesWithStreamingResponse", + "AsyncRolesWithStreamingResponse", + "DataRetention", + "AsyncDataRetention", + "DataRetentionWithRawResponse", + "AsyncDataRetentionWithRawResponse", + "DataRetentionWithStreamingResponse", + "AsyncDataRetentionWithStreamingResponse", + "SpendAlerts", + "AsyncSpendAlerts", + "SpendAlertsWithRawResponse", + "AsyncSpendAlertsWithRawResponse", + "SpendAlertsWithStreamingResponse", + "AsyncSpendAlertsWithStreamingResponse", + "Certificates", + "AsyncCertificates", + "CertificatesWithRawResponse", + "AsyncCertificatesWithRawResponse", + "CertificatesWithStreamingResponse", + "AsyncCertificatesWithStreamingResponse", + "Projects", + "AsyncProjects", + "ProjectsWithRawResponse", + "AsyncProjectsWithRawResponse", + "ProjectsWithStreamingResponse", + "AsyncProjectsWithStreamingResponse", + "Organization", + "AsyncOrganization", + "OrganizationWithRawResponse", + "AsyncOrganizationWithRawResponse", + "OrganizationWithStreamingResponse", + "AsyncOrganizationWithStreamingResponse", +] diff --git a/src/openai/resources/admin/organization/admin_api_keys.py b/src/openai/resources/admin/organization/admin_api_keys.py new file mode 100644 index 0000000000..80ddc39b49 --- /dev/null +++ b/src/openai/resources/admin/organization/admin_api_keys.py @@ -0,0 +1,469 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncCursorPage, AsyncCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.admin.organization import admin_api_key_list_params, admin_api_key_create_params +from ....types.admin.organization.admin_api_key import AdminAPIKey +from ....types.admin.organization.admin_api_key_create_response import AdminAPIKeyCreateResponse +from ....types.admin.organization.admin_api_key_delete_response import AdminAPIKeyDeleteResponse + +__all__ = ["AdminAPIKeys", "AsyncAdminAPIKeys"] + + +class AdminAPIKeys(SyncAPIResource): + @cached_property + def with_raw_response(self) -> AdminAPIKeysWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AdminAPIKeysWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AdminAPIKeysWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AdminAPIKeysWithStreamingResponse(self) + + def create( + self, + *, + name: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdminAPIKeyCreateResponse: + """ + Create an organization admin API key + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/organization/admin_api_keys", + body=maybe_transform({"name": name}, admin_api_key_create_params.AdminAPIKeyCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=AdminAPIKeyCreateResponse, + ) + + def retrieve( + self, + key_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdminAPIKey: + """ + Retrieve a single organization API key + + Args: + key_id: The ID of the API key. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not key_id: + raise ValueError(f"Expected a non-empty value for `key_id` but received {key_id!r}") + return self._get( + path_template("/organization/admin_api_keys/{key_id}", key_id=key_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=AdminAPIKey, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[AdminAPIKey]: + """ + List organization API keys + + Args: + after: Return keys with IDs that come after this ID in the pagination order. + + limit: Maximum number of keys to return. + + order: Order results by creation time, ascending or descending. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/admin_api_keys", + page=SyncCursorPage[AdminAPIKey], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + admin_api_key_list_params.AdminAPIKeyListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=AdminAPIKey, + ) + + def delete( + self, + key_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdminAPIKeyDeleteResponse: + """ + Delete an organization admin API key + + Args: + key_id: The ID of the API key to be deleted. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not key_id: + raise ValueError(f"Expected a non-empty value for `key_id` but received {key_id!r}") + return self._delete( + path_template("/organization/admin_api_keys/{key_id}", key_id=key_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=AdminAPIKeyDeleteResponse, + ) + + +class AsyncAdminAPIKeys(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncAdminAPIKeysWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncAdminAPIKeysWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAdminAPIKeysWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncAdminAPIKeysWithStreamingResponse(self) + + async def create( + self, + *, + name: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdminAPIKeyCreateResponse: + """ + Create an organization admin API key + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/organization/admin_api_keys", + body=await async_maybe_transform({"name": name}, admin_api_key_create_params.AdminAPIKeyCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=AdminAPIKeyCreateResponse, + ) + + async def retrieve( + self, + key_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdminAPIKey: + """ + Retrieve a single organization API key + + Args: + key_id: The ID of the API key. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not key_id: + raise ValueError(f"Expected a non-empty value for `key_id` but received {key_id!r}") + return await self._get( + path_template("/organization/admin_api_keys/{key_id}", key_id=key_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=AdminAPIKey, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[AdminAPIKey, AsyncCursorPage[AdminAPIKey]]: + """ + List organization API keys + + Args: + after: Return keys with IDs that come after this ID in the pagination order. + + limit: Maximum number of keys to return. + + order: Order results by creation time, ascending or descending. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/admin_api_keys", + page=AsyncCursorPage[AdminAPIKey], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + admin_api_key_list_params.AdminAPIKeyListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=AdminAPIKey, + ) + + async def delete( + self, + key_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AdminAPIKeyDeleteResponse: + """ + Delete an organization admin API key + + Args: + key_id: The ID of the API key to be deleted. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not key_id: + raise ValueError(f"Expected a non-empty value for `key_id` but received {key_id!r}") + return await self._delete( + path_template("/organization/admin_api_keys/{key_id}", key_id=key_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=AdminAPIKeyDeleteResponse, + ) + + +class AdminAPIKeysWithRawResponse: + def __init__(self, admin_api_keys: AdminAPIKeys) -> None: + self._admin_api_keys = admin_api_keys + + self.create = _legacy_response.to_raw_response_wrapper( + admin_api_keys.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + admin_api_keys.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + admin_api_keys.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + admin_api_keys.delete, + ) + + +class AsyncAdminAPIKeysWithRawResponse: + def __init__(self, admin_api_keys: AsyncAdminAPIKeys) -> None: + self._admin_api_keys = admin_api_keys + + self.create = _legacy_response.async_to_raw_response_wrapper( + admin_api_keys.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + admin_api_keys.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + admin_api_keys.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + admin_api_keys.delete, + ) + + +class AdminAPIKeysWithStreamingResponse: + def __init__(self, admin_api_keys: AdminAPIKeys) -> None: + self._admin_api_keys = admin_api_keys + + self.create = to_streamed_response_wrapper( + admin_api_keys.create, + ) + self.retrieve = to_streamed_response_wrapper( + admin_api_keys.retrieve, + ) + self.list = to_streamed_response_wrapper( + admin_api_keys.list, + ) + self.delete = to_streamed_response_wrapper( + admin_api_keys.delete, + ) + + +class AsyncAdminAPIKeysWithStreamingResponse: + def __init__(self, admin_api_keys: AsyncAdminAPIKeys) -> None: + self._admin_api_keys = admin_api_keys + + self.create = async_to_streamed_response_wrapper( + admin_api_keys.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + admin_api_keys.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + admin_api_keys.list, + ) + self.delete = async_to_streamed_response_wrapper( + admin_api_keys.delete, + ) diff --git a/src/openai/resources/admin/organization/audit_logs.py b/src/openai/resources/admin/organization/audit_logs.py new file mode 100644 index 0000000000..760fb4b034 --- /dev/null +++ b/src/openai/resources/admin/organization/audit_logs.py @@ -0,0 +1,399 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ...._utils import maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.admin.organization import audit_log_list_params +from ....types.admin.organization.audit_log_list_response import AuditLogListResponse + +__all__ = ["AuditLogs", "AsyncAuditLogs"] + + +class AuditLogs(SyncAPIResource): + """List user actions and configuration changes within this organization.""" + + @cached_property + def with_raw_response(self) -> AuditLogsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AuditLogsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AuditLogsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AuditLogsWithStreamingResponse(self) + + def list( + self, + *, + actor_emails: SequenceNotStr[str] | Omit = omit, + actor_ids: SequenceNotStr[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + effective_at: audit_log_list_params.EffectiveAt | Omit = omit, + event_types: List[ + Literal[ + "api_key.created", + "api_key.updated", + "api_key.deleted", + "certificate.created", + "certificate.updated", + "certificate.deleted", + "certificates.activated", + "certificates.deactivated", + "checkpoint.permission.created", + "checkpoint.permission.deleted", + "external_key.registered", + "external_key.removed", + "group.created", + "group.updated", + "group.deleted", + "invite.sent", + "invite.accepted", + "invite.deleted", + "ip_allowlist.created", + "ip_allowlist.updated", + "ip_allowlist.deleted", + "ip_allowlist.config.activated", + "ip_allowlist.config.deactivated", + "login.succeeded", + "login.failed", + "logout.succeeded", + "logout.failed", + "organization.updated", + "project.created", + "project.updated", + "project.archived", + "project.deleted", + "rate_limit.updated", + "rate_limit.deleted", + "resource.deleted", + "tunnel.created", + "tunnel.updated", + "tunnel.deleted", + "workload_identity_provider.created", + "workload_identity_provider.updated", + "workload_identity_provider.deleted", + "workload_identity_provider_mapping.created", + "workload_identity_provider_mapping.updated", + "workload_identity_provider_mapping.deleted", + "role.created", + "role.updated", + "role.deleted", + "role.assignment.created", + "role.assignment.deleted", + "scim.enabled", + "scim.disabled", + "service_account.created", + "service_account.updated", + "service_account.deleted", + "user.added", + "user.updated", + "user.deleted", + ] + ] + | Omit = omit, + limit: int | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + resource_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[AuditLogListResponse]: + """ + List user actions and configuration changes within this organization. + + Args: + actor_emails: Return only events performed by users with these emails. + + actor_ids: Return only events performed by these actors. Can be a user ID, a service + account ID, or an api key tracking ID. + + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + before: A cursor for use in pagination. `before` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. + + effective_at: Return only events whose `effective_at` (Unix seconds) is in this range. + + event_types: Return only events with a `type` in one of these values. For example, + `project.created`. For all options, see the documentation for the + [audit log object](https://platform.openai.com/docs/api-reference/audit-logs/object). + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + project_ids: Return only events for these projects. + + resource_ids: Return only events performed on these targets. For example, a project ID + updated. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/audit_logs", + page=SyncConversationCursorPage[AuditLogListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "actor_emails": actor_emails, + "actor_ids": actor_ids, + "after": after, + "before": before, + "effective_at": effective_at, + "event_types": event_types, + "limit": limit, + "project_ids": project_ids, + "resource_ids": resource_ids, + }, + audit_log_list_params.AuditLogListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=AuditLogListResponse, + ) + + +class AsyncAuditLogs(AsyncAPIResource): + """List user actions and configuration changes within this organization.""" + + @cached_property + def with_raw_response(self) -> AsyncAuditLogsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncAuditLogsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAuditLogsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncAuditLogsWithStreamingResponse(self) + + def list( + self, + *, + actor_emails: SequenceNotStr[str] | Omit = omit, + actor_ids: SequenceNotStr[str] | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + effective_at: audit_log_list_params.EffectiveAt | Omit = omit, + event_types: List[ + Literal[ + "api_key.created", + "api_key.updated", + "api_key.deleted", + "certificate.created", + "certificate.updated", + "certificate.deleted", + "certificates.activated", + "certificates.deactivated", + "checkpoint.permission.created", + "checkpoint.permission.deleted", + "external_key.registered", + "external_key.removed", + "group.created", + "group.updated", + "group.deleted", + "invite.sent", + "invite.accepted", + "invite.deleted", + "ip_allowlist.created", + "ip_allowlist.updated", + "ip_allowlist.deleted", + "ip_allowlist.config.activated", + "ip_allowlist.config.deactivated", + "login.succeeded", + "login.failed", + "logout.succeeded", + "logout.failed", + "organization.updated", + "project.created", + "project.updated", + "project.archived", + "project.deleted", + "rate_limit.updated", + "rate_limit.deleted", + "resource.deleted", + "tunnel.created", + "tunnel.updated", + "tunnel.deleted", + "workload_identity_provider.created", + "workload_identity_provider.updated", + "workload_identity_provider.deleted", + "workload_identity_provider_mapping.created", + "workload_identity_provider_mapping.updated", + "workload_identity_provider_mapping.deleted", + "role.created", + "role.updated", + "role.deleted", + "role.assignment.created", + "role.assignment.deleted", + "scim.enabled", + "scim.disabled", + "service_account.created", + "service_account.updated", + "service_account.deleted", + "user.added", + "user.updated", + "user.deleted", + ] + ] + | Omit = omit, + limit: int | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + resource_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[AuditLogListResponse, AsyncConversationCursorPage[AuditLogListResponse]]: + """ + List user actions and configuration changes within this organization. + + Args: + actor_emails: Return only events performed by users with these emails. + + actor_ids: Return only events performed by these actors. Can be a user ID, a service + account ID, or an api key tracking ID. + + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + before: A cursor for use in pagination. `before` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. + + effective_at: Return only events whose `effective_at` (Unix seconds) is in this range. + + event_types: Return only events with a `type` in one of these values. For example, + `project.created`. For all options, see the documentation for the + [audit log object](https://platform.openai.com/docs/api-reference/audit-logs/object). + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + project_ids: Return only events for these projects. + + resource_ids: Return only events performed on these targets. For example, a project ID + updated. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/audit_logs", + page=AsyncConversationCursorPage[AuditLogListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "actor_emails": actor_emails, + "actor_ids": actor_ids, + "after": after, + "before": before, + "effective_at": effective_at, + "event_types": event_types, + "limit": limit, + "project_ids": project_ids, + "resource_ids": resource_ids, + }, + audit_log_list_params.AuditLogListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=AuditLogListResponse, + ) + + +class AuditLogsWithRawResponse: + def __init__(self, audit_logs: AuditLogs) -> None: + self._audit_logs = audit_logs + + self.list = _legacy_response.to_raw_response_wrapper( + audit_logs.list, + ) + + +class AsyncAuditLogsWithRawResponse: + def __init__(self, audit_logs: AsyncAuditLogs) -> None: + self._audit_logs = audit_logs + + self.list = _legacy_response.async_to_raw_response_wrapper( + audit_logs.list, + ) + + +class AuditLogsWithStreamingResponse: + def __init__(self, audit_logs: AuditLogs) -> None: + self._audit_logs = audit_logs + + self.list = to_streamed_response_wrapper( + audit_logs.list, + ) + + +class AsyncAuditLogsWithStreamingResponse: + def __init__(self, audit_logs: AsyncAuditLogs) -> None: + self._audit_logs = audit_logs + + self.list = async_to_streamed_response_wrapper( + audit_logs.list, + ) diff --git a/src/openai/resources/admin/organization/certificates.py b/src/openai/resources/admin/organization/certificates.py new file mode 100644 index 0000000000..aa7826794d --- /dev/null +++ b/src/openai/resources/admin/organization/certificates.py @@ -0,0 +1,818 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncPage, AsyncPage, SyncConversationCursorPage, AsyncConversationCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.admin.organization import ( + certificate_list_params, + certificate_create_params, + certificate_update_params, + certificate_activate_params, + certificate_retrieve_params, + certificate_deactivate_params, +) +from ....types.admin.organization.certificate import Certificate +from ....types.admin.organization.certificate_list_response import CertificateListResponse +from ....types.admin.organization.certificate_delete_response import CertificateDeleteResponse +from ....types.admin.organization.certificate_activate_response import CertificateActivateResponse +from ....types.admin.organization.certificate_deactivate_response import CertificateDeactivateResponse + +__all__ = ["Certificates", "AsyncCertificates"] + + +class Certificates(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CertificatesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return CertificatesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CertificatesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return CertificatesWithStreamingResponse(self) + + def create( + self, + *, + certificate: str, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Certificate: + """Upload a certificate to the organization. + + This does **not** automatically + activate the certificate. + + Organizations can upload up to 50 certificates. + + Args: + certificate: The certificate content in PEM format + + name: An optional name for the certificate + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/organization/certificates", + body=maybe_transform( + { + "certificate": certificate, + "name": name, + }, + certificate_create_params.CertificateCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Certificate, + ) + + def retrieve( + self, + certificate_id: str, + *, + include: List[Literal["content"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Certificate: + """ + Get a certificate that has been uploaded to the organization. + + You can get a certificate regardless of whether it is active or not. + + Args: + include: A list of additional fields to include in the response. Currently the only + supported value is `content` to fetch the PEM content of the certificate. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not certificate_id: + raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}") + return self._get( + path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"include": include}, certificate_retrieve_params.CertificateRetrieveParams), + security={"admin_api_key_auth": True}, + ), + cast_to=Certificate, + ) + + def update( + self, + certificate_id: str, + *, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Certificate: + """Modify a certificate. + + Note that only the name can be modified. + + Args: + name: The updated name for the certificate + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not certificate_id: + raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}") + return self._post( + path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id), + body=maybe_transform({"name": name}, certificate_update_params.CertificateUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Certificate, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[CertificateListResponse]: + """ + List uploaded certificates for this organization. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending + order and `desc` for descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/certificates", + page=SyncConversationCursorPage[CertificateListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + certificate_list_params.CertificateListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=CertificateListResponse, + ) + + def delete( + self, + certificate_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CertificateDeleteResponse: + """ + Delete a certificate from the organization. + + The certificate must be inactive for the organization and all projects. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not certificate_id: + raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}") + return self._delete( + path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=CertificateDeleteResponse, + ) + + def activate( + self, + *, + certificate_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncPage[CertificateActivateResponse]: + """ + Activate certificates at the organization level. + + You can atomically and idempotently activate up to 10 certificates at a time. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/certificates/activate", + page=SyncPage[CertificateActivateResponse], + body=maybe_transform( + {"certificate_ids": certificate_ids}, certificate_activate_params.CertificateActivateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + model=CertificateActivateResponse, + method="post", + ) + + def deactivate( + self, + *, + certificate_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncPage[CertificateDeactivateResponse]: + """ + Deactivate certificates at the organization level. + + You can atomically and idempotently deactivate up to 10 certificates at a time. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/certificates/deactivate", + page=SyncPage[CertificateDeactivateResponse], + body=maybe_transform( + {"certificate_ids": certificate_ids}, certificate_deactivate_params.CertificateDeactivateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + model=CertificateDeactivateResponse, + method="post", + ) + + +class AsyncCertificates(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCertificatesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncCertificatesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCertificatesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncCertificatesWithStreamingResponse(self) + + async def create( + self, + *, + certificate: str, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Certificate: + """Upload a certificate to the organization. + + This does **not** automatically + activate the certificate. + + Organizations can upload up to 50 certificates. + + Args: + certificate: The certificate content in PEM format + + name: An optional name for the certificate + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/organization/certificates", + body=await async_maybe_transform( + { + "certificate": certificate, + "name": name, + }, + certificate_create_params.CertificateCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Certificate, + ) + + async def retrieve( + self, + certificate_id: str, + *, + include: List[Literal["content"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Certificate: + """ + Get a certificate that has been uploaded to the organization. + + You can get a certificate regardless of whether it is active or not. + + Args: + include: A list of additional fields to include in the response. Currently the only + supported value is `content` to fetch the PEM content of the certificate. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not certificate_id: + raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}") + return await self._get( + path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"include": include}, certificate_retrieve_params.CertificateRetrieveParams + ), + security={"admin_api_key_auth": True}, + ), + cast_to=Certificate, + ) + + async def update( + self, + certificate_id: str, + *, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Certificate: + """Modify a certificate. + + Note that only the name can be modified. + + Args: + name: The updated name for the certificate + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not certificate_id: + raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}") + return await self._post( + path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id), + body=await async_maybe_transform({"name": name}, certificate_update_params.CertificateUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Certificate, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CertificateListResponse, AsyncConversationCursorPage[CertificateListResponse]]: + """ + List uploaded certificates for this organization. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending + order and `desc` for descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/certificates", + page=AsyncConversationCursorPage[CertificateListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + certificate_list_params.CertificateListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=CertificateListResponse, + ) + + async def delete( + self, + certificate_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CertificateDeleteResponse: + """ + Delete a certificate from the organization. + + The certificate must be inactive for the organization and all projects. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not certificate_id: + raise ValueError(f"Expected a non-empty value for `certificate_id` but received {certificate_id!r}") + return await self._delete( + path_template("/organization/certificates/{certificate_id}", certificate_id=certificate_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=CertificateDeleteResponse, + ) + + def activate( + self, + *, + certificate_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CertificateActivateResponse, AsyncPage[CertificateActivateResponse]]: + """ + Activate certificates at the organization level. + + You can atomically and idempotently activate up to 10 certificates at a time. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/certificates/activate", + page=AsyncPage[CertificateActivateResponse], + body=maybe_transform( + {"certificate_ids": certificate_ids}, certificate_activate_params.CertificateActivateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + model=CertificateActivateResponse, + method="post", + ) + + def deactivate( + self, + *, + certificate_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CertificateDeactivateResponse, AsyncPage[CertificateDeactivateResponse]]: + """ + Deactivate certificates at the organization level. + + You can atomically and idempotently deactivate up to 10 certificates at a time. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/certificates/deactivate", + page=AsyncPage[CertificateDeactivateResponse], + body=maybe_transform( + {"certificate_ids": certificate_ids}, certificate_deactivate_params.CertificateDeactivateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + model=CertificateDeactivateResponse, + method="post", + ) + + +class CertificatesWithRawResponse: + def __init__(self, certificates: Certificates) -> None: + self._certificates = certificates + + self.create = _legacy_response.to_raw_response_wrapper( + certificates.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + certificates.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + certificates.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + certificates.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + certificates.delete, + ) + self.activate = _legacy_response.to_raw_response_wrapper( + certificates.activate, + ) + self.deactivate = _legacy_response.to_raw_response_wrapper( + certificates.deactivate, + ) + + +class AsyncCertificatesWithRawResponse: + def __init__(self, certificates: AsyncCertificates) -> None: + self._certificates = certificates + + self.create = _legacy_response.async_to_raw_response_wrapper( + certificates.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + certificates.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + certificates.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + certificates.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + certificates.delete, + ) + self.activate = _legacy_response.async_to_raw_response_wrapper( + certificates.activate, + ) + self.deactivate = _legacy_response.async_to_raw_response_wrapper( + certificates.deactivate, + ) + + +class CertificatesWithStreamingResponse: + def __init__(self, certificates: Certificates) -> None: + self._certificates = certificates + + self.create = to_streamed_response_wrapper( + certificates.create, + ) + self.retrieve = to_streamed_response_wrapper( + certificates.retrieve, + ) + self.update = to_streamed_response_wrapper( + certificates.update, + ) + self.list = to_streamed_response_wrapper( + certificates.list, + ) + self.delete = to_streamed_response_wrapper( + certificates.delete, + ) + self.activate = to_streamed_response_wrapper( + certificates.activate, + ) + self.deactivate = to_streamed_response_wrapper( + certificates.deactivate, + ) + + +class AsyncCertificatesWithStreamingResponse: + def __init__(self, certificates: AsyncCertificates) -> None: + self._certificates = certificates + + self.create = async_to_streamed_response_wrapper( + certificates.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + certificates.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + certificates.update, + ) + self.list = async_to_streamed_response_wrapper( + certificates.list, + ) + self.delete = async_to_streamed_response_wrapper( + certificates.delete, + ) + self.activate = async_to_streamed_response_wrapper( + certificates.activate, + ) + self.deactivate = async_to_streamed_response_wrapper( + certificates.deactivate, + ) diff --git a/src/openai/resources/admin/organization/data_retention.py b/src/openai/resources/admin/organization/data_retention.py new file mode 100644 index 0000000000..c3b7325a92 --- /dev/null +++ b/src/openai/resources/admin/organization/data_retention.py @@ -0,0 +1,245 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Query, Headers, NotGiven, not_given +from ...._utils import maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...._base_client import make_request_options +from ....types.admin.organization import data_retention_update_params +from ....types.admin.organization.organization_data_retention import OrganizationDataRetention + +__all__ = ["DataRetention", "AsyncDataRetention"] + + +class DataRetention(SyncAPIResource): + @cached_property + def with_raw_response(self) -> DataRetentionWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return DataRetentionWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> DataRetentionWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return DataRetentionWithStreamingResponse(self) + + def retrieve( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationDataRetention: + """Retrieves organization data retention controls.""" + return self._get( + "/organization/data_retention", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationDataRetention, + ) + + def update( + self, + *, + retention_type: Literal[ + "zero_data_retention", + "modified_abuse_monitoring", + "enhanced_zero_data_retention", + "enhanced_modified_abuse_monitoring", + ], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationDataRetention: + """ + Updates organization data retention controls. + + Args: + retention_type: The desired organization data retention type. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/organization/data_retention", + body=maybe_transform( + {"retention_type": retention_type}, data_retention_update_params.DataRetentionUpdateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationDataRetention, + ) + + +class AsyncDataRetention(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncDataRetentionWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncDataRetentionWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncDataRetentionWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncDataRetentionWithStreamingResponse(self) + + async def retrieve( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationDataRetention: + """Retrieves organization data retention controls.""" + return await self._get( + "/organization/data_retention", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationDataRetention, + ) + + async def update( + self, + *, + retention_type: Literal[ + "zero_data_retention", + "modified_abuse_monitoring", + "enhanced_zero_data_retention", + "enhanced_modified_abuse_monitoring", + ], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationDataRetention: + """ + Updates organization data retention controls. + + Args: + retention_type: The desired organization data retention type. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/organization/data_retention", + body=await async_maybe_transform( + {"retention_type": retention_type}, data_retention_update_params.DataRetentionUpdateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationDataRetention, + ) + + +class DataRetentionWithRawResponse: + def __init__(self, data_retention: DataRetention) -> None: + self._data_retention = data_retention + + self.retrieve = _legacy_response.to_raw_response_wrapper( + data_retention.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + data_retention.update, + ) + + +class AsyncDataRetentionWithRawResponse: + def __init__(self, data_retention: AsyncDataRetention) -> None: + self._data_retention = data_retention + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + data_retention.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + data_retention.update, + ) + + +class DataRetentionWithStreamingResponse: + def __init__(self, data_retention: DataRetention) -> None: + self._data_retention = data_retention + + self.retrieve = to_streamed_response_wrapper( + data_retention.retrieve, + ) + self.update = to_streamed_response_wrapper( + data_retention.update, + ) + + +class AsyncDataRetentionWithStreamingResponse: + def __init__(self, data_retention: AsyncDataRetention) -> None: + self._data_retention = data_retention + + self.retrieve = async_to_streamed_response_wrapper( + data_retention.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + data_retention.update, + ) diff --git a/src/openai/resources/admin/organization/groups/__init__.py b/src/openai/resources/admin/organization/groups/__init__.py new file mode 100644 index 0000000000..ffeb8b60ec --- /dev/null +++ b/src/openai/resources/admin/organization/groups/__init__.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from .users import ( + Users, + AsyncUsers, + UsersWithRawResponse, + AsyncUsersWithRawResponse, + UsersWithStreamingResponse, + AsyncUsersWithStreamingResponse, +) +from .groups import ( + Groups, + AsyncGroups, + GroupsWithRawResponse, + AsyncGroupsWithRawResponse, + GroupsWithStreamingResponse, + AsyncGroupsWithStreamingResponse, +) + +__all__ = [ + "Users", + "AsyncUsers", + "UsersWithRawResponse", + "AsyncUsersWithRawResponse", + "UsersWithStreamingResponse", + "AsyncUsersWithStreamingResponse", + "Roles", + "AsyncRoles", + "RolesWithRawResponse", + "AsyncRolesWithRawResponse", + "RolesWithStreamingResponse", + "AsyncRolesWithStreamingResponse", + "Groups", + "AsyncGroups", + "GroupsWithRawResponse", + "AsyncGroupsWithRawResponse", + "GroupsWithStreamingResponse", + "AsyncGroupsWithStreamingResponse", +] diff --git a/src/openai/resources/admin/organization/groups/groups.py b/src/openai/resources/admin/organization/groups/groups.py new file mode 100644 index 0000000000..fa7a6b2354 --- /dev/null +++ b/src/openai/resources/admin/organization/groups/groups.py @@ -0,0 +1,630 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..... import _legacy_response +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from .users import ( + Users, + AsyncUsers, + UsersWithRawResponse, + AsyncUsersWithRawResponse, + UsersWithStreamingResponse, + AsyncUsersWithStreamingResponse, +) +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncNextCursorPage, AsyncNextCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization import group_list_params, group_create_params, group_update_params +from .....types.admin.organization.group import Group +from .....types.admin.organization.group_delete_response import GroupDeleteResponse +from .....types.admin.organization.group_update_response import GroupUpdateResponse + +__all__ = ["Groups", "AsyncGroups"] + + +class Groups(SyncAPIResource): + @cached_property + def users(self) -> Users: + return Users(self._client) + + @cached_property + def roles(self) -> Roles: + return Roles(self._client) + + @cached_property + def with_raw_response(self) -> GroupsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return GroupsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> GroupsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return GroupsWithStreamingResponse(self) + + def create( + self, + *, + name: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Group: + """ + Creates a new group in the organization. + + Args: + name: Human readable name for the group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/organization/groups", + body=maybe_transform({"name": name}, group_create_params.GroupCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Group, + ) + + def retrieve( + self, + group_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Group: + """ + Retrieves a group. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._get( + path_template("/organization/groups/{group_id}", group_id=group_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Group, + ) + + def update( + self, + group_id: str, + *, + name: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GroupUpdateResponse: + """ + Updates a group's information. + + Args: + name: New display name for the group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._post( + path_template("/organization/groups/{group_id}", group_id=group_id), + body=maybe_transform({"name": name}, group_update_params.GroupUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=GroupUpdateResponse, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncNextCursorPage[Group]: + """ + Lists all groups in the organization. + + Args: + after: A cursor for use in pagination. `after` is a group ID that defines your place in + the list. For instance, if you make a list request and receive 100 objects, + ending with group_abc, your subsequent call can include `after=group_abc` in + order to fetch the next page of the list. + + limit: A limit on the number of groups to be returned. Limit can range between 0 and + 1000, and the default is 100. + + order: Specifies the sort order of the returned groups. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/groups", + page=SyncNextCursorPage[Group], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + group_list_params.GroupListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=Group, + ) + + def delete( + self, + group_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GroupDeleteResponse: + """ + Deletes a group from the organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._delete( + path_template("/organization/groups/{group_id}", group_id=group_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=GroupDeleteResponse, + ) + + +class AsyncGroups(AsyncAPIResource): + @cached_property + def users(self) -> AsyncUsers: + return AsyncUsers(self._client) + + @cached_property + def roles(self) -> AsyncRoles: + return AsyncRoles(self._client) + + @cached_property + def with_raw_response(self) -> AsyncGroupsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncGroupsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncGroupsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncGroupsWithStreamingResponse(self) + + async def create( + self, + *, + name: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Group: + """ + Creates a new group in the organization. + + Args: + name: Human readable name for the group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/organization/groups", + body=await async_maybe_transform({"name": name}, group_create_params.GroupCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Group, + ) + + async def retrieve( + self, + group_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Group: + """ + Retrieves a group. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return await self._get( + path_template("/organization/groups/{group_id}", group_id=group_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Group, + ) + + async def update( + self, + group_id: str, + *, + name: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GroupUpdateResponse: + """ + Updates a group's information. + + Args: + name: New display name for the group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return await self._post( + path_template("/organization/groups/{group_id}", group_id=group_id), + body=await async_maybe_transform({"name": name}, group_update_params.GroupUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=GroupUpdateResponse, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Group, AsyncNextCursorPage[Group]]: + """ + Lists all groups in the organization. + + Args: + after: A cursor for use in pagination. `after` is a group ID that defines your place in + the list. For instance, if you make a list request and receive 100 objects, + ending with group_abc, your subsequent call can include `after=group_abc` in + order to fetch the next page of the list. + + limit: A limit on the number of groups to be returned. Limit can range between 0 and + 1000, and the default is 100. + + order: Specifies the sort order of the returned groups. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/groups", + page=AsyncNextCursorPage[Group], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + group_list_params.GroupListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=Group, + ) + + async def delete( + self, + group_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GroupDeleteResponse: + """ + Deletes a group from the organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return await self._delete( + path_template("/organization/groups/{group_id}", group_id=group_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=GroupDeleteResponse, + ) + + +class GroupsWithRawResponse: + def __init__(self, groups: Groups) -> None: + self._groups = groups + + self.create = _legacy_response.to_raw_response_wrapper( + groups.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + groups.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + groups.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + groups.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + groups.delete, + ) + + @cached_property + def users(self) -> UsersWithRawResponse: + return UsersWithRawResponse(self._groups.users) + + @cached_property + def roles(self) -> RolesWithRawResponse: + return RolesWithRawResponse(self._groups.roles) + + +class AsyncGroupsWithRawResponse: + def __init__(self, groups: AsyncGroups) -> None: + self._groups = groups + + self.create = _legacy_response.async_to_raw_response_wrapper( + groups.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + groups.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + groups.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + groups.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + groups.delete, + ) + + @cached_property + def users(self) -> AsyncUsersWithRawResponse: + return AsyncUsersWithRawResponse(self._groups.users) + + @cached_property + def roles(self) -> AsyncRolesWithRawResponse: + return AsyncRolesWithRawResponse(self._groups.roles) + + +class GroupsWithStreamingResponse: + def __init__(self, groups: Groups) -> None: + self._groups = groups + + self.create = to_streamed_response_wrapper( + groups.create, + ) + self.retrieve = to_streamed_response_wrapper( + groups.retrieve, + ) + self.update = to_streamed_response_wrapper( + groups.update, + ) + self.list = to_streamed_response_wrapper( + groups.list, + ) + self.delete = to_streamed_response_wrapper( + groups.delete, + ) + + @cached_property + def users(self) -> UsersWithStreamingResponse: + return UsersWithStreamingResponse(self._groups.users) + + @cached_property + def roles(self) -> RolesWithStreamingResponse: + return RolesWithStreamingResponse(self._groups.roles) + + +class AsyncGroupsWithStreamingResponse: + def __init__(self, groups: AsyncGroups) -> None: + self._groups = groups + + self.create = async_to_streamed_response_wrapper( + groups.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + groups.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + groups.update, + ) + self.list = async_to_streamed_response_wrapper( + groups.list, + ) + self.delete = async_to_streamed_response_wrapper( + groups.delete, + ) + + @cached_property + def users(self) -> AsyncUsersWithStreamingResponse: + return AsyncUsersWithStreamingResponse(self._groups.users) + + @cached_property + def roles(self) -> AsyncRolesWithStreamingResponse: + return AsyncRolesWithStreamingResponse(self._groups.roles) diff --git a/src/openai/resources/admin/organization/groups/roles.py b/src/openai/resources/admin/organization/groups/roles.py new file mode 100644 index 0000000000..c4405c487c --- /dev/null +++ b/src/openai/resources/admin/organization/groups/roles.py @@ -0,0 +1,491 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncNextCursorPage, AsyncNextCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization.groups import role_list_params, role_create_params +from .....types.admin.organization.groups.role_list_response import RoleListResponse +from .....types.admin.organization.groups.role_create_response import RoleCreateResponse +from .....types.admin.organization.groups.role_delete_response import RoleDeleteResponse +from .....types.admin.organization.groups.role_retrieve_response import RoleRetrieveResponse + +__all__ = ["Roles", "AsyncRoles"] + + +class Roles(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return RolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return RolesWithStreamingResponse(self) + + def create( + self, + group_id: str, + *, + role_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleCreateResponse: + """ + Assigns an organization role to a group within the organization. + + Args: + role_id: Identifier of the role to assign. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._post( + path_template("/organization/groups/{group_id}/roles", group_id=group_id), + body=maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleCreateResponse, + ) + + def retrieve( + self, + role_id: str, + *, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleRetrieveResponse: + """ + Retrieves an organization role assigned to a group. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._get( + path_template("/organization/groups/{group_id}/roles/{role_id}", group_id=group_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleRetrieveResponse, + ) + + def list( + self, + group_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncNextCursorPage[RoleListResponse]: + """ + Lists the organization roles assigned to a group within the organization. + + Args: + after: Cursor for pagination. Provide the value from the previous response's `next` + field to continue listing organization roles. + + limit: A limit on the number of organization role assignments to return. + + order: Sort order for the returned organization roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._get_api_list( + path_template("/organization/groups/{group_id}/roles", group_id=group_id), + page=SyncNextCursorPage[RoleListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=RoleListResponse, + ) + + def delete( + self, + role_id: str, + *, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Unassigns an organization role from a group within the organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._delete( + path_template("/organization/groups/{group_id}/roles/{role_id}", group_id=group_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class AsyncRoles(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncRolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncRolesWithStreamingResponse(self) + + async def create( + self, + group_id: str, + *, + role_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleCreateResponse: + """ + Assigns an organization role to a group within the organization. + + Args: + role_id: Identifier of the role to assign. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return await self._post( + path_template("/organization/groups/{group_id}/roles", group_id=group_id), + body=await async_maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleCreateResponse, + ) + + async def retrieve( + self, + role_id: str, + *, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleRetrieveResponse: + """ + Retrieves an organization role assigned to a group. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._get( + path_template("/organization/groups/{group_id}/roles/{role_id}", group_id=group_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleRetrieveResponse, + ) + + def list( + self, + group_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]: + """ + Lists the organization roles assigned to a group within the organization. + + Args: + after: Cursor for pagination. Provide the value from the previous response's `next` + field to continue listing organization roles. + + limit: A limit on the number of organization role assignments to return. + + order: Sort order for the returned organization roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._get_api_list( + path_template("/organization/groups/{group_id}/roles", group_id=group_id), + page=AsyncNextCursorPage[RoleListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=RoleListResponse, + ) + + async def delete( + self, + role_id: str, + *, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Unassigns an organization role from a group within the organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._delete( + path_template("/organization/groups/{group_id}/roles/{role_id}", group_id=group_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class RolesWithRawResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = _legacy_response.to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + roles.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithRawResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = _legacy_response.async_to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + roles.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + roles.delete, + ) + + +class RolesWithStreamingResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = to_streamed_response_wrapper( + roles.retrieve, + ) + self.list = to_streamed_response_wrapper( + roles.list, + ) + self.delete = to_streamed_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithStreamingResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = async_to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + roles.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + roles.list, + ) + self.delete = async_to_streamed_response_wrapper( + roles.delete, + ) diff --git a/src/openai/resources/admin/organization/groups/users.py b/src/openai/resources/admin/organization/groups/users.py new file mode 100644 index 0000000000..e0ac71afd9 --- /dev/null +++ b/src/openai/resources/admin/organization/groups/users.py @@ -0,0 +1,493 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncNextCursorPage, AsyncNextCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization.groups import user_list_params, user_create_params +from .....types.admin.organization.groups.user_create_response import UserCreateResponse +from .....types.admin.organization.groups.user_delete_response import UserDeleteResponse +from .....types.admin.organization.groups.user_retrieve_response import UserRetrieveResponse +from .....types.admin.organization.groups.organization_group_user import OrganizationGroupUser + +__all__ = ["Users", "AsyncUsers"] + + +class Users(SyncAPIResource): + @cached_property + def with_raw_response(self) -> UsersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return UsersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> UsersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return UsersWithStreamingResponse(self) + + def create( + self, + group_id: str, + *, + user_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UserCreateResponse: + """ + Adds a user to a group. + + Args: + user_id: Identifier of the user to add to the group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._post( + path_template("/organization/groups/{group_id}/users", group_id=group_id), + body=maybe_transform({"user_id": user_id}, user_create_params.UserCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=UserCreateResponse, + ) + + def retrieve( + self, + user_id: str, + *, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UserRetrieveResponse: + """ + Retrieves a user in a group. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._get( + path_template("/organization/groups/{group_id}/users/{user_id}", group_id=group_id, user_id=user_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=UserRetrieveResponse, + ) + + def list( + self, + group_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncNextCursorPage[OrganizationGroupUser]: + """ + Lists the users assigned to a group. + + Args: + after: A cursor for use in pagination. Provide the ID of the last user from the + previous list response to retrieve the next page. + + limit: A limit on the number of users to be returned. Limit can range between 0 and + 1000, and the default is 100. + + order: Specifies the sort order of users in the list. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._get_api_list( + path_template("/organization/groups/{group_id}/users", group_id=group_id), + page=SyncNextCursorPage[OrganizationGroupUser], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + user_list_params.UserListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=OrganizationGroupUser, + ) + + def delete( + self, + user_id: str, + *, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UserDeleteResponse: + """ + Removes a user from a group. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._delete( + path_template("/organization/groups/{group_id}/users/{user_id}", group_id=group_id, user_id=user_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=UserDeleteResponse, + ) + + +class AsyncUsers(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncUsersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncUsersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncUsersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncUsersWithStreamingResponse(self) + + async def create( + self, + group_id: str, + *, + user_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UserCreateResponse: + """ + Adds a user to a group. + + Args: + user_id: Identifier of the user to add to the group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return await self._post( + path_template("/organization/groups/{group_id}/users", group_id=group_id), + body=await async_maybe_transform({"user_id": user_id}, user_create_params.UserCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=UserCreateResponse, + ) + + async def retrieve( + self, + user_id: str, + *, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UserRetrieveResponse: + """ + Retrieves a user in a group. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return await self._get( + path_template("/organization/groups/{group_id}/users/{user_id}", group_id=group_id, user_id=user_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=UserRetrieveResponse, + ) + + def list( + self, + group_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[OrganizationGroupUser, AsyncNextCursorPage[OrganizationGroupUser]]: + """ + Lists the users assigned to a group. + + Args: + after: A cursor for use in pagination. Provide the ID of the last user from the + previous list response to retrieve the next page. + + limit: A limit on the number of users to be returned. Limit can range between 0 and + 1000, and the default is 100. + + order: Specifies the sort order of users in the list. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._get_api_list( + path_template("/organization/groups/{group_id}/users", group_id=group_id), + page=AsyncNextCursorPage[OrganizationGroupUser], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + user_list_params.UserListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=OrganizationGroupUser, + ) + + async def delete( + self, + user_id: str, + *, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UserDeleteResponse: + """ + Removes a user from a group. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return await self._delete( + path_template("/organization/groups/{group_id}/users/{user_id}", group_id=group_id, user_id=user_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=UserDeleteResponse, + ) + + +class UsersWithRawResponse: + def __init__(self, users: Users) -> None: + self._users = users + + self.create = _legacy_response.to_raw_response_wrapper( + users.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + users.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + users.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + users.delete, + ) + + +class AsyncUsersWithRawResponse: + def __init__(self, users: AsyncUsers) -> None: + self._users = users + + self.create = _legacy_response.async_to_raw_response_wrapper( + users.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + users.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + users.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + users.delete, + ) + + +class UsersWithStreamingResponse: + def __init__(self, users: Users) -> None: + self._users = users + + self.create = to_streamed_response_wrapper( + users.create, + ) + self.retrieve = to_streamed_response_wrapper( + users.retrieve, + ) + self.list = to_streamed_response_wrapper( + users.list, + ) + self.delete = to_streamed_response_wrapper( + users.delete, + ) + + +class AsyncUsersWithStreamingResponse: + def __init__(self, users: AsyncUsers) -> None: + self._users = users + + self.create = async_to_streamed_response_wrapper( + users.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + users.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + users.list, + ) + self.delete = async_to_streamed_response_wrapper( + users.delete, + ) diff --git a/src/openai/resources/admin/organization/invites.py b/src/openai/resources/admin/organization/invites.py new file mode 100644 index 0000000000..b9db8c622c --- /dev/null +++ b/src/openai/resources/admin/organization/invites.py @@ -0,0 +1,502 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.admin.organization import invite_list_params, invite_create_params +from ....types.admin.organization.invite import Invite +from ....types.admin.organization.invite_delete_response import InviteDeleteResponse + +__all__ = ["Invites", "AsyncInvites"] + + +class Invites(SyncAPIResource): + @cached_property + def with_raw_response(self) -> InvitesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return InvitesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> InvitesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return InvitesWithStreamingResponse(self) + + def create( + self, + *, + email: str, + role: Literal["reader", "owner"], + projects: Iterable[invite_create_params.Project] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Invite: + """Create an invite for a user to the organization. + + The invite must be accepted by + the user before they have access to the organization. + + Args: + email: Send an email to this address + + role: `owner` or `reader` + + projects: An array of projects to which membership is granted at the same time the org + invite is accepted. If omitted, the user will be invited to the default project + for compatibility with legacy behavior. If empty list is passed, the user will + not be invited to any projects, including the default one. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/organization/invites", + body=maybe_transform( + { + "email": email, + "role": role, + "projects": projects, + }, + invite_create_params.InviteCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Invite, + ) + + def retrieve( + self, + invite_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Invite: + """ + Retrieves an invite. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not invite_id: + raise ValueError(f"Expected a non-empty value for `invite_id` but received {invite_id!r}") + return self._get( + path_template("/organization/invites/{invite_id}", invite_id=invite_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Invite, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[Invite]: + """ + Returns a list of invites in the organization. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/invites", + page=SyncConversationCursorPage[Invite], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + }, + invite_list_params.InviteListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=Invite, + ) + + def delete( + self, + invite_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> InviteDeleteResponse: + """Delete an invite. + + If the invite has already been accepted, it cannot be deleted. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not invite_id: + raise ValueError(f"Expected a non-empty value for `invite_id` but received {invite_id!r}") + return self._delete( + path_template("/organization/invites/{invite_id}", invite_id=invite_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=InviteDeleteResponse, + ) + + +class AsyncInvites(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncInvitesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncInvitesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncInvitesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncInvitesWithStreamingResponse(self) + + async def create( + self, + *, + email: str, + role: Literal["reader", "owner"], + projects: Iterable[invite_create_params.Project] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Invite: + """Create an invite for a user to the organization. + + The invite must be accepted by + the user before they have access to the organization. + + Args: + email: Send an email to this address + + role: `owner` or `reader` + + projects: An array of projects to which membership is granted at the same time the org + invite is accepted. If omitted, the user will be invited to the default project + for compatibility with legacy behavior. If empty list is passed, the user will + not be invited to any projects, including the default one. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/organization/invites", + body=await async_maybe_transform( + { + "email": email, + "role": role, + "projects": projects, + }, + invite_create_params.InviteCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Invite, + ) + + async def retrieve( + self, + invite_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Invite: + """ + Retrieves an invite. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not invite_id: + raise ValueError(f"Expected a non-empty value for `invite_id` but received {invite_id!r}") + return await self._get( + path_template("/organization/invites/{invite_id}", invite_id=invite_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Invite, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Invite, AsyncConversationCursorPage[Invite]]: + """ + Returns a list of invites in the organization. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/invites", + page=AsyncConversationCursorPage[Invite], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + }, + invite_list_params.InviteListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=Invite, + ) + + async def delete( + self, + invite_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> InviteDeleteResponse: + """Delete an invite. + + If the invite has already been accepted, it cannot be deleted. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not invite_id: + raise ValueError(f"Expected a non-empty value for `invite_id` but received {invite_id!r}") + return await self._delete( + path_template("/organization/invites/{invite_id}", invite_id=invite_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=InviteDeleteResponse, + ) + + +class InvitesWithRawResponse: + def __init__(self, invites: Invites) -> None: + self._invites = invites + + self.create = _legacy_response.to_raw_response_wrapper( + invites.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + invites.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + invites.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + invites.delete, + ) + + +class AsyncInvitesWithRawResponse: + def __init__(self, invites: AsyncInvites) -> None: + self._invites = invites + + self.create = _legacy_response.async_to_raw_response_wrapper( + invites.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + invites.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + invites.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + invites.delete, + ) + + +class InvitesWithStreamingResponse: + def __init__(self, invites: Invites) -> None: + self._invites = invites + + self.create = to_streamed_response_wrapper( + invites.create, + ) + self.retrieve = to_streamed_response_wrapper( + invites.retrieve, + ) + self.list = to_streamed_response_wrapper( + invites.list, + ) + self.delete = to_streamed_response_wrapper( + invites.delete, + ) + + +class AsyncInvitesWithStreamingResponse: + def __init__(self, invites: AsyncInvites) -> None: + self._invites = invites + + self.create = async_to_streamed_response_wrapper( + invites.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + invites.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + invites.list, + ) + self.delete = async_to_streamed_response_wrapper( + invites.delete, + ) diff --git a/src/openai/resources/admin/organization/organization.py b/src/openai/resources/admin/organization/organization.py new file mode 100644 index 0000000000..62bba207cb --- /dev/null +++ b/src/openai/resources/admin/organization/organization.py @@ -0,0 +1,428 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from .usage import ( + Usage, + AsyncUsage, + UsageWithRawResponse, + AsyncUsageWithRawResponse, + UsageWithStreamingResponse, + AsyncUsageWithStreamingResponse, +) +from .invites import ( + Invites, + AsyncInvites, + InvitesWithRawResponse, + AsyncInvitesWithRawResponse, + InvitesWithStreamingResponse, + AsyncInvitesWithStreamingResponse, +) +from ...._compat import cached_property +from .audit_logs import ( + AuditLogs, + AsyncAuditLogs, + AuditLogsWithRawResponse, + AsyncAuditLogsWithRawResponse, + AuditLogsWithStreamingResponse, + AsyncAuditLogsWithStreamingResponse, +) +from .users.users import ( + Users, + AsyncUsers, + UsersWithRawResponse, + AsyncUsersWithRawResponse, + UsersWithStreamingResponse, + AsyncUsersWithStreamingResponse, +) +from ...._resource import SyncAPIResource, AsyncAPIResource +from .certificates import ( + Certificates, + AsyncCertificates, + CertificatesWithRawResponse, + AsyncCertificatesWithRawResponse, + CertificatesWithStreamingResponse, + AsyncCertificatesWithStreamingResponse, +) +from .spend_alerts import ( + SpendAlerts, + AsyncSpendAlerts, + SpendAlertsWithRawResponse, + AsyncSpendAlertsWithRawResponse, + SpendAlertsWithStreamingResponse, + AsyncSpendAlertsWithStreamingResponse, +) +from .groups.groups import ( + Groups, + AsyncGroups, + GroupsWithRawResponse, + AsyncGroupsWithRawResponse, + GroupsWithStreamingResponse, + AsyncGroupsWithStreamingResponse, +) +from .admin_api_keys import ( + AdminAPIKeys, + AsyncAdminAPIKeys, + AdminAPIKeysWithRawResponse, + AsyncAdminAPIKeysWithRawResponse, + AdminAPIKeysWithStreamingResponse, + AsyncAdminAPIKeysWithStreamingResponse, +) +from .data_retention import ( + DataRetention, + AsyncDataRetention, + DataRetentionWithRawResponse, + AsyncDataRetentionWithRawResponse, + DataRetentionWithStreamingResponse, + AsyncDataRetentionWithStreamingResponse, +) +from .projects.projects import ( + Projects, + AsyncProjects, + ProjectsWithRawResponse, + AsyncProjectsWithRawResponse, + ProjectsWithStreamingResponse, + AsyncProjectsWithStreamingResponse, +) + +__all__ = ["Organization", "AsyncOrganization"] + + +class Organization(SyncAPIResource): + @cached_property + def audit_logs(self) -> AuditLogs: + """List user actions and configuration changes within this organization.""" + return AuditLogs(self._client) + + @cached_property + def admin_api_keys(self) -> AdminAPIKeys: + return AdminAPIKeys(self._client) + + @cached_property + def usage(self) -> Usage: + return Usage(self._client) + + @cached_property + def invites(self) -> Invites: + return Invites(self._client) + + @cached_property + def users(self) -> Users: + return Users(self._client) + + @cached_property + def groups(self) -> Groups: + return Groups(self._client) + + @cached_property + def roles(self) -> Roles: + return Roles(self._client) + + @cached_property + def data_retention(self) -> DataRetention: + return DataRetention(self._client) + + @cached_property + def spend_alerts(self) -> SpendAlerts: + return SpendAlerts(self._client) + + @cached_property + def certificates(self) -> Certificates: + return Certificates(self._client) + + @cached_property + def projects(self) -> Projects: + return Projects(self._client) + + @cached_property + def with_raw_response(self) -> OrganizationWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return OrganizationWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> OrganizationWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return OrganizationWithStreamingResponse(self) + + +class AsyncOrganization(AsyncAPIResource): + @cached_property + def audit_logs(self) -> AsyncAuditLogs: + """List user actions and configuration changes within this organization.""" + return AsyncAuditLogs(self._client) + + @cached_property + def admin_api_keys(self) -> AsyncAdminAPIKeys: + return AsyncAdminAPIKeys(self._client) + + @cached_property + def usage(self) -> AsyncUsage: + return AsyncUsage(self._client) + + @cached_property + def invites(self) -> AsyncInvites: + return AsyncInvites(self._client) + + @cached_property + def users(self) -> AsyncUsers: + return AsyncUsers(self._client) + + @cached_property + def groups(self) -> AsyncGroups: + return AsyncGroups(self._client) + + @cached_property + def roles(self) -> AsyncRoles: + return AsyncRoles(self._client) + + @cached_property + def data_retention(self) -> AsyncDataRetention: + return AsyncDataRetention(self._client) + + @cached_property + def spend_alerts(self) -> AsyncSpendAlerts: + return AsyncSpendAlerts(self._client) + + @cached_property + def certificates(self) -> AsyncCertificates: + return AsyncCertificates(self._client) + + @cached_property + def projects(self) -> AsyncProjects: + return AsyncProjects(self._client) + + @cached_property + def with_raw_response(self) -> AsyncOrganizationWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncOrganizationWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncOrganizationWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncOrganizationWithStreamingResponse(self) + + +class OrganizationWithRawResponse: + def __init__(self, organization: Organization) -> None: + self._organization = organization + + @cached_property + def audit_logs(self) -> AuditLogsWithRawResponse: + """List user actions and configuration changes within this organization.""" + return AuditLogsWithRawResponse(self._organization.audit_logs) + + @cached_property + def admin_api_keys(self) -> AdminAPIKeysWithRawResponse: + return AdminAPIKeysWithRawResponse(self._organization.admin_api_keys) + + @cached_property + def usage(self) -> UsageWithRawResponse: + return UsageWithRawResponse(self._organization.usage) + + @cached_property + def invites(self) -> InvitesWithRawResponse: + return InvitesWithRawResponse(self._organization.invites) + + @cached_property + def users(self) -> UsersWithRawResponse: + return UsersWithRawResponse(self._organization.users) + + @cached_property + def groups(self) -> GroupsWithRawResponse: + return GroupsWithRawResponse(self._organization.groups) + + @cached_property + def roles(self) -> RolesWithRawResponse: + return RolesWithRawResponse(self._organization.roles) + + @cached_property + def data_retention(self) -> DataRetentionWithRawResponse: + return DataRetentionWithRawResponse(self._organization.data_retention) + + @cached_property + def spend_alerts(self) -> SpendAlertsWithRawResponse: + return SpendAlertsWithRawResponse(self._organization.spend_alerts) + + @cached_property + def certificates(self) -> CertificatesWithRawResponse: + return CertificatesWithRawResponse(self._organization.certificates) + + @cached_property + def projects(self) -> ProjectsWithRawResponse: + return ProjectsWithRawResponse(self._organization.projects) + + +class AsyncOrganizationWithRawResponse: + def __init__(self, organization: AsyncOrganization) -> None: + self._organization = organization + + @cached_property + def audit_logs(self) -> AsyncAuditLogsWithRawResponse: + """List user actions and configuration changes within this organization.""" + return AsyncAuditLogsWithRawResponse(self._organization.audit_logs) + + @cached_property + def admin_api_keys(self) -> AsyncAdminAPIKeysWithRawResponse: + return AsyncAdminAPIKeysWithRawResponse(self._organization.admin_api_keys) + + @cached_property + def usage(self) -> AsyncUsageWithRawResponse: + return AsyncUsageWithRawResponse(self._organization.usage) + + @cached_property + def invites(self) -> AsyncInvitesWithRawResponse: + return AsyncInvitesWithRawResponse(self._organization.invites) + + @cached_property + def users(self) -> AsyncUsersWithRawResponse: + return AsyncUsersWithRawResponse(self._organization.users) + + @cached_property + def groups(self) -> AsyncGroupsWithRawResponse: + return AsyncGroupsWithRawResponse(self._organization.groups) + + @cached_property + def roles(self) -> AsyncRolesWithRawResponse: + return AsyncRolesWithRawResponse(self._organization.roles) + + @cached_property + def data_retention(self) -> AsyncDataRetentionWithRawResponse: + return AsyncDataRetentionWithRawResponse(self._organization.data_retention) + + @cached_property + def spend_alerts(self) -> AsyncSpendAlertsWithRawResponse: + return AsyncSpendAlertsWithRawResponse(self._organization.spend_alerts) + + @cached_property + def certificates(self) -> AsyncCertificatesWithRawResponse: + return AsyncCertificatesWithRawResponse(self._organization.certificates) + + @cached_property + def projects(self) -> AsyncProjectsWithRawResponse: + return AsyncProjectsWithRawResponse(self._organization.projects) + + +class OrganizationWithStreamingResponse: + def __init__(self, organization: Organization) -> None: + self._organization = organization + + @cached_property + def audit_logs(self) -> AuditLogsWithStreamingResponse: + """List user actions and configuration changes within this organization.""" + return AuditLogsWithStreamingResponse(self._organization.audit_logs) + + @cached_property + def admin_api_keys(self) -> AdminAPIKeysWithStreamingResponse: + return AdminAPIKeysWithStreamingResponse(self._organization.admin_api_keys) + + @cached_property + def usage(self) -> UsageWithStreamingResponse: + return UsageWithStreamingResponse(self._organization.usage) + + @cached_property + def invites(self) -> InvitesWithStreamingResponse: + return InvitesWithStreamingResponse(self._organization.invites) + + @cached_property + def users(self) -> UsersWithStreamingResponse: + return UsersWithStreamingResponse(self._organization.users) + + @cached_property + def groups(self) -> GroupsWithStreamingResponse: + return GroupsWithStreamingResponse(self._organization.groups) + + @cached_property + def roles(self) -> RolesWithStreamingResponse: + return RolesWithStreamingResponse(self._organization.roles) + + @cached_property + def data_retention(self) -> DataRetentionWithStreamingResponse: + return DataRetentionWithStreamingResponse(self._organization.data_retention) + + @cached_property + def spend_alerts(self) -> SpendAlertsWithStreamingResponse: + return SpendAlertsWithStreamingResponse(self._organization.spend_alerts) + + @cached_property + def certificates(self) -> CertificatesWithStreamingResponse: + return CertificatesWithStreamingResponse(self._organization.certificates) + + @cached_property + def projects(self) -> ProjectsWithStreamingResponse: + return ProjectsWithStreamingResponse(self._organization.projects) + + +class AsyncOrganizationWithStreamingResponse: + def __init__(self, organization: AsyncOrganization) -> None: + self._organization = organization + + @cached_property + def audit_logs(self) -> AsyncAuditLogsWithStreamingResponse: + """List user actions and configuration changes within this organization.""" + return AsyncAuditLogsWithStreamingResponse(self._organization.audit_logs) + + @cached_property + def admin_api_keys(self) -> AsyncAdminAPIKeysWithStreamingResponse: + return AsyncAdminAPIKeysWithStreamingResponse(self._organization.admin_api_keys) + + @cached_property + def usage(self) -> AsyncUsageWithStreamingResponse: + return AsyncUsageWithStreamingResponse(self._organization.usage) + + @cached_property + def invites(self) -> AsyncInvitesWithStreamingResponse: + return AsyncInvitesWithStreamingResponse(self._organization.invites) + + @cached_property + def users(self) -> AsyncUsersWithStreamingResponse: + return AsyncUsersWithStreamingResponse(self._organization.users) + + @cached_property + def groups(self) -> AsyncGroupsWithStreamingResponse: + return AsyncGroupsWithStreamingResponse(self._organization.groups) + + @cached_property + def roles(self) -> AsyncRolesWithStreamingResponse: + return AsyncRolesWithStreamingResponse(self._organization.roles) + + @cached_property + def data_retention(self) -> AsyncDataRetentionWithStreamingResponse: + return AsyncDataRetentionWithStreamingResponse(self._organization.data_retention) + + @cached_property + def spend_alerts(self) -> AsyncSpendAlertsWithStreamingResponse: + return AsyncSpendAlertsWithStreamingResponse(self._organization.spend_alerts) + + @cached_property + def certificates(self) -> AsyncCertificatesWithStreamingResponse: + return AsyncCertificatesWithStreamingResponse(self._organization.certificates) + + @cached_property + def projects(self) -> AsyncProjectsWithStreamingResponse: + return AsyncProjectsWithStreamingResponse(self._organization.projects) diff --git a/src/openai/resources/admin/organization/projects/__init__.py b/src/openai/resources/admin/organization/projects/__init__.py new file mode 100644 index 0000000000..3bc97170fd --- /dev/null +++ b/src/openai/resources/admin/organization/projects/__init__.py @@ -0,0 +1,173 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from .users import ( + Users, + AsyncUsers, + UsersWithRawResponse, + AsyncUsersWithRawResponse, + UsersWithStreamingResponse, + AsyncUsersWithStreamingResponse, +) +from .groups import ( + Groups, + AsyncGroups, + GroupsWithRawResponse, + AsyncGroupsWithRawResponse, + GroupsWithStreamingResponse, + AsyncGroupsWithStreamingResponse, +) +from .api_keys import ( + APIKeys, + AsyncAPIKeys, + APIKeysWithRawResponse, + AsyncAPIKeysWithRawResponse, + APIKeysWithStreamingResponse, + AsyncAPIKeysWithStreamingResponse, +) +from .projects import ( + Projects, + AsyncProjects, + ProjectsWithRawResponse, + AsyncProjectsWithRawResponse, + ProjectsWithStreamingResponse, + AsyncProjectsWithStreamingResponse, +) +from .rate_limits import ( + RateLimits, + AsyncRateLimits, + RateLimitsWithRawResponse, + AsyncRateLimitsWithRawResponse, + RateLimitsWithStreamingResponse, + AsyncRateLimitsWithStreamingResponse, +) +from .certificates import ( + Certificates, + AsyncCertificates, + CertificatesWithRawResponse, + AsyncCertificatesWithRawResponse, + CertificatesWithStreamingResponse, + AsyncCertificatesWithStreamingResponse, +) +from .spend_alerts import ( + SpendAlerts, + AsyncSpendAlerts, + SpendAlertsWithRawResponse, + AsyncSpendAlertsWithRawResponse, + SpendAlertsWithStreamingResponse, + AsyncSpendAlertsWithStreamingResponse, +) +from .data_retention import ( + DataRetention, + AsyncDataRetention, + DataRetentionWithRawResponse, + AsyncDataRetentionWithRawResponse, + DataRetentionWithStreamingResponse, + AsyncDataRetentionWithStreamingResponse, +) +from .service_accounts import ( + ServiceAccounts, + AsyncServiceAccounts, + ServiceAccountsWithRawResponse, + AsyncServiceAccountsWithRawResponse, + ServiceAccountsWithStreamingResponse, + AsyncServiceAccountsWithStreamingResponse, +) +from .model_permissions import ( + ModelPermissions, + AsyncModelPermissions, + ModelPermissionsWithRawResponse, + AsyncModelPermissionsWithRawResponse, + ModelPermissionsWithStreamingResponse, + AsyncModelPermissionsWithStreamingResponse, +) +from .hosted_tool_permissions import ( + HostedToolPermissions, + AsyncHostedToolPermissions, + HostedToolPermissionsWithRawResponse, + AsyncHostedToolPermissionsWithRawResponse, + HostedToolPermissionsWithStreamingResponse, + AsyncHostedToolPermissionsWithStreamingResponse, +) + +__all__ = [ + "Users", + "AsyncUsers", + "UsersWithRawResponse", + "AsyncUsersWithRawResponse", + "UsersWithStreamingResponse", + "AsyncUsersWithStreamingResponse", + "ServiceAccounts", + "AsyncServiceAccounts", + "ServiceAccountsWithRawResponse", + "AsyncServiceAccountsWithRawResponse", + "ServiceAccountsWithStreamingResponse", + "AsyncServiceAccountsWithStreamingResponse", + "APIKeys", + "AsyncAPIKeys", + "APIKeysWithRawResponse", + "AsyncAPIKeysWithRawResponse", + "APIKeysWithStreamingResponse", + "AsyncAPIKeysWithStreamingResponse", + "RateLimits", + "AsyncRateLimits", + "RateLimitsWithRawResponse", + "AsyncRateLimitsWithRawResponse", + "RateLimitsWithStreamingResponse", + "AsyncRateLimitsWithStreamingResponse", + "ModelPermissions", + "AsyncModelPermissions", + "ModelPermissionsWithRawResponse", + "AsyncModelPermissionsWithRawResponse", + "ModelPermissionsWithStreamingResponse", + "AsyncModelPermissionsWithStreamingResponse", + "HostedToolPermissions", + "AsyncHostedToolPermissions", + "HostedToolPermissionsWithRawResponse", + "AsyncHostedToolPermissionsWithRawResponse", + "HostedToolPermissionsWithStreamingResponse", + "AsyncHostedToolPermissionsWithStreamingResponse", + "Groups", + "AsyncGroups", + "GroupsWithRawResponse", + "AsyncGroupsWithRawResponse", + "GroupsWithStreamingResponse", + "AsyncGroupsWithStreamingResponse", + "Roles", + "AsyncRoles", + "RolesWithRawResponse", + "AsyncRolesWithRawResponse", + "RolesWithStreamingResponse", + "AsyncRolesWithStreamingResponse", + "DataRetention", + "AsyncDataRetention", + "DataRetentionWithRawResponse", + "AsyncDataRetentionWithRawResponse", + "DataRetentionWithStreamingResponse", + "AsyncDataRetentionWithStreamingResponse", + "SpendAlerts", + "AsyncSpendAlerts", + "SpendAlertsWithRawResponse", + "AsyncSpendAlertsWithRawResponse", + "SpendAlertsWithStreamingResponse", + "AsyncSpendAlertsWithStreamingResponse", + "Certificates", + "AsyncCertificates", + "CertificatesWithRawResponse", + "AsyncCertificatesWithRawResponse", + "CertificatesWithStreamingResponse", + "AsyncCertificatesWithStreamingResponse", + "Projects", + "AsyncProjects", + "ProjectsWithRawResponse", + "AsyncProjectsWithRawResponse", + "ProjectsWithStreamingResponse", + "AsyncProjectsWithStreamingResponse", +] diff --git a/src/openai/resources/admin/organization/projects/api_keys.py b/src/openai/resources/admin/organization/projects/api_keys.py new file mode 100644 index 0000000000..1517d213e0 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/api_keys.py @@ -0,0 +1,413 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization.projects import api_key_list_params +from .....types.admin.organization.projects.project_api_key import ProjectAPIKey +from .....types.admin.organization.projects.api_key_delete_response import APIKeyDeleteResponse + +__all__ = ["APIKeys", "AsyncAPIKeys"] + + +class APIKeys(SyncAPIResource): + @cached_property + def with_raw_response(self) -> APIKeysWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return APIKeysWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> APIKeysWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return APIKeysWithStreamingResponse(self) + + def retrieve( + self, + api_key_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectAPIKey: + """ + Retrieves an API key in the project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return self._get( + path_template( + "/organization/projects/{project_id}/api_keys/{api_key_id}", + project_id=project_id, + api_key_id=api_key_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectAPIKey, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[ProjectAPIKey]: + """ + Returns a list of API keys in the project. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/api_keys", project_id=project_id), + page=SyncConversationCursorPage[ProjectAPIKey], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + }, + api_key_list_params.APIKeyListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectAPIKey, + ) + + def delete( + self, + api_key_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> APIKeyDeleteResponse: + """ + Deletes an API key from the project. + + Returns confirmation of the key deletion, or an error if the key belonged to a + service account. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return self._delete( + path_template( + "/organization/projects/{project_id}/api_keys/{api_key_id}", + project_id=project_id, + api_key_id=api_key_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=APIKeyDeleteResponse, + ) + + +class AsyncAPIKeys(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncAPIKeysWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncAPIKeysWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAPIKeysWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncAPIKeysWithStreamingResponse(self) + + async def retrieve( + self, + api_key_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectAPIKey: + """ + Retrieves an API key in the project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return await self._get( + path_template( + "/organization/projects/{project_id}/api_keys/{api_key_id}", + project_id=project_id, + api_key_id=api_key_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectAPIKey, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ProjectAPIKey, AsyncConversationCursorPage[ProjectAPIKey]]: + """ + Returns a list of API keys in the project. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/api_keys", project_id=project_id), + page=AsyncConversationCursorPage[ProjectAPIKey], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + }, + api_key_list_params.APIKeyListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectAPIKey, + ) + + async def delete( + self, + api_key_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> APIKeyDeleteResponse: + """ + Deletes an API key from the project. + + Returns confirmation of the key deletion, or an error if the key belonged to a + service account. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return await self._delete( + path_template( + "/organization/projects/{project_id}/api_keys/{api_key_id}", + project_id=project_id, + api_key_id=api_key_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=APIKeyDeleteResponse, + ) + + +class APIKeysWithRawResponse: + def __init__(self, api_keys: APIKeys) -> None: + self._api_keys = api_keys + + self.retrieve = _legacy_response.to_raw_response_wrapper( + api_keys.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + api_keys.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + api_keys.delete, + ) + + +class AsyncAPIKeysWithRawResponse: + def __init__(self, api_keys: AsyncAPIKeys) -> None: + self._api_keys = api_keys + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + api_keys.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + api_keys.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + api_keys.delete, + ) + + +class APIKeysWithStreamingResponse: + def __init__(self, api_keys: APIKeys) -> None: + self._api_keys = api_keys + + self.retrieve = to_streamed_response_wrapper( + api_keys.retrieve, + ) + self.list = to_streamed_response_wrapper( + api_keys.list, + ) + self.delete = to_streamed_response_wrapper( + api_keys.delete, + ) + + +class AsyncAPIKeysWithStreamingResponse: + def __init__(self, api_keys: AsyncAPIKeys) -> None: + self._api_keys = api_keys + + self.retrieve = async_to_streamed_response_wrapper( + api_keys.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + api_keys.list, + ) + self.delete = async_to_streamed_response_wrapper( + api_keys.delete, + ) diff --git a/src/openai/resources/admin/organization/projects/certificates.py b/src/openai/resources/admin/organization/projects/certificates.py new file mode 100644 index 0000000000..ec449d570a --- /dev/null +++ b/src/openai/resources/admin/organization/projects/certificates.py @@ -0,0 +1,428 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ....._utils import path_template, maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncPage, AsyncPage, SyncConversationCursorPage, AsyncConversationCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization.projects import ( + certificate_list_params, + certificate_activate_params, + certificate_deactivate_params, +) +from .....types.admin.organization.projects.certificate_list_response import CertificateListResponse +from .....types.admin.organization.projects.certificate_activate_response import CertificateActivateResponse +from .....types.admin.organization.projects.certificate_deactivate_response import CertificateDeactivateResponse + +__all__ = ["Certificates", "AsyncCertificates"] + + +class Certificates(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CertificatesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return CertificatesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CertificatesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return CertificatesWithStreamingResponse(self) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[CertificateListResponse]: + """ + List certificates for this project. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending + order and `desc` for descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/certificates", project_id=project_id), + page=SyncConversationCursorPage[CertificateListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + certificate_list_params.CertificateListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=CertificateListResponse, + ) + + def activate( + self, + project_id: str, + *, + certificate_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncPage[CertificateActivateResponse]: + """ + Activate certificates at the project level. + + You can atomically and idempotently activate up to 10 certificates at a time. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/certificates/activate", project_id=project_id), + page=SyncPage[CertificateActivateResponse], + body=maybe_transform( + {"certificate_ids": certificate_ids}, certificate_activate_params.CertificateActivateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + model=CertificateActivateResponse, + method="post", + ) + + def deactivate( + self, + project_id: str, + *, + certificate_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncPage[CertificateDeactivateResponse]: + """Deactivate certificates at the project level. + + You can atomically and + idempotently deactivate up to 10 certificates at a time. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/certificates/deactivate", project_id=project_id), + page=SyncPage[CertificateDeactivateResponse], + body=maybe_transform( + {"certificate_ids": certificate_ids}, certificate_deactivate_params.CertificateDeactivateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + model=CertificateDeactivateResponse, + method="post", + ) + + +class AsyncCertificates(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCertificatesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncCertificatesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCertificatesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncCertificatesWithStreamingResponse(self) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CertificateListResponse, AsyncConversationCursorPage[CertificateListResponse]]: + """ + List certificates for this project. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending + order and `desc` for descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/certificates", project_id=project_id), + page=AsyncConversationCursorPage[CertificateListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + certificate_list_params.CertificateListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=CertificateListResponse, + ) + + def activate( + self, + project_id: str, + *, + certificate_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CertificateActivateResponse, AsyncPage[CertificateActivateResponse]]: + """ + Activate certificates at the project level. + + You can atomically and idempotently activate up to 10 certificates at a time. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/certificates/activate", project_id=project_id), + page=AsyncPage[CertificateActivateResponse], + body=maybe_transform( + {"certificate_ids": certificate_ids}, certificate_activate_params.CertificateActivateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + model=CertificateActivateResponse, + method="post", + ) + + def deactivate( + self, + project_id: str, + *, + certificate_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CertificateDeactivateResponse, AsyncPage[CertificateDeactivateResponse]]: + """Deactivate certificates at the project level. + + You can atomically and + idempotently deactivate up to 10 certificates at a time. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/certificates/deactivate", project_id=project_id), + page=AsyncPage[CertificateDeactivateResponse], + body=maybe_transform( + {"certificate_ids": certificate_ids}, certificate_deactivate_params.CertificateDeactivateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + model=CertificateDeactivateResponse, + method="post", + ) + + +class CertificatesWithRawResponse: + def __init__(self, certificates: Certificates) -> None: + self._certificates = certificates + + self.list = _legacy_response.to_raw_response_wrapper( + certificates.list, + ) + self.activate = _legacy_response.to_raw_response_wrapper( + certificates.activate, + ) + self.deactivate = _legacy_response.to_raw_response_wrapper( + certificates.deactivate, + ) + + +class AsyncCertificatesWithRawResponse: + def __init__(self, certificates: AsyncCertificates) -> None: + self._certificates = certificates + + self.list = _legacy_response.async_to_raw_response_wrapper( + certificates.list, + ) + self.activate = _legacy_response.async_to_raw_response_wrapper( + certificates.activate, + ) + self.deactivate = _legacy_response.async_to_raw_response_wrapper( + certificates.deactivate, + ) + + +class CertificatesWithStreamingResponse: + def __init__(self, certificates: Certificates) -> None: + self._certificates = certificates + + self.list = to_streamed_response_wrapper( + certificates.list, + ) + self.activate = to_streamed_response_wrapper( + certificates.activate, + ) + self.deactivate = to_streamed_response_wrapper( + certificates.deactivate, + ) + + +class AsyncCertificatesWithStreamingResponse: + def __init__(self, certificates: AsyncCertificates) -> None: + self._certificates = certificates + + self.list = async_to_streamed_response_wrapper( + certificates.list, + ) + self.activate = async_to_streamed_response_wrapper( + certificates.activate, + ) + self.deactivate = async_to_streamed_response_wrapper( + certificates.deactivate, + ) diff --git a/src/openai/resources/admin/organization/projects/data_retention.py b/src/openai/resources/admin/organization/projects/data_retention.py new file mode 100644 index 0000000000..2d0df3e7e2 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/data_retention.py @@ -0,0 +1,283 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Query, Headers, NotGiven, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....._base_client import make_request_options +from .....types.admin.organization.projects import data_retention_update_params +from .....types.admin.organization.projects.project_data_retention import ProjectDataRetention + +__all__ = ["DataRetention", "AsyncDataRetention"] + + +class DataRetention(SyncAPIResource): + @cached_property + def with_raw_response(self) -> DataRetentionWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return DataRetentionWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> DataRetentionWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return DataRetentionWithStreamingResponse(self) + + def retrieve( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectDataRetention: + """ + Retrieves project data retention controls. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + path_template("/organization/projects/{project_id}/data_retention", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectDataRetention, + ) + + def update( + self, + project_id: str, + *, + retention_type: Literal[ + "organization_default", + "none", + "zero_data_retention", + "modified_abuse_monitoring", + "enhanced_zero_data_retention", + "enhanced_modified_abuse_monitoring", + ], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectDataRetention: + """ + Updates project data retention controls. + + Args: + retention_type: The desired project data retention type. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + path_template("/organization/projects/{project_id}/data_retention", project_id=project_id), + body=maybe_transform( + {"retention_type": retention_type}, data_retention_update_params.DataRetentionUpdateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectDataRetention, + ) + + +class AsyncDataRetention(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncDataRetentionWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncDataRetentionWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncDataRetentionWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncDataRetentionWithStreamingResponse(self) + + async def retrieve( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectDataRetention: + """ + Retrieves project data retention controls. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + path_template("/organization/projects/{project_id}/data_retention", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectDataRetention, + ) + + async def update( + self, + project_id: str, + *, + retention_type: Literal[ + "organization_default", + "none", + "zero_data_retention", + "modified_abuse_monitoring", + "enhanced_zero_data_retention", + "enhanced_modified_abuse_monitoring", + ], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectDataRetention: + """ + Updates project data retention controls. + + Args: + retention_type: The desired project data retention type. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + path_template("/organization/projects/{project_id}/data_retention", project_id=project_id), + body=await async_maybe_transform( + {"retention_type": retention_type}, data_retention_update_params.DataRetentionUpdateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectDataRetention, + ) + + +class DataRetentionWithRawResponse: + def __init__(self, data_retention: DataRetention) -> None: + self._data_retention = data_retention + + self.retrieve = _legacy_response.to_raw_response_wrapper( + data_retention.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + data_retention.update, + ) + + +class AsyncDataRetentionWithRawResponse: + def __init__(self, data_retention: AsyncDataRetention) -> None: + self._data_retention = data_retention + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + data_retention.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + data_retention.update, + ) + + +class DataRetentionWithStreamingResponse: + def __init__(self, data_retention: DataRetention) -> None: + self._data_retention = data_retention + + self.retrieve = to_streamed_response_wrapper( + data_retention.retrieve, + ) + self.update = to_streamed_response_wrapper( + data_retention.update, + ) + + +class AsyncDataRetentionWithStreamingResponse: + def __init__(self, data_retention: AsyncDataRetention) -> None: + self._data_retention = data_retention + + self.retrieve = async_to_streamed_response_wrapper( + data_retention.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + data_retention.update, + ) diff --git a/src/openai/resources/admin/organization/projects/groups/__init__.py b/src/openai/resources/admin/organization/projects/groups/__init__.py new file mode 100644 index 0000000000..4feb66239f --- /dev/null +++ b/src/openai/resources/admin/organization/projects/groups/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from .groups import ( + Groups, + AsyncGroups, + GroupsWithRawResponse, + AsyncGroupsWithRawResponse, + GroupsWithStreamingResponse, + AsyncGroupsWithStreamingResponse, +) + +__all__ = [ + "Roles", + "AsyncRoles", + "RolesWithRawResponse", + "AsyncRolesWithRawResponse", + "RolesWithStreamingResponse", + "AsyncRolesWithStreamingResponse", + "Groups", + "AsyncGroups", + "GroupsWithRawResponse", + "AsyncGroupsWithRawResponse", + "GroupsWithStreamingResponse", + "AsyncGroupsWithStreamingResponse", +] diff --git a/src/openai/resources/admin/organization/projects/groups/groups.py b/src/openai/resources/admin/organization/projects/groups/groups.py new file mode 100644 index 0000000000..a7ef36fd64 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/groups/groups.py @@ -0,0 +1,557 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ...... import _legacy_response +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ......_utils import path_template, maybe_transform, async_maybe_transform +from ......_compat import cached_property +from ......_resource import SyncAPIResource, AsyncAPIResource +from ......_response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ......pagination import SyncNextCursorPage, AsyncNextCursorPage +from ......_base_client import AsyncPaginator, make_request_options +from ......types.admin.organization.projects import group_list_params, group_create_params, group_retrieve_params +from ......types.admin.organization.projects.project_group import ProjectGroup +from ......types.admin.organization.projects.group_delete_response import GroupDeleteResponse + +__all__ = ["Groups", "AsyncGroups"] + + +class Groups(SyncAPIResource): + @cached_property + def roles(self) -> Roles: + return Roles(self._client) + + @cached_property + def with_raw_response(self) -> GroupsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return GroupsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> GroupsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return GroupsWithStreamingResponse(self) + + def create( + self, + project_id: str, + *, + group_id: str, + role: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectGroup: + """ + Grants a group access to a project. + + Args: + group_id: Identifier of the group to add to the project. + + role: Identifier of the project role to grant to the group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + path_template("/organization/projects/{project_id}/groups", project_id=project_id), + body=maybe_transform( + { + "group_id": group_id, + "role": role, + }, + group_create_params.GroupCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectGroup, + ) + + def retrieve( + self, + group_id: str, + *, + project_id: str, + group_type: Literal["group", "tenant_group"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectGroup: + """ + Retrieves a project's group. + + Args: + group_type: The type of group to retrieve. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._get( + path_template( + "/organization/projects/{project_id}/groups/{group_id}", project_id=project_id, group_id=group_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"group_type": group_type}, group_retrieve_params.GroupRetrieveParams), + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectGroup, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncNextCursorPage[ProjectGroup]: + """ + Lists the groups that have access to a project. + + Args: + after: Cursor for pagination. Provide the ID of the last group from the previous + response to fetch the next page. + + limit: A limit on the number of project groups to return. Defaults to 20. + + order: Sort order for the returned groups. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/groups", project_id=project_id), + page=SyncNextCursorPage[ProjectGroup], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + group_list_params.GroupListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectGroup, + ) + + def delete( + self, + group_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GroupDeleteResponse: + """ + Revokes a group's access to a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._delete( + path_template( + "/organization/projects/{project_id}/groups/{group_id}", project_id=project_id, group_id=group_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=GroupDeleteResponse, + ) + + +class AsyncGroups(AsyncAPIResource): + @cached_property + def roles(self) -> AsyncRoles: + return AsyncRoles(self._client) + + @cached_property + def with_raw_response(self) -> AsyncGroupsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncGroupsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncGroupsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncGroupsWithStreamingResponse(self) + + async def create( + self, + project_id: str, + *, + group_id: str, + role: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectGroup: + """ + Grants a group access to a project. + + Args: + group_id: Identifier of the group to add to the project. + + role: Identifier of the project role to grant to the group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + path_template("/organization/projects/{project_id}/groups", project_id=project_id), + body=await async_maybe_transform( + { + "group_id": group_id, + "role": role, + }, + group_create_params.GroupCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectGroup, + ) + + async def retrieve( + self, + group_id: str, + *, + project_id: str, + group_type: Literal["group", "tenant_group"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectGroup: + """ + Retrieves a project's group. + + Args: + group_type: The type of group to retrieve. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return await self._get( + path_template( + "/organization/projects/{project_id}/groups/{group_id}", project_id=project_id, group_id=group_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"group_type": group_type}, group_retrieve_params.GroupRetrieveParams + ), + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectGroup, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ProjectGroup, AsyncNextCursorPage[ProjectGroup]]: + """ + Lists the groups that have access to a project. + + Args: + after: Cursor for pagination. Provide the ID of the last group from the previous + response to fetch the next page. + + limit: A limit on the number of project groups to return. Defaults to 20. + + order: Sort order for the returned groups. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/groups", project_id=project_id), + page=AsyncNextCursorPage[ProjectGroup], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + group_list_params.GroupListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectGroup, + ) + + async def delete( + self, + group_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GroupDeleteResponse: + """ + Revokes a group's access to a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return await self._delete( + path_template( + "/organization/projects/{project_id}/groups/{group_id}", project_id=project_id, group_id=group_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=GroupDeleteResponse, + ) + + +class GroupsWithRawResponse: + def __init__(self, groups: Groups) -> None: + self._groups = groups + + self.create = _legacy_response.to_raw_response_wrapper( + groups.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + groups.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + groups.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + groups.delete, + ) + + @cached_property + def roles(self) -> RolesWithRawResponse: + return RolesWithRawResponse(self._groups.roles) + + +class AsyncGroupsWithRawResponse: + def __init__(self, groups: AsyncGroups) -> None: + self._groups = groups + + self.create = _legacy_response.async_to_raw_response_wrapper( + groups.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + groups.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + groups.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + groups.delete, + ) + + @cached_property + def roles(self) -> AsyncRolesWithRawResponse: + return AsyncRolesWithRawResponse(self._groups.roles) + + +class GroupsWithStreamingResponse: + def __init__(self, groups: Groups) -> None: + self._groups = groups + + self.create = to_streamed_response_wrapper( + groups.create, + ) + self.retrieve = to_streamed_response_wrapper( + groups.retrieve, + ) + self.list = to_streamed_response_wrapper( + groups.list, + ) + self.delete = to_streamed_response_wrapper( + groups.delete, + ) + + @cached_property + def roles(self) -> RolesWithStreamingResponse: + return RolesWithStreamingResponse(self._groups.roles) + + +class AsyncGroupsWithStreamingResponse: + def __init__(self, groups: AsyncGroups) -> None: + self._groups = groups + + self.create = async_to_streamed_response_wrapper( + groups.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + groups.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + groups.list, + ) + self.delete = async_to_streamed_response_wrapper( + groups.delete, + ) + + @cached_property + def roles(self) -> AsyncRolesWithStreamingResponse: + return AsyncRolesWithStreamingResponse(self._groups.roles) diff --git a/src/openai/resources/admin/organization/projects/groups/roles.py b/src/openai/resources/admin/organization/projects/groups/roles.py new file mode 100644 index 0000000000..5961d05e70 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/groups/roles.py @@ -0,0 +1,535 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ...... import _legacy_response +from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ......_utils import path_template, maybe_transform, async_maybe_transform +from ......_compat import cached_property +from ......_resource import SyncAPIResource, AsyncAPIResource +from ......_response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ......pagination import SyncNextCursorPage, AsyncNextCursorPage +from ......_base_client import AsyncPaginator, make_request_options +from ......types.admin.organization.projects.groups import role_list_params, role_create_params +from ......types.admin.organization.projects.groups.role_list_response import RoleListResponse +from ......types.admin.organization.projects.groups.role_create_response import RoleCreateResponse +from ......types.admin.organization.projects.groups.role_delete_response import RoleDeleteResponse +from ......types.admin.organization.projects.groups.role_retrieve_response import RoleRetrieveResponse + +__all__ = ["Roles", "AsyncRoles"] + + +class Roles(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return RolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return RolesWithStreamingResponse(self) + + def create( + self, + group_id: str, + *, + project_id: str, + role_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleCreateResponse: + """ + Assigns a project role to a group within a project. + + Args: + role_id: Identifier of the role to assign. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._post( + path_template("/projects/{project_id}/groups/{group_id}/roles", project_id=project_id, group_id=group_id), + body=maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleCreateResponse, + ) + + def retrieve( + self, + role_id: str, + *, + project_id: str, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleRetrieveResponse: + """ + Retrieves a project role assigned to a group. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._get( + path_template( + "/projects/{project_id}/groups/{group_id}/roles/{role_id}", + project_id=project_id, + group_id=group_id, + role_id=role_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleRetrieveResponse, + ) + + def list( + self, + group_id: str, + *, + project_id: str, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncNextCursorPage[RoleListResponse]: + """ + Lists the project roles assigned to a group within a project. + + Args: + after: Cursor for pagination. Provide the value from the previous response's `next` + field to continue listing project roles. + + limit: A limit on the number of project role assignments to return. + + order: Sort order for the returned project roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._get_api_list( + path_template("/projects/{project_id}/groups/{group_id}/roles", project_id=project_id, group_id=group_id), + page=SyncNextCursorPage[RoleListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=RoleListResponse, + ) + + def delete( + self, + role_id: str, + *, + project_id: str, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Unassigns a project role from a group within a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._delete( + path_template( + "/projects/{project_id}/groups/{group_id}/roles/{role_id}", + project_id=project_id, + group_id=group_id, + role_id=role_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class AsyncRoles(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncRolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncRolesWithStreamingResponse(self) + + async def create( + self, + group_id: str, + *, + project_id: str, + role_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleCreateResponse: + """ + Assigns a project role to a group within a project. + + Args: + role_id: Identifier of the role to assign. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return await self._post( + path_template("/projects/{project_id}/groups/{group_id}/roles", project_id=project_id, group_id=group_id), + body=await async_maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleCreateResponse, + ) + + async def retrieve( + self, + role_id: str, + *, + project_id: str, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleRetrieveResponse: + """ + Retrieves a project role assigned to a group. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._get( + path_template( + "/projects/{project_id}/groups/{group_id}/roles/{role_id}", + project_id=project_id, + group_id=group_id, + role_id=role_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleRetrieveResponse, + ) + + def list( + self, + group_id: str, + *, + project_id: str, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]: + """ + Lists the project roles assigned to a group within a project. + + Args: + after: Cursor for pagination. Provide the value from the previous response's `next` + field to continue listing project roles. + + limit: A limit on the number of project role assignments to return. + + order: Sort order for the returned project roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._get_api_list( + path_template("/projects/{project_id}/groups/{group_id}/roles", project_id=project_id, group_id=group_id), + page=AsyncNextCursorPage[RoleListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=RoleListResponse, + ) + + async def delete( + self, + role_id: str, + *, + project_id: str, + group_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Unassigns a project role from a group within a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._delete( + path_template( + "/projects/{project_id}/groups/{group_id}/roles/{role_id}", + project_id=project_id, + group_id=group_id, + role_id=role_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class RolesWithRawResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = _legacy_response.to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + roles.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithRawResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = _legacy_response.async_to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + roles.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + roles.delete, + ) + + +class RolesWithStreamingResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = to_streamed_response_wrapper( + roles.retrieve, + ) + self.list = to_streamed_response_wrapper( + roles.list, + ) + self.delete = to_streamed_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithStreamingResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = async_to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + roles.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + roles.list, + ) + self.delete = async_to_streamed_response_wrapper( + roles.delete, + ) diff --git a/src/openai/resources/admin/organization/projects/hosted_tool_permissions.py b/src/openai/resources/admin/organization/projects/hosted_tool_permissions.py new file mode 100644 index 0000000000..27bcee5f5e --- /dev/null +++ b/src/openai/resources/admin/organization/projects/hosted_tool_permissions.py @@ -0,0 +1,307 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....._base_client import make_request_options +from .....types.admin.organization.projects import hosted_tool_permission_update_params +from .....types.admin.organization.projects.project_hosted_tool_permissions import ProjectHostedToolPermissions + +__all__ = ["HostedToolPermissions", "AsyncHostedToolPermissions"] + + +class HostedToolPermissions(SyncAPIResource): + @cached_property + def with_raw_response(self) -> HostedToolPermissionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return HostedToolPermissionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> HostedToolPermissionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return HostedToolPermissionsWithStreamingResponse(self) + + def retrieve( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectHostedToolPermissions: + """ + Returns hosted tool permissions for a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + path_template("/organization/projects/{project_id}/hosted_tool_permissions", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectHostedToolPermissions, + ) + + def update( + self, + project_id: str, + *, + code_interpreter: Optional[hosted_tool_permission_update_params.CodeInterpreter] | Omit = omit, + file_search: Optional[hosted_tool_permission_update_params.FileSearch] | Omit = omit, + image_generation: Optional[hosted_tool_permission_update_params.ImageGeneration] | Omit = omit, + mcp: Optional[hosted_tool_permission_update_params.Mcp] | Omit = omit, + web_search: Optional[hosted_tool_permission_update_params.WebSearch] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectHostedToolPermissions: + """ + Updates hosted tool permissions for a project. + + Args: + code_interpreter: The code interpreter permission update. + + file_search: The file search permission update. + + image_generation: The image generation permission update. + + mcp: The MCP permission update. + + web_search: The web search permission update. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + path_template("/organization/projects/{project_id}/hosted_tool_permissions", project_id=project_id), + body=maybe_transform( + { + "code_interpreter": code_interpreter, + "file_search": file_search, + "image_generation": image_generation, + "mcp": mcp, + "web_search": web_search, + }, + hosted_tool_permission_update_params.HostedToolPermissionUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectHostedToolPermissions, + ) + + +class AsyncHostedToolPermissions(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncHostedToolPermissionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncHostedToolPermissionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncHostedToolPermissionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncHostedToolPermissionsWithStreamingResponse(self) + + async def retrieve( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectHostedToolPermissions: + """ + Returns hosted tool permissions for a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + path_template("/organization/projects/{project_id}/hosted_tool_permissions", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectHostedToolPermissions, + ) + + async def update( + self, + project_id: str, + *, + code_interpreter: Optional[hosted_tool_permission_update_params.CodeInterpreter] | Omit = omit, + file_search: Optional[hosted_tool_permission_update_params.FileSearch] | Omit = omit, + image_generation: Optional[hosted_tool_permission_update_params.ImageGeneration] | Omit = omit, + mcp: Optional[hosted_tool_permission_update_params.Mcp] | Omit = omit, + web_search: Optional[hosted_tool_permission_update_params.WebSearch] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectHostedToolPermissions: + """ + Updates hosted tool permissions for a project. + + Args: + code_interpreter: The code interpreter permission update. + + file_search: The file search permission update. + + image_generation: The image generation permission update. + + mcp: The MCP permission update. + + web_search: The web search permission update. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + path_template("/organization/projects/{project_id}/hosted_tool_permissions", project_id=project_id), + body=await async_maybe_transform( + { + "code_interpreter": code_interpreter, + "file_search": file_search, + "image_generation": image_generation, + "mcp": mcp, + "web_search": web_search, + }, + hosted_tool_permission_update_params.HostedToolPermissionUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectHostedToolPermissions, + ) + + +class HostedToolPermissionsWithRawResponse: + def __init__(self, hosted_tool_permissions: HostedToolPermissions) -> None: + self._hosted_tool_permissions = hosted_tool_permissions + + self.retrieve = _legacy_response.to_raw_response_wrapper( + hosted_tool_permissions.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + hosted_tool_permissions.update, + ) + + +class AsyncHostedToolPermissionsWithRawResponse: + def __init__(self, hosted_tool_permissions: AsyncHostedToolPermissions) -> None: + self._hosted_tool_permissions = hosted_tool_permissions + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + hosted_tool_permissions.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + hosted_tool_permissions.update, + ) + + +class HostedToolPermissionsWithStreamingResponse: + def __init__(self, hosted_tool_permissions: HostedToolPermissions) -> None: + self._hosted_tool_permissions = hosted_tool_permissions + + self.retrieve = to_streamed_response_wrapper( + hosted_tool_permissions.retrieve, + ) + self.update = to_streamed_response_wrapper( + hosted_tool_permissions.update, + ) + + +class AsyncHostedToolPermissionsWithStreamingResponse: + def __init__(self, hosted_tool_permissions: AsyncHostedToolPermissions) -> None: + self._hosted_tool_permissions = hosted_tool_permissions + + self.retrieve = async_to_streamed_response_wrapper( + hosted_tool_permissions.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + hosted_tool_permissions.update, + ) diff --git a/src/openai/resources/admin/organization/projects/model_permissions.py b/src/openai/resources/admin/organization/projects/model_permissions.py new file mode 100644 index 0000000000..157b4a4f72 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/model_permissions.py @@ -0,0 +1,370 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Query, Headers, NotGiven, SequenceNotStr, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....._base_client import make_request_options +from .....types.admin.organization.projects import model_permission_update_params +from .....types.admin.organization.projects.project_model_permissions import ProjectModelPermissions +from .....types.admin.organization.projects.project_model_permissions_deleted import ProjectModelPermissionsDeleted + +__all__ = ["ModelPermissions", "AsyncModelPermissions"] + + +class ModelPermissions(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ModelPermissionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ModelPermissionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ModelPermissionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ModelPermissionsWithStreamingResponse(self) + + def retrieve( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectModelPermissions: + """ + Returns model permissions for a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectModelPermissions, + ) + + def update( + self, + project_id: str, + *, + mode: Literal["allow_list", "deny_list"], + model_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectModelPermissions: + """ + Updates model permissions for a project. + + Args: + mode: The model permissions mode to apply. + + model_ids: The model IDs included in this permissions policy. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id), + body=maybe_transform( + { + "mode": mode, + "model_ids": model_ids, + }, + model_permission_update_params.ModelPermissionUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectModelPermissions, + ) + + def delete( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectModelPermissionsDeleted: + """ + Deletes model permissions for a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._delete( + path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectModelPermissionsDeleted, + ) + + +class AsyncModelPermissions(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncModelPermissionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncModelPermissionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncModelPermissionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncModelPermissionsWithStreamingResponse(self) + + async def retrieve( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectModelPermissions: + """ + Returns model permissions for a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectModelPermissions, + ) + + async def update( + self, + project_id: str, + *, + mode: Literal["allow_list", "deny_list"], + model_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectModelPermissions: + """ + Updates model permissions for a project. + + Args: + mode: The model permissions mode to apply. + + model_ids: The model IDs included in this permissions policy. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id), + body=await async_maybe_transform( + { + "mode": mode, + "model_ids": model_ids, + }, + model_permission_update_params.ModelPermissionUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectModelPermissions, + ) + + async def delete( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectModelPermissionsDeleted: + """ + Deletes model permissions for a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._delete( + path_template("/organization/projects/{project_id}/model_permissions", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectModelPermissionsDeleted, + ) + + +class ModelPermissionsWithRawResponse: + def __init__(self, model_permissions: ModelPermissions) -> None: + self._model_permissions = model_permissions + + self.retrieve = _legacy_response.to_raw_response_wrapper( + model_permissions.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + model_permissions.update, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + model_permissions.delete, + ) + + +class AsyncModelPermissionsWithRawResponse: + def __init__(self, model_permissions: AsyncModelPermissions) -> None: + self._model_permissions = model_permissions + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + model_permissions.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + model_permissions.update, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + model_permissions.delete, + ) + + +class ModelPermissionsWithStreamingResponse: + def __init__(self, model_permissions: ModelPermissions) -> None: + self._model_permissions = model_permissions + + self.retrieve = to_streamed_response_wrapper( + model_permissions.retrieve, + ) + self.update = to_streamed_response_wrapper( + model_permissions.update, + ) + self.delete = to_streamed_response_wrapper( + model_permissions.delete, + ) + + +class AsyncModelPermissionsWithStreamingResponse: + def __init__(self, model_permissions: AsyncModelPermissions) -> None: + self._model_permissions = model_permissions + + self.retrieve = async_to_streamed_response_wrapper( + model_permissions.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + model_permissions.update, + ) + self.delete = async_to_streamed_response_wrapper( + model_permissions.delete, + ) diff --git a/src/openai/resources/admin/organization/projects/projects.py b/src/openai/resources/admin/organization/projects/projects.py new file mode 100644 index 0000000000..6ffd4c0f85 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/projects.py @@ -0,0 +1,986 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ..... import _legacy_response +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from .api_keys import ( + APIKeys, + AsyncAPIKeys, + APIKeysWithRawResponse, + AsyncAPIKeysWithRawResponse, + APIKeysWithStreamingResponse, + AsyncAPIKeysWithStreamingResponse, +) +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from .rate_limits import ( + RateLimits, + AsyncRateLimits, + RateLimitsWithRawResponse, + AsyncRateLimitsWithRawResponse, + RateLimitsWithStreamingResponse, + AsyncRateLimitsWithStreamingResponse, +) +from .users.users import ( + Users, + AsyncUsers, + UsersWithRawResponse, + AsyncUsersWithRawResponse, + UsersWithStreamingResponse, + AsyncUsersWithStreamingResponse, +) +from .certificates import ( + Certificates, + AsyncCertificates, + CertificatesWithRawResponse, + AsyncCertificatesWithRawResponse, + CertificatesWithStreamingResponse, + AsyncCertificatesWithStreamingResponse, +) +from .spend_alerts import ( + SpendAlerts, + AsyncSpendAlerts, + SpendAlertsWithRawResponse, + AsyncSpendAlertsWithRawResponse, + SpendAlertsWithStreamingResponse, + AsyncSpendAlertsWithStreamingResponse, +) +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .groups.groups import ( + Groups, + AsyncGroups, + GroupsWithRawResponse, + AsyncGroupsWithRawResponse, + GroupsWithStreamingResponse, + AsyncGroupsWithStreamingResponse, +) +from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from .data_retention import ( + DataRetention, + AsyncDataRetention, + DataRetentionWithRawResponse, + AsyncDataRetentionWithRawResponse, + DataRetentionWithStreamingResponse, + AsyncDataRetentionWithStreamingResponse, +) +from ....._base_client import AsyncPaginator, make_request_options +from .service_accounts import ( + ServiceAccounts, + AsyncServiceAccounts, + ServiceAccountsWithRawResponse, + AsyncServiceAccountsWithRawResponse, + ServiceAccountsWithStreamingResponse, + AsyncServiceAccountsWithStreamingResponse, +) +from .model_permissions import ( + ModelPermissions, + AsyncModelPermissions, + ModelPermissionsWithRawResponse, + AsyncModelPermissionsWithRawResponse, + ModelPermissionsWithStreamingResponse, + AsyncModelPermissionsWithStreamingResponse, +) +from .hosted_tool_permissions import ( + HostedToolPermissions, + AsyncHostedToolPermissions, + HostedToolPermissionsWithRawResponse, + AsyncHostedToolPermissionsWithRawResponse, + HostedToolPermissionsWithStreamingResponse, + AsyncHostedToolPermissionsWithStreamingResponse, +) +from .....types.admin.organization import project_list_params, project_create_params, project_update_params +from .....types.admin.organization.project import Project + +__all__ = ["Projects", "AsyncProjects"] + + +class Projects(SyncAPIResource): + @cached_property + def users(self) -> Users: + return Users(self._client) + + @cached_property + def service_accounts(self) -> ServiceAccounts: + return ServiceAccounts(self._client) + + @cached_property + def api_keys(self) -> APIKeys: + return APIKeys(self._client) + + @cached_property + def rate_limits(self) -> RateLimits: + return RateLimits(self._client) + + @cached_property + def model_permissions(self) -> ModelPermissions: + return ModelPermissions(self._client) + + @cached_property + def hosted_tool_permissions(self) -> HostedToolPermissions: + return HostedToolPermissions(self._client) + + @cached_property + def groups(self) -> Groups: + return Groups(self._client) + + @cached_property + def roles(self) -> Roles: + return Roles(self._client) + + @cached_property + def data_retention(self) -> DataRetention: + return DataRetention(self._client) + + @cached_property + def spend_alerts(self) -> SpendAlerts: + return SpendAlerts(self._client) + + @cached_property + def certificates(self) -> Certificates: + return Certificates(self._client) + + @cached_property + def with_raw_response(self) -> ProjectsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ProjectsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ProjectsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ProjectsWithStreamingResponse(self) + + def create( + self, + *, + name: str, + external_key_id: Optional[str] | Omit = omit, + geography: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Project: + """Create a new project in the organization. + + Projects can be created and archived, + but cannot be deleted. + + Args: + name: The friendly name of the project, this name appears in reports. + + external_key_id: External key ID to associate with the project. + + geography: Create the project with the specified data residency region. Your organization + must have access to Data residency functionality in order to use. See + [data residency controls](https://platform.openai.com/docs/guides/your-data#data-residency-controls) + to review the functionality and limitations of setting this field. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/organization/projects", + body=maybe_transform( + { + "name": name, + "external_key_id": external_key_id, + "geography": geography, + }, + project_create_params.ProjectCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Project, + ) + + def retrieve( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Project: + """ + Retrieves a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + path_template("/organization/projects/{project_id}", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Project, + ) + + def update( + self, + project_id: str, + *, + external_key_id: Optional[str] | Omit = omit, + geography: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Project: + """ + Modifies a project in the organization. + + Args: + external_key_id: External key ID to associate with the project. + + geography: Geography for the project. + + name: The updated name of the project, this name appears in reports. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + path_template("/organization/projects/{project_id}", project_id=project_id), + body=maybe_transform( + { + "external_key_id": external_key_id, + "geography": geography, + "name": name, + }, + project_update_params.ProjectUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Project, + ) + + def list( + self, + *, + after: str | Omit = omit, + include_archived: bool | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[Project]: + """Returns a list of projects. + + Args: + after: A cursor for use in pagination. + + `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + include_archived: If `true` returns all projects including those that have been `archived`. + Archived projects are not included by default. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/projects", + page=SyncConversationCursorPage[Project], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "include_archived": include_archived, + "limit": limit, + }, + project_list_params.ProjectListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=Project, + ) + + def archive( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Project: + """Archives a project in the organization. + + Archived projects cannot be used or + updated. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + path_template("/organization/projects/{project_id}/archive", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Project, + ) + + +class AsyncProjects(AsyncAPIResource): + @cached_property + def users(self) -> AsyncUsers: + return AsyncUsers(self._client) + + @cached_property + def service_accounts(self) -> AsyncServiceAccounts: + return AsyncServiceAccounts(self._client) + + @cached_property + def api_keys(self) -> AsyncAPIKeys: + return AsyncAPIKeys(self._client) + + @cached_property + def rate_limits(self) -> AsyncRateLimits: + return AsyncRateLimits(self._client) + + @cached_property + def model_permissions(self) -> AsyncModelPermissions: + return AsyncModelPermissions(self._client) + + @cached_property + def hosted_tool_permissions(self) -> AsyncHostedToolPermissions: + return AsyncHostedToolPermissions(self._client) + + @cached_property + def groups(self) -> AsyncGroups: + return AsyncGroups(self._client) + + @cached_property + def roles(self) -> AsyncRoles: + return AsyncRoles(self._client) + + @cached_property + def data_retention(self) -> AsyncDataRetention: + return AsyncDataRetention(self._client) + + @cached_property + def spend_alerts(self) -> AsyncSpendAlerts: + return AsyncSpendAlerts(self._client) + + @cached_property + def certificates(self) -> AsyncCertificates: + return AsyncCertificates(self._client) + + @cached_property + def with_raw_response(self) -> AsyncProjectsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncProjectsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncProjectsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncProjectsWithStreamingResponse(self) + + async def create( + self, + *, + name: str, + external_key_id: Optional[str] | Omit = omit, + geography: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Project: + """Create a new project in the organization. + + Projects can be created and archived, + but cannot be deleted. + + Args: + name: The friendly name of the project, this name appears in reports. + + external_key_id: External key ID to associate with the project. + + geography: Create the project with the specified data residency region. Your organization + must have access to Data residency functionality in order to use. See + [data residency controls](https://platform.openai.com/docs/guides/your-data#data-residency-controls) + to review the functionality and limitations of setting this field. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/organization/projects", + body=await async_maybe_transform( + { + "name": name, + "external_key_id": external_key_id, + "geography": geography, + }, + project_create_params.ProjectCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Project, + ) + + async def retrieve( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Project: + """ + Retrieves a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + path_template("/organization/projects/{project_id}", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Project, + ) + + async def update( + self, + project_id: str, + *, + external_key_id: Optional[str] | Omit = omit, + geography: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Project: + """ + Modifies a project in the organization. + + Args: + external_key_id: External key ID to associate with the project. + + geography: Geography for the project. + + name: The updated name of the project, this name appears in reports. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + path_template("/organization/projects/{project_id}", project_id=project_id), + body=await async_maybe_transform( + { + "external_key_id": external_key_id, + "geography": geography, + "name": name, + }, + project_update_params.ProjectUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Project, + ) + + def list( + self, + *, + after: str | Omit = omit, + include_archived: bool | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Project, AsyncConversationCursorPage[Project]]: + """Returns a list of projects. + + Args: + after: A cursor for use in pagination. + + `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + include_archived: If `true` returns all projects including those that have been `archived`. + Archived projects are not included by default. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/projects", + page=AsyncConversationCursorPage[Project], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "include_archived": include_archived, + "limit": limit, + }, + project_list_params.ProjectListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=Project, + ) + + async def archive( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Project: + """Archives a project in the organization. + + Archived projects cannot be used or + updated. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + path_template("/organization/projects/{project_id}/archive", project_id=project_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Project, + ) + + +class ProjectsWithRawResponse: + def __init__(self, projects: Projects) -> None: + self._projects = projects + + self.create = _legacy_response.to_raw_response_wrapper( + projects.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + projects.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + projects.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + projects.list, + ) + self.archive = _legacy_response.to_raw_response_wrapper( + projects.archive, + ) + + @cached_property + def users(self) -> UsersWithRawResponse: + return UsersWithRawResponse(self._projects.users) + + @cached_property + def service_accounts(self) -> ServiceAccountsWithRawResponse: + return ServiceAccountsWithRawResponse(self._projects.service_accounts) + + @cached_property + def api_keys(self) -> APIKeysWithRawResponse: + return APIKeysWithRawResponse(self._projects.api_keys) + + @cached_property + def rate_limits(self) -> RateLimitsWithRawResponse: + return RateLimitsWithRawResponse(self._projects.rate_limits) + + @cached_property + def model_permissions(self) -> ModelPermissionsWithRawResponse: + return ModelPermissionsWithRawResponse(self._projects.model_permissions) + + @cached_property + def hosted_tool_permissions(self) -> HostedToolPermissionsWithRawResponse: + return HostedToolPermissionsWithRawResponse(self._projects.hosted_tool_permissions) + + @cached_property + def groups(self) -> GroupsWithRawResponse: + return GroupsWithRawResponse(self._projects.groups) + + @cached_property + def roles(self) -> RolesWithRawResponse: + return RolesWithRawResponse(self._projects.roles) + + @cached_property + def data_retention(self) -> DataRetentionWithRawResponse: + return DataRetentionWithRawResponse(self._projects.data_retention) + + @cached_property + def spend_alerts(self) -> SpendAlertsWithRawResponse: + return SpendAlertsWithRawResponse(self._projects.spend_alerts) + + @cached_property + def certificates(self) -> CertificatesWithRawResponse: + return CertificatesWithRawResponse(self._projects.certificates) + + +class AsyncProjectsWithRawResponse: + def __init__(self, projects: AsyncProjects) -> None: + self._projects = projects + + self.create = _legacy_response.async_to_raw_response_wrapper( + projects.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + projects.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + projects.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + projects.list, + ) + self.archive = _legacy_response.async_to_raw_response_wrapper( + projects.archive, + ) + + @cached_property + def users(self) -> AsyncUsersWithRawResponse: + return AsyncUsersWithRawResponse(self._projects.users) + + @cached_property + def service_accounts(self) -> AsyncServiceAccountsWithRawResponse: + return AsyncServiceAccountsWithRawResponse(self._projects.service_accounts) + + @cached_property + def api_keys(self) -> AsyncAPIKeysWithRawResponse: + return AsyncAPIKeysWithRawResponse(self._projects.api_keys) + + @cached_property + def rate_limits(self) -> AsyncRateLimitsWithRawResponse: + return AsyncRateLimitsWithRawResponse(self._projects.rate_limits) + + @cached_property + def model_permissions(self) -> AsyncModelPermissionsWithRawResponse: + return AsyncModelPermissionsWithRawResponse(self._projects.model_permissions) + + @cached_property + def hosted_tool_permissions(self) -> AsyncHostedToolPermissionsWithRawResponse: + return AsyncHostedToolPermissionsWithRawResponse(self._projects.hosted_tool_permissions) + + @cached_property + def groups(self) -> AsyncGroupsWithRawResponse: + return AsyncGroupsWithRawResponse(self._projects.groups) + + @cached_property + def roles(self) -> AsyncRolesWithRawResponse: + return AsyncRolesWithRawResponse(self._projects.roles) + + @cached_property + def data_retention(self) -> AsyncDataRetentionWithRawResponse: + return AsyncDataRetentionWithRawResponse(self._projects.data_retention) + + @cached_property + def spend_alerts(self) -> AsyncSpendAlertsWithRawResponse: + return AsyncSpendAlertsWithRawResponse(self._projects.spend_alerts) + + @cached_property + def certificates(self) -> AsyncCertificatesWithRawResponse: + return AsyncCertificatesWithRawResponse(self._projects.certificates) + + +class ProjectsWithStreamingResponse: + def __init__(self, projects: Projects) -> None: + self._projects = projects + + self.create = to_streamed_response_wrapper( + projects.create, + ) + self.retrieve = to_streamed_response_wrapper( + projects.retrieve, + ) + self.update = to_streamed_response_wrapper( + projects.update, + ) + self.list = to_streamed_response_wrapper( + projects.list, + ) + self.archive = to_streamed_response_wrapper( + projects.archive, + ) + + @cached_property + def users(self) -> UsersWithStreamingResponse: + return UsersWithStreamingResponse(self._projects.users) + + @cached_property + def service_accounts(self) -> ServiceAccountsWithStreamingResponse: + return ServiceAccountsWithStreamingResponse(self._projects.service_accounts) + + @cached_property + def api_keys(self) -> APIKeysWithStreamingResponse: + return APIKeysWithStreamingResponse(self._projects.api_keys) + + @cached_property + def rate_limits(self) -> RateLimitsWithStreamingResponse: + return RateLimitsWithStreamingResponse(self._projects.rate_limits) + + @cached_property + def model_permissions(self) -> ModelPermissionsWithStreamingResponse: + return ModelPermissionsWithStreamingResponse(self._projects.model_permissions) + + @cached_property + def hosted_tool_permissions(self) -> HostedToolPermissionsWithStreamingResponse: + return HostedToolPermissionsWithStreamingResponse(self._projects.hosted_tool_permissions) + + @cached_property + def groups(self) -> GroupsWithStreamingResponse: + return GroupsWithStreamingResponse(self._projects.groups) + + @cached_property + def roles(self) -> RolesWithStreamingResponse: + return RolesWithStreamingResponse(self._projects.roles) + + @cached_property + def data_retention(self) -> DataRetentionWithStreamingResponse: + return DataRetentionWithStreamingResponse(self._projects.data_retention) + + @cached_property + def spend_alerts(self) -> SpendAlertsWithStreamingResponse: + return SpendAlertsWithStreamingResponse(self._projects.spend_alerts) + + @cached_property + def certificates(self) -> CertificatesWithStreamingResponse: + return CertificatesWithStreamingResponse(self._projects.certificates) + + +class AsyncProjectsWithStreamingResponse: + def __init__(self, projects: AsyncProjects) -> None: + self._projects = projects + + self.create = async_to_streamed_response_wrapper( + projects.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + projects.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + projects.update, + ) + self.list = async_to_streamed_response_wrapper( + projects.list, + ) + self.archive = async_to_streamed_response_wrapper( + projects.archive, + ) + + @cached_property + def users(self) -> AsyncUsersWithStreamingResponse: + return AsyncUsersWithStreamingResponse(self._projects.users) + + @cached_property + def service_accounts(self) -> AsyncServiceAccountsWithStreamingResponse: + return AsyncServiceAccountsWithStreamingResponse(self._projects.service_accounts) + + @cached_property + def api_keys(self) -> AsyncAPIKeysWithStreamingResponse: + return AsyncAPIKeysWithStreamingResponse(self._projects.api_keys) + + @cached_property + def rate_limits(self) -> AsyncRateLimitsWithStreamingResponse: + return AsyncRateLimitsWithStreamingResponse(self._projects.rate_limits) + + @cached_property + def model_permissions(self) -> AsyncModelPermissionsWithStreamingResponse: + return AsyncModelPermissionsWithStreamingResponse(self._projects.model_permissions) + + @cached_property + def hosted_tool_permissions(self) -> AsyncHostedToolPermissionsWithStreamingResponse: + return AsyncHostedToolPermissionsWithStreamingResponse(self._projects.hosted_tool_permissions) + + @cached_property + def groups(self) -> AsyncGroupsWithStreamingResponse: + return AsyncGroupsWithStreamingResponse(self._projects.groups) + + @cached_property + def roles(self) -> AsyncRolesWithStreamingResponse: + return AsyncRolesWithStreamingResponse(self._projects.roles) + + @cached_property + def data_retention(self) -> AsyncDataRetentionWithStreamingResponse: + return AsyncDataRetentionWithStreamingResponse(self._projects.data_retention) + + @cached_property + def spend_alerts(self) -> AsyncSpendAlertsWithStreamingResponse: + return AsyncSpendAlertsWithStreamingResponse(self._projects.spend_alerts) + + @cached_property + def certificates(self) -> AsyncCertificatesWithStreamingResponse: + return AsyncCertificatesWithStreamingResponse(self._projects.certificates) diff --git a/src/openai/resources/admin/organization/projects/rate_limits.py b/src/openai/resources/admin/organization/projects/rate_limits.py new file mode 100644 index 0000000000..9fe20f572e --- /dev/null +++ b/src/openai/resources/admin/organization/projects/rate_limits.py @@ -0,0 +1,379 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization.projects import ( + rate_limit_list_rate_limits_params, + rate_limit_update_rate_limit_params, +) +from .....types.admin.organization.projects.project_rate_limit import ProjectRateLimit + +__all__ = ["RateLimits", "AsyncRateLimits"] + + +class RateLimits(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RateLimitsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return RateLimitsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RateLimitsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return RateLimitsWithStreamingResponse(self) + + def list_rate_limits( + self, + project_id: str, + *, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[ProjectRateLimit]: + """ + Returns the rate limits per model for a project. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + before: A cursor for use in pagination. `before` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + beginning with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. + + limit: A limit on the number of objects to be returned. The default is 100. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/rate_limits", project_id=project_id), + page=SyncConversationCursorPage[ProjectRateLimit], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "limit": limit, + }, + rate_limit_list_rate_limits_params.RateLimitListRateLimitsParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectRateLimit, + ) + + def update_rate_limit( + self, + rate_limit_id: str, + *, + project_id: str, + batch_1_day_max_input_tokens: int | Omit = omit, + max_audio_megabytes_per_1_minute: int | Omit = omit, + max_images_per_1_minute: int | Omit = omit, + max_requests_per_1_day: int | Omit = omit, + max_requests_per_1_minute: int | Omit = omit, + max_tokens_per_1_minute: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectRateLimit: + """ + Updates a project rate limit. + + Args: + batch_1_day_max_input_tokens: The maximum batch input tokens per day. Only relevant for certain models. + + max_audio_megabytes_per_1_minute: The maximum audio megabytes per minute. Only relevant for certain models. + + max_images_per_1_minute: The maximum images per minute. Only relevant for certain models. + + max_requests_per_1_day: The maximum requests per day. Only relevant for certain models. + + max_requests_per_1_minute: The maximum requests per minute. + + max_tokens_per_1_minute: The maximum tokens per minute. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not rate_limit_id: + raise ValueError(f"Expected a non-empty value for `rate_limit_id` but received {rate_limit_id!r}") + return self._post( + path_template( + "/organization/projects/{project_id}/rate_limits/{rate_limit_id}", + project_id=project_id, + rate_limit_id=rate_limit_id, + ), + body=maybe_transform( + { + "batch_1_day_max_input_tokens": batch_1_day_max_input_tokens, + "max_audio_megabytes_per_1_minute": max_audio_megabytes_per_1_minute, + "max_images_per_1_minute": max_images_per_1_minute, + "max_requests_per_1_day": max_requests_per_1_day, + "max_requests_per_1_minute": max_requests_per_1_minute, + "max_tokens_per_1_minute": max_tokens_per_1_minute, + }, + rate_limit_update_rate_limit_params.RateLimitUpdateRateLimitParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectRateLimit, + ) + + +class AsyncRateLimits(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRateLimitsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncRateLimitsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRateLimitsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncRateLimitsWithStreamingResponse(self) + + def list_rate_limits( + self, + project_id: str, + *, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ProjectRateLimit, AsyncConversationCursorPage[ProjectRateLimit]]: + """ + Returns the rate limits per model for a project. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + before: A cursor for use in pagination. `before` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + beginning with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. + + limit: A limit on the number of objects to be returned. The default is 100. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/rate_limits", project_id=project_id), + page=AsyncConversationCursorPage[ProjectRateLimit], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "limit": limit, + }, + rate_limit_list_rate_limits_params.RateLimitListRateLimitsParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectRateLimit, + ) + + async def update_rate_limit( + self, + rate_limit_id: str, + *, + project_id: str, + batch_1_day_max_input_tokens: int | Omit = omit, + max_audio_megabytes_per_1_minute: int | Omit = omit, + max_images_per_1_minute: int | Omit = omit, + max_requests_per_1_day: int | Omit = omit, + max_requests_per_1_minute: int | Omit = omit, + max_tokens_per_1_minute: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectRateLimit: + """ + Updates a project rate limit. + + Args: + batch_1_day_max_input_tokens: The maximum batch input tokens per day. Only relevant for certain models. + + max_audio_megabytes_per_1_minute: The maximum audio megabytes per minute. Only relevant for certain models. + + max_images_per_1_minute: The maximum images per minute. Only relevant for certain models. + + max_requests_per_1_day: The maximum requests per day. Only relevant for certain models. + + max_requests_per_1_minute: The maximum requests per minute. + + max_tokens_per_1_minute: The maximum tokens per minute. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not rate_limit_id: + raise ValueError(f"Expected a non-empty value for `rate_limit_id` but received {rate_limit_id!r}") + return await self._post( + path_template( + "/organization/projects/{project_id}/rate_limits/{rate_limit_id}", + project_id=project_id, + rate_limit_id=rate_limit_id, + ), + body=await async_maybe_transform( + { + "batch_1_day_max_input_tokens": batch_1_day_max_input_tokens, + "max_audio_megabytes_per_1_minute": max_audio_megabytes_per_1_minute, + "max_images_per_1_minute": max_images_per_1_minute, + "max_requests_per_1_day": max_requests_per_1_day, + "max_requests_per_1_minute": max_requests_per_1_minute, + "max_tokens_per_1_minute": max_tokens_per_1_minute, + }, + rate_limit_update_rate_limit_params.RateLimitUpdateRateLimitParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectRateLimit, + ) + + +class RateLimitsWithRawResponse: + def __init__(self, rate_limits: RateLimits) -> None: + self._rate_limits = rate_limits + + self.list_rate_limits = _legacy_response.to_raw_response_wrapper( + rate_limits.list_rate_limits, + ) + self.update_rate_limit = _legacy_response.to_raw_response_wrapper( + rate_limits.update_rate_limit, + ) + + +class AsyncRateLimitsWithRawResponse: + def __init__(self, rate_limits: AsyncRateLimits) -> None: + self._rate_limits = rate_limits + + self.list_rate_limits = _legacy_response.async_to_raw_response_wrapper( + rate_limits.list_rate_limits, + ) + self.update_rate_limit = _legacy_response.async_to_raw_response_wrapper( + rate_limits.update_rate_limit, + ) + + +class RateLimitsWithStreamingResponse: + def __init__(self, rate_limits: RateLimits) -> None: + self._rate_limits = rate_limits + + self.list_rate_limits = to_streamed_response_wrapper( + rate_limits.list_rate_limits, + ) + self.update_rate_limit = to_streamed_response_wrapper( + rate_limits.update_rate_limit, + ) + + +class AsyncRateLimitsWithStreamingResponse: + def __init__(self, rate_limits: AsyncRateLimits) -> None: + self._rate_limits = rate_limits + + self.list_rate_limits = async_to_streamed_response_wrapper( + rate_limits.list_rate_limits, + ) + self.update_rate_limit = async_to_streamed_response_wrapper( + rate_limits.update_rate_limit, + ) diff --git a/src/openai/resources/admin/organization/projects/roles.py b/src/openai/resources/admin/organization/projects/roles.py new file mode 100644 index 0000000000..4c603ec5b1 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/roles.py @@ -0,0 +1,644 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncNextCursorPage, AsyncNextCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization.role import Role +from .....types.admin.organization.projects import role_list_params, role_create_params, role_update_params +from .....types.admin.organization.projects.role_delete_response import RoleDeleteResponse + +__all__ = ["Roles", "AsyncRoles"] + + +class Roles(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return RolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return RolesWithStreamingResponse(self) + + def create( + self, + project_id: str, + *, + permissions: SequenceNotStr[str], + role_name: str, + description: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Creates a custom role for a project. + + Args: + permissions: Permissions to grant to the role. + + role_name: Unique name for the role. + + description: Optional description of the role. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + path_template("/projects/{project_id}/roles", project_id=project_id), + body=maybe_transform( + { + "permissions": permissions, + "role_name": role_name, + "description": description, + }, + role_create_params.RoleCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + def retrieve( + self, + role_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Retrieves a project role. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._get( + path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + def update( + self, + role_id: str, + *, + project_id: str, + description: Optional[str] | Omit = omit, + permissions: Optional[SequenceNotStr[str]] | Omit = omit, + role_name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Updates an existing project role. + + Args: + description: New description for the role. + + permissions: Updated set of permissions for the role. + + role_name: New name for the role. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._post( + path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id), + body=maybe_transform( + { + "description": description, + "permissions": permissions, + "role_name": role_name, + }, + role_update_params.RoleUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncNextCursorPage[Role]: + """Lists the roles configured for a project. + + Args: + after: Cursor for pagination. + + Provide the value from the previous response's `next` + field to continue listing roles. + + limit: A limit on the number of roles to return. Defaults to 1000. + + order: Sort order for the returned roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/projects/{project_id}/roles", project_id=project_id), + page=SyncNextCursorPage[Role], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=Role, + ) + + def delete( + self, + role_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Deletes a custom role from a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._delete( + path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class AsyncRoles(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncRolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncRolesWithStreamingResponse(self) + + async def create( + self, + project_id: str, + *, + permissions: SequenceNotStr[str], + role_name: str, + description: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Creates a custom role for a project. + + Args: + permissions: Permissions to grant to the role. + + role_name: Unique name for the role. + + description: Optional description of the role. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + path_template("/projects/{project_id}/roles", project_id=project_id), + body=await async_maybe_transform( + { + "permissions": permissions, + "role_name": role_name, + "description": description, + }, + role_create_params.RoleCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + async def retrieve( + self, + role_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Retrieves a project role. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._get( + path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + async def update( + self, + role_id: str, + *, + project_id: str, + description: Optional[str] | Omit = omit, + permissions: Optional[SequenceNotStr[str]] | Omit = omit, + role_name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Updates an existing project role. + + Args: + description: New description for the role. + + permissions: Updated set of permissions for the role. + + role_name: New name for the role. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._post( + path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id), + body=await async_maybe_transform( + { + "description": description, + "permissions": permissions, + "role_name": role_name, + }, + role_update_params.RoleUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Role, AsyncNextCursorPage[Role]]: + """Lists the roles configured for a project. + + Args: + after: Cursor for pagination. + + Provide the value from the previous response's `next` + field to continue listing roles. + + limit: A limit on the number of roles to return. Defaults to 1000. + + order: Sort order for the returned roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/projects/{project_id}/roles", project_id=project_id), + page=AsyncNextCursorPage[Role], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=Role, + ) + + async def delete( + self, + role_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Deletes a custom role from a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._delete( + path_template("/projects/{project_id}/roles/{role_id}", project_id=project_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class RolesWithRawResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = _legacy_response.to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + roles.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + roles.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithRawResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = _legacy_response.async_to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + roles.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + roles.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + roles.delete, + ) + + +class RolesWithStreamingResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = to_streamed_response_wrapper( + roles.retrieve, + ) + self.update = to_streamed_response_wrapper( + roles.update, + ) + self.list = to_streamed_response_wrapper( + roles.list, + ) + self.delete = to_streamed_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithStreamingResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = async_to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + roles.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + roles.update, + ) + self.list = async_to_streamed_response_wrapper( + roles.list, + ) + self.delete = async_to_streamed_response_wrapper( + roles.delete, + ) diff --git a/src/openai/resources/admin/organization/projects/service_accounts.py b/src/openai/resources/admin/organization/projects/service_accounts.py new file mode 100644 index 0000000000..0de0ced9cf --- /dev/null +++ b/src/openai/resources/admin/organization/projects/service_accounts.py @@ -0,0 +1,644 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization.projects import ( + service_account_list_params, + service_account_create_params, + service_account_update_params, +) +from .....types.admin.organization.projects.project_service_account import ProjectServiceAccount +from .....types.admin.organization.projects.service_account_create_response import ServiceAccountCreateResponse +from .....types.admin.organization.projects.service_account_delete_response import ServiceAccountDeleteResponse + +__all__ = ["ServiceAccounts", "AsyncServiceAccounts"] + + +class ServiceAccounts(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ServiceAccountsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ServiceAccountsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ServiceAccountsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ServiceAccountsWithStreamingResponse(self) + + def create( + self, + project_id: str, + *, + name: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ServiceAccountCreateResponse: + """Creates a new service account in the project. + + This also returns an unredacted + API key for the service account. + + Args: + name: The name of the service account being created. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + path_template("/organization/projects/{project_id}/service_accounts", project_id=project_id), + body=maybe_transform({"name": name}, service_account_create_params.ServiceAccountCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ServiceAccountCreateResponse, + ) + + def retrieve( + self, + service_account_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectServiceAccount: + """ + Retrieves a service account in the project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not service_account_id: + raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") + return self._get( + path_template( + "/organization/projects/{project_id}/service_accounts/{service_account_id}", + project_id=project_id, + service_account_id=service_account_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectServiceAccount, + ) + + def update( + self, + service_account_id: str, + *, + project_id: str, + name: str | Omit = omit, + role: Literal["member", "owner"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectServiceAccount: + """ + Updates a service account in the project. + + Args: + name: The updated service account name. + + role: The updated service account role. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not service_account_id: + raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") + return self._post( + path_template( + "/organization/projects/{project_id}/service_accounts/{service_account_id}", + project_id=project_id, + service_account_id=service_account_id, + ), + body=maybe_transform( + { + "name": name, + "role": role, + }, + service_account_update_params.ServiceAccountUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectServiceAccount, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[ProjectServiceAccount]: + """ + Returns a list of service accounts in the project. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/service_accounts", project_id=project_id), + page=SyncConversationCursorPage[ProjectServiceAccount], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + }, + service_account_list_params.ServiceAccountListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectServiceAccount, + ) + + def delete( + self, + service_account_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ServiceAccountDeleteResponse: + """ + Deletes a service account from the project. + + Returns confirmation of service account deletion, or an error if the project is + archived (archived projects have no service accounts). + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not service_account_id: + raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") + return self._delete( + path_template( + "/organization/projects/{project_id}/service_accounts/{service_account_id}", + project_id=project_id, + service_account_id=service_account_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ServiceAccountDeleteResponse, + ) + + +class AsyncServiceAccounts(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncServiceAccountsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncServiceAccountsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncServiceAccountsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncServiceAccountsWithStreamingResponse(self) + + async def create( + self, + project_id: str, + *, + name: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ServiceAccountCreateResponse: + """Creates a new service account in the project. + + This also returns an unredacted + API key for the service account. + + Args: + name: The name of the service account being created. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + path_template("/organization/projects/{project_id}/service_accounts", project_id=project_id), + body=await async_maybe_transform({"name": name}, service_account_create_params.ServiceAccountCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ServiceAccountCreateResponse, + ) + + async def retrieve( + self, + service_account_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectServiceAccount: + """ + Retrieves a service account in the project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not service_account_id: + raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") + return await self._get( + path_template( + "/organization/projects/{project_id}/service_accounts/{service_account_id}", + project_id=project_id, + service_account_id=service_account_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectServiceAccount, + ) + + async def update( + self, + service_account_id: str, + *, + project_id: str, + name: str | Omit = omit, + role: Literal["member", "owner"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectServiceAccount: + """ + Updates a service account in the project. + + Args: + name: The updated service account name. + + role: The updated service account role. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not service_account_id: + raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") + return await self._post( + path_template( + "/organization/projects/{project_id}/service_accounts/{service_account_id}", + project_id=project_id, + service_account_id=service_account_id, + ), + body=await async_maybe_transform( + { + "name": name, + "role": role, + }, + service_account_update_params.ServiceAccountUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectServiceAccount, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ProjectServiceAccount, AsyncConversationCursorPage[ProjectServiceAccount]]: + """ + Returns a list of service accounts in the project. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/service_accounts", project_id=project_id), + page=AsyncConversationCursorPage[ProjectServiceAccount], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + }, + service_account_list_params.ServiceAccountListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectServiceAccount, + ) + + async def delete( + self, + service_account_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ServiceAccountDeleteResponse: + """ + Deletes a service account from the project. + + Returns confirmation of service account deletion, or an error if the project is + archived (archived projects have no service accounts). + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not service_account_id: + raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") + return await self._delete( + path_template( + "/organization/projects/{project_id}/service_accounts/{service_account_id}", + project_id=project_id, + service_account_id=service_account_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ServiceAccountDeleteResponse, + ) + + +class ServiceAccountsWithRawResponse: + def __init__(self, service_accounts: ServiceAccounts) -> None: + self._service_accounts = service_accounts + + self.create = _legacy_response.to_raw_response_wrapper( + service_accounts.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + service_accounts.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + service_accounts.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + service_accounts.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + service_accounts.delete, + ) + + +class AsyncServiceAccountsWithRawResponse: + def __init__(self, service_accounts: AsyncServiceAccounts) -> None: + self._service_accounts = service_accounts + + self.create = _legacy_response.async_to_raw_response_wrapper( + service_accounts.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + service_accounts.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + service_accounts.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + service_accounts.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + service_accounts.delete, + ) + + +class ServiceAccountsWithStreamingResponse: + def __init__(self, service_accounts: ServiceAccounts) -> None: + self._service_accounts = service_accounts + + self.create = to_streamed_response_wrapper( + service_accounts.create, + ) + self.retrieve = to_streamed_response_wrapper( + service_accounts.retrieve, + ) + self.update = to_streamed_response_wrapper( + service_accounts.update, + ) + self.list = to_streamed_response_wrapper( + service_accounts.list, + ) + self.delete = to_streamed_response_wrapper( + service_accounts.delete, + ) + + +class AsyncServiceAccountsWithStreamingResponse: + def __init__(self, service_accounts: AsyncServiceAccounts) -> None: + self._service_accounts = service_accounts + + self.create = async_to_streamed_response_wrapper( + service_accounts.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + service_accounts.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + service_accounts.update, + ) + self.list = async_to_streamed_response_wrapper( + service_accounts.list, + ) + self.delete = async_to_streamed_response_wrapper( + service_accounts.delete, + ) diff --git a/src/openai/resources/admin/organization/projects/spend_alerts.py b/src/openai/resources/admin/organization/projects/spend_alerts.py new file mode 100644 index 0000000000..3bc5d0c1f9 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/spend_alerts.py @@ -0,0 +1,589 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization.projects import ( + spend_alert_list_params, + spend_alert_create_params, + spend_alert_update_params, +) +from .....types.admin.organization.projects.project_spend_alert import ProjectSpendAlert +from .....types.admin.organization.projects.project_spend_alert_deleted import ProjectSpendAlertDeleted + +__all__ = ["SpendAlerts", "AsyncSpendAlerts"] + + +class SpendAlerts(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SpendAlertsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return SpendAlertsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SpendAlertsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return SpendAlertsWithStreamingResponse(self) + + def create( + self, + project_id: str, + *, + currency: Literal["USD"], + interval: Literal["month"], + notification_channel: spend_alert_create_params.NotificationChannel, + threshold_amount: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectSpendAlert: + """ + Creates a project spend alert. + + Args: + currency: The currency for the threshold amount. + + interval: The time interval for evaluating spend against the threshold. + + notification_channel: Email notification settings for a spend alert. + + threshold_amount: The alert threshold amount, in cents. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + path_template("/organization/projects/{project_id}/spend_alerts", project_id=project_id), + body=maybe_transform( + { + "currency": currency, + "interval": interval, + "notification_channel": notification_channel, + "threshold_amount": threshold_amount, + }, + spend_alert_create_params.SpendAlertCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectSpendAlert, + ) + + def update( + self, + alert_id: str, + *, + project_id: str, + currency: Literal["USD"], + interval: Literal["month"], + notification_channel: spend_alert_update_params.NotificationChannel, + threshold_amount: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectSpendAlert: + """ + Updates a project spend alert. + + Args: + currency: The currency for the threshold amount. + + interval: The time interval for evaluating spend against the threshold. + + notification_channel: Email notification settings for a spend alert. + + threshold_amount: The alert threshold amount, in cents. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not alert_id: + raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}") + return self._post( + path_template( + "/organization/projects/{project_id}/spend_alerts/{alert_id}", project_id=project_id, alert_id=alert_id + ), + body=maybe_transform( + { + "currency": currency, + "interval": interval, + "notification_channel": notification_channel, + "threshold_amount": threshold_amount, + }, + spend_alert_update_params.SpendAlertUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectSpendAlert, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[ProjectSpendAlert]: + """Lists project spend alerts. + + Args: + after: Cursor for pagination. + + Provide the ID of the last spend alert from the previous + response to fetch the next page. + + before: Cursor for pagination. Provide the ID of the first spend alert from the previous + response to fetch the previous page. + + limit: A limit on the number of spend alerts to return. Defaults to 20. + + order: Sort order for the returned spend alerts. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/spend_alerts", project_id=project_id), + page=SyncConversationCursorPage[ProjectSpendAlert], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "limit": limit, + "order": order, + }, + spend_alert_list_params.SpendAlertListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectSpendAlert, + ) + + def delete( + self, + alert_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectSpendAlertDeleted: + """ + Deletes a project spend alert. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not alert_id: + raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}") + return self._delete( + path_template( + "/organization/projects/{project_id}/spend_alerts/{alert_id}", project_id=project_id, alert_id=alert_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectSpendAlertDeleted, + ) + + +class AsyncSpendAlerts(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSpendAlertsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncSpendAlertsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSpendAlertsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncSpendAlertsWithStreamingResponse(self) + + async def create( + self, + project_id: str, + *, + currency: Literal["USD"], + interval: Literal["month"], + notification_channel: spend_alert_create_params.NotificationChannel, + threshold_amount: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectSpendAlert: + """ + Creates a project spend alert. + + Args: + currency: The currency for the threshold amount. + + interval: The time interval for evaluating spend against the threshold. + + notification_channel: Email notification settings for a spend alert. + + threshold_amount: The alert threshold amount, in cents. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + path_template("/organization/projects/{project_id}/spend_alerts", project_id=project_id), + body=await async_maybe_transform( + { + "currency": currency, + "interval": interval, + "notification_channel": notification_channel, + "threshold_amount": threshold_amount, + }, + spend_alert_create_params.SpendAlertCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectSpendAlert, + ) + + async def update( + self, + alert_id: str, + *, + project_id: str, + currency: Literal["USD"], + interval: Literal["month"], + notification_channel: spend_alert_update_params.NotificationChannel, + threshold_amount: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectSpendAlert: + """ + Updates a project spend alert. + + Args: + currency: The currency for the threshold amount. + + interval: The time interval for evaluating spend against the threshold. + + notification_channel: Email notification settings for a spend alert. + + threshold_amount: The alert threshold amount, in cents. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not alert_id: + raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}") + return await self._post( + path_template( + "/organization/projects/{project_id}/spend_alerts/{alert_id}", project_id=project_id, alert_id=alert_id + ), + body=await async_maybe_transform( + { + "currency": currency, + "interval": interval, + "notification_channel": notification_channel, + "threshold_amount": threshold_amount, + }, + spend_alert_update_params.SpendAlertUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectSpendAlert, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ProjectSpendAlert, AsyncConversationCursorPage[ProjectSpendAlert]]: + """Lists project spend alerts. + + Args: + after: Cursor for pagination. + + Provide the ID of the last spend alert from the previous + response to fetch the next page. + + before: Cursor for pagination. Provide the ID of the first spend alert from the previous + response to fetch the previous page. + + limit: A limit on the number of spend alerts to return. Defaults to 20. + + order: Sort order for the returned spend alerts. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/spend_alerts", project_id=project_id), + page=AsyncConversationCursorPage[ProjectSpendAlert], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "limit": limit, + "order": order, + }, + spend_alert_list_params.SpendAlertListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectSpendAlert, + ) + + async def delete( + self, + alert_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectSpendAlertDeleted: + """ + Deletes a project spend alert. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not alert_id: + raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}") + return await self._delete( + path_template( + "/organization/projects/{project_id}/spend_alerts/{alert_id}", project_id=project_id, alert_id=alert_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectSpendAlertDeleted, + ) + + +class SpendAlertsWithRawResponse: + def __init__(self, spend_alerts: SpendAlerts) -> None: + self._spend_alerts = spend_alerts + + self.create = _legacy_response.to_raw_response_wrapper( + spend_alerts.create, + ) + self.update = _legacy_response.to_raw_response_wrapper( + spend_alerts.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + spend_alerts.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + spend_alerts.delete, + ) + + +class AsyncSpendAlertsWithRawResponse: + def __init__(self, spend_alerts: AsyncSpendAlerts) -> None: + self._spend_alerts = spend_alerts + + self.create = _legacy_response.async_to_raw_response_wrapper( + spend_alerts.create, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + spend_alerts.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + spend_alerts.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + spend_alerts.delete, + ) + + +class SpendAlertsWithStreamingResponse: + def __init__(self, spend_alerts: SpendAlerts) -> None: + self._spend_alerts = spend_alerts + + self.create = to_streamed_response_wrapper( + spend_alerts.create, + ) + self.update = to_streamed_response_wrapper( + spend_alerts.update, + ) + self.list = to_streamed_response_wrapper( + spend_alerts.list, + ) + self.delete = to_streamed_response_wrapper( + spend_alerts.delete, + ) + + +class AsyncSpendAlertsWithStreamingResponse: + def __init__(self, spend_alerts: AsyncSpendAlerts) -> None: + self._spend_alerts = spend_alerts + + self.create = async_to_streamed_response_wrapper( + spend_alerts.create, + ) + self.update = async_to_streamed_response_wrapper( + spend_alerts.update, + ) + self.list = async_to_streamed_response_wrapper( + spend_alerts.list, + ) + self.delete = async_to_streamed_response_wrapper( + spend_alerts.delete, + ) diff --git a/src/openai/resources/admin/organization/projects/users/__init__.py b/src/openai/resources/admin/organization/projects/users/__init__.py new file mode 100644 index 0000000000..d230cb8f34 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/users/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from .users import ( + Users, + AsyncUsers, + UsersWithRawResponse, + AsyncUsersWithRawResponse, + UsersWithStreamingResponse, + AsyncUsersWithStreamingResponse, +) + +__all__ = [ + "Roles", + "AsyncRoles", + "RolesWithRawResponse", + "AsyncRolesWithRawResponse", + "RolesWithStreamingResponse", + "AsyncRolesWithStreamingResponse", + "Users", + "AsyncUsers", + "UsersWithRawResponse", + "AsyncUsersWithRawResponse", + "UsersWithStreamingResponse", + "AsyncUsersWithStreamingResponse", +] diff --git a/src/openai/resources/admin/organization/projects/users/roles.py b/src/openai/resources/admin/organization/projects/users/roles.py new file mode 100644 index 0000000000..38b79acec3 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/users/roles.py @@ -0,0 +1,535 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ...... import _legacy_response +from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ......_utils import path_template, maybe_transform, async_maybe_transform +from ......_compat import cached_property +from ......_resource import SyncAPIResource, AsyncAPIResource +from ......_response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ......pagination import SyncNextCursorPage, AsyncNextCursorPage +from ......_base_client import AsyncPaginator, make_request_options +from ......types.admin.organization.projects.users import role_list_params, role_create_params +from ......types.admin.organization.projects.users.role_list_response import RoleListResponse +from ......types.admin.organization.projects.users.role_create_response import RoleCreateResponse +from ......types.admin.organization.projects.users.role_delete_response import RoleDeleteResponse +from ......types.admin.organization.projects.users.role_retrieve_response import RoleRetrieveResponse + +__all__ = ["Roles", "AsyncRoles"] + + +class Roles(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return RolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return RolesWithStreamingResponse(self) + + def create( + self, + user_id: str, + *, + project_id: str, + role_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleCreateResponse: + """ + Assigns a project role to a user within a project. + + Args: + role_id: Identifier of the role to assign. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._post( + path_template("/projects/{project_id}/users/{user_id}/roles", project_id=project_id, user_id=user_id), + body=maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleCreateResponse, + ) + + def retrieve( + self, + role_id: str, + *, + project_id: str, + user_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleRetrieveResponse: + """ + Retrieves a project role assigned to a user. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._get( + path_template( + "/projects/{project_id}/users/{user_id}/roles/{role_id}", + project_id=project_id, + user_id=user_id, + role_id=role_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleRetrieveResponse, + ) + + def list( + self, + user_id: str, + *, + project_id: str, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncNextCursorPage[RoleListResponse]: + """ + Lists the project roles assigned to a user within a project. + + Args: + after: Cursor for pagination. Provide the value from the previous response's `next` + field to continue listing project roles. + + limit: A limit on the number of project role assignments to return. + + order: Sort order for the returned project roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._get_api_list( + path_template("/projects/{project_id}/users/{user_id}/roles", project_id=project_id, user_id=user_id), + page=SyncNextCursorPage[RoleListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=RoleListResponse, + ) + + def delete( + self, + role_id: str, + *, + project_id: str, + user_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Unassigns a project role from a user within a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._delete( + path_template( + "/projects/{project_id}/users/{user_id}/roles/{role_id}", + project_id=project_id, + user_id=user_id, + role_id=role_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class AsyncRoles(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncRolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncRolesWithStreamingResponse(self) + + async def create( + self, + user_id: str, + *, + project_id: str, + role_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleCreateResponse: + """ + Assigns a project role to a user within a project. + + Args: + role_id: Identifier of the role to assign. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return await self._post( + path_template("/projects/{project_id}/users/{user_id}/roles", project_id=project_id, user_id=user_id), + body=await async_maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleCreateResponse, + ) + + async def retrieve( + self, + role_id: str, + *, + project_id: str, + user_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleRetrieveResponse: + """ + Retrieves a project role assigned to a user. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._get( + path_template( + "/projects/{project_id}/users/{user_id}/roles/{role_id}", + project_id=project_id, + user_id=user_id, + role_id=role_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleRetrieveResponse, + ) + + def list( + self, + user_id: str, + *, + project_id: str, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]: + """ + Lists the project roles assigned to a user within a project. + + Args: + after: Cursor for pagination. Provide the value from the previous response's `next` + field to continue listing project roles. + + limit: A limit on the number of project role assignments to return. + + order: Sort order for the returned project roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._get_api_list( + path_template("/projects/{project_id}/users/{user_id}/roles", project_id=project_id, user_id=user_id), + page=AsyncNextCursorPage[RoleListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=RoleListResponse, + ) + + async def delete( + self, + role_id: str, + *, + project_id: str, + user_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Unassigns a project role from a user within a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._delete( + path_template( + "/projects/{project_id}/users/{user_id}/roles/{role_id}", + project_id=project_id, + user_id=user_id, + role_id=role_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class RolesWithRawResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = _legacy_response.to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + roles.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithRawResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = _legacy_response.async_to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + roles.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + roles.delete, + ) + + +class RolesWithStreamingResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = to_streamed_response_wrapper( + roles.retrieve, + ) + self.list = to_streamed_response_wrapper( + roles.list, + ) + self.delete = to_streamed_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithStreamingResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = async_to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + roles.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + roles.list, + ) + self.delete = async_to_streamed_response_wrapper( + roles.delete, + ) diff --git a/src/openai/resources/admin/organization/projects/users/users.py b/src/openai/resources/admin/organization/projects/users/users.py new file mode 100644 index 0000000000..3852bfb8c0 --- /dev/null +++ b/src/openai/resources/admin/organization/projects/users/users.py @@ -0,0 +1,667 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ...... import _legacy_response +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from ......_types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ......_utils import path_template, maybe_transform, async_maybe_transform +from ......_compat import cached_property +from ......_resource import SyncAPIResource, AsyncAPIResource +from ......_response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ......pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ......_base_client import AsyncPaginator, make_request_options +from ......types.admin.organization.projects import user_list_params, user_create_params, user_update_params +from ......types.admin.organization.projects.project_user import ProjectUser +from ......types.admin.organization.projects.user_delete_response import UserDeleteResponse + +__all__ = ["Users", "AsyncUsers"] + + +class Users(SyncAPIResource): + @cached_property + def roles(self) -> Roles: + return Roles(self._client) + + @cached_property + def with_raw_response(self) -> UsersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return UsersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> UsersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return UsersWithStreamingResponse(self) + + def create( + self, + project_id: str, + *, + role: str, + email: Optional[str] | Omit = omit, + user_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectUser: + """Adds a user to the project. + + Users must already be members of the organization to + be added to a project. + + Args: + role: `owner` or `member` + + email: Email of the user to add. + + user_id: The ID of the user. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + path_template("/organization/projects/{project_id}/users", project_id=project_id), + body=maybe_transform( + { + "role": role, + "email": email, + "user_id": user_id, + }, + user_create_params.UserCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectUser, + ) + + def retrieve( + self, + user_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectUser: + """ + Retrieves a user in the project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._get( + path_template( + "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectUser, + ) + + def update( + self, + user_id: str, + *, + project_id: str, + role: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectUser: + """ + Modifies a user's role in the project. + + Args: + role: `owner` or `member` + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._post( + path_template( + "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id + ), + body=maybe_transform({"role": role}, user_update_params.UserUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectUser, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[ProjectUser]: + """ + Returns a list of users in the project. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/users", project_id=project_id), + page=SyncConversationCursorPage[ProjectUser], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + }, + user_list_params.UserListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectUser, + ) + + def delete( + self, + user_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UserDeleteResponse: + """ + Deletes a user from the project. + + Returns confirmation of project user deletion, or an error if the project is + archived (archived projects have no users). + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._delete( + path_template( + "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=UserDeleteResponse, + ) + + +class AsyncUsers(AsyncAPIResource): + @cached_property + def roles(self) -> AsyncRoles: + return AsyncRoles(self._client) + + @cached_property + def with_raw_response(self) -> AsyncUsersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncUsersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncUsersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncUsersWithStreamingResponse(self) + + async def create( + self, + project_id: str, + *, + role: str, + email: Optional[str] | Omit = omit, + user_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectUser: + """Adds a user to the project. + + Users must already be members of the organization to + be added to a project. + + Args: + role: `owner` or `member` + + email: Email of the user to add. + + user_id: The ID of the user. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + path_template("/organization/projects/{project_id}/users", project_id=project_id), + body=await async_maybe_transform( + { + "role": role, + "email": email, + "user_id": user_id, + }, + user_create_params.UserCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectUser, + ) + + async def retrieve( + self, + user_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectUser: + """ + Retrieves a user in the project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return await self._get( + path_template( + "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectUser, + ) + + async def update( + self, + user_id: str, + *, + project_id: str, + role: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectUser: + """ + Modifies a user's role in the project. + + Args: + role: `owner` or `member` + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return await self._post( + path_template( + "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id + ), + body=await async_maybe_transform({"role": role}, user_update_params.UserUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ProjectUser, + ) + + def list( + self, + project_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ProjectUser, AsyncConversationCursorPage[ProjectUser]]: + """ + Returns a list of users in the project. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + path_template("/organization/projects/{project_id}/users", project_id=project_id), + page=AsyncConversationCursorPage[ProjectUser], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + }, + user_list_params.UserListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ProjectUser, + ) + + async def delete( + self, + user_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UserDeleteResponse: + """ + Deletes a user from the project. + + Returns confirmation of project user deletion, or an error if the project is + archived (archived projects have no users). + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return await self._delete( + path_template( + "/organization/projects/{project_id}/users/{user_id}", project_id=project_id, user_id=user_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=UserDeleteResponse, + ) + + +class UsersWithRawResponse: + def __init__(self, users: Users) -> None: + self._users = users + + self.create = _legacy_response.to_raw_response_wrapper( + users.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + users.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + users.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + users.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + users.delete, + ) + + @cached_property + def roles(self) -> RolesWithRawResponse: + return RolesWithRawResponse(self._users.roles) + + +class AsyncUsersWithRawResponse: + def __init__(self, users: AsyncUsers) -> None: + self._users = users + + self.create = _legacy_response.async_to_raw_response_wrapper( + users.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + users.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + users.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + users.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + users.delete, + ) + + @cached_property + def roles(self) -> AsyncRolesWithRawResponse: + return AsyncRolesWithRawResponse(self._users.roles) + + +class UsersWithStreamingResponse: + def __init__(self, users: Users) -> None: + self._users = users + + self.create = to_streamed_response_wrapper( + users.create, + ) + self.retrieve = to_streamed_response_wrapper( + users.retrieve, + ) + self.update = to_streamed_response_wrapper( + users.update, + ) + self.list = to_streamed_response_wrapper( + users.list, + ) + self.delete = to_streamed_response_wrapper( + users.delete, + ) + + @cached_property + def roles(self) -> RolesWithStreamingResponse: + return RolesWithStreamingResponse(self._users.roles) + + +class AsyncUsersWithStreamingResponse: + def __init__(self, users: AsyncUsers) -> None: + self._users = users + + self.create = async_to_streamed_response_wrapper( + users.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + users.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + users.update, + ) + self.list = async_to_streamed_response_wrapper( + users.list, + ) + self.delete = async_to_streamed_response_wrapper( + users.delete, + ) + + @cached_property + def roles(self) -> AsyncRolesWithStreamingResponse: + return AsyncRolesWithStreamingResponse(self._users.roles) diff --git a/src/openai/resources/admin/organization/roles.py b/src/openai/resources/admin/organization/roles.py new file mode 100644 index 0000000000..056a47d7b2 --- /dev/null +++ b/src/openai/resources/admin/organization/roles.py @@ -0,0 +1,612 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncNextCursorPage, AsyncNextCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.admin.organization import role_list_params, role_create_params, role_update_params +from ....types.admin.organization.role import Role +from ....types.admin.organization.role_delete_response import RoleDeleteResponse + +__all__ = ["Roles", "AsyncRoles"] + + +class Roles(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return RolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return RolesWithStreamingResponse(self) + + def create( + self, + *, + permissions: SequenceNotStr[str], + role_name: str, + description: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Creates a custom role for the organization. + + Args: + permissions: Permissions to grant to the role. + + role_name: Unique name for the role. + + description: Optional description of the role. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/organization/roles", + body=maybe_transform( + { + "permissions": permissions, + "role_name": role_name, + "description": description, + }, + role_create_params.RoleCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + def retrieve( + self, + role_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Retrieves an organization role. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._get( + path_template("/organization/roles/{role_id}", role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + def update( + self, + role_id: str, + *, + description: Optional[str] | Omit = omit, + permissions: Optional[SequenceNotStr[str]] | Omit = omit, + role_name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Updates an existing organization role. + + Args: + description: New description for the role. + + permissions: Updated set of permissions for the role. + + role_name: New name for the role. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._post( + path_template("/organization/roles/{role_id}", role_id=role_id), + body=maybe_transform( + { + "description": description, + "permissions": permissions, + "role_name": role_name, + }, + role_update_params.RoleUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncNextCursorPage[Role]: + """ + Lists the roles configured for the organization. + + Args: + after: Cursor for pagination. Provide the value from the previous response's `next` + field to continue listing roles. + + limit: A limit on the number of roles to return. Defaults to 1000. + + order: Sort order for the returned roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/roles", + page=SyncNextCursorPage[Role], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=Role, + ) + + def delete( + self, + role_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Deletes a custom role from the organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._delete( + path_template("/organization/roles/{role_id}", role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class AsyncRoles(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncRolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncRolesWithStreamingResponse(self) + + async def create( + self, + *, + permissions: SequenceNotStr[str], + role_name: str, + description: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Creates a custom role for the organization. + + Args: + permissions: Permissions to grant to the role. + + role_name: Unique name for the role. + + description: Optional description of the role. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/organization/roles", + body=await async_maybe_transform( + { + "permissions": permissions, + "role_name": role_name, + "description": description, + }, + role_create_params.RoleCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + async def retrieve( + self, + role_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Retrieves an organization role. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._get( + path_template("/organization/roles/{role_id}", role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + async def update( + self, + role_id: str, + *, + description: Optional[str] | Omit = omit, + permissions: Optional[SequenceNotStr[str]] | Omit = omit, + role_name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Role: + """ + Updates an existing organization role. + + Args: + description: New description for the role. + + permissions: Updated set of permissions for the role. + + role_name: New name for the role. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._post( + path_template("/organization/roles/{role_id}", role_id=role_id), + body=await async_maybe_transform( + { + "description": description, + "permissions": permissions, + "role_name": role_name, + }, + role_update_params.RoleUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=Role, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Role, AsyncNextCursorPage[Role]]: + """ + Lists the roles configured for the organization. + + Args: + after: Cursor for pagination. Provide the value from the previous response's `next` + field to continue listing roles. + + limit: A limit on the number of roles to return. Defaults to 1000. + + order: Sort order for the returned roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/roles", + page=AsyncNextCursorPage[Role], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=Role, + ) + + async def delete( + self, + role_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Deletes a custom role from the organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._delete( + path_template("/organization/roles/{role_id}", role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class RolesWithRawResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = _legacy_response.to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + roles.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + roles.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithRawResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = _legacy_response.async_to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + roles.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + roles.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + roles.delete, + ) + + +class RolesWithStreamingResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = to_streamed_response_wrapper( + roles.retrieve, + ) + self.update = to_streamed_response_wrapper( + roles.update, + ) + self.list = to_streamed_response_wrapper( + roles.list, + ) + self.delete = to_streamed_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithStreamingResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = async_to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + roles.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + roles.update, + ) + self.list = async_to_streamed_response_wrapper( + roles.list, + ) + self.delete = async_to_streamed_response_wrapper( + roles.delete, + ) diff --git a/src/openai/resources/admin/organization/spend_alerts.py b/src/openai/resources/admin/organization/spend_alerts.py new file mode 100644 index 0000000000..4f3e223269 --- /dev/null +++ b/src/openai/resources/admin/organization/spend_alerts.py @@ -0,0 +1,553 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.admin.organization import spend_alert_list_params, spend_alert_create_params, spend_alert_update_params +from ....types.admin.organization.organization_spend_alert import OrganizationSpendAlert +from ....types.admin.organization.organization_spend_alert_deleted import OrganizationSpendAlertDeleted + +__all__ = ["SpendAlerts", "AsyncSpendAlerts"] + + +class SpendAlerts(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SpendAlertsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return SpendAlertsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SpendAlertsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return SpendAlertsWithStreamingResponse(self) + + def create( + self, + *, + currency: Literal["USD"], + interval: Literal["month"], + notification_channel: spend_alert_create_params.NotificationChannel, + threshold_amount: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationSpendAlert: + """ + Creates an organization spend alert. + + Args: + currency: The currency for the threshold amount. + + interval: The time interval for evaluating spend against the threshold. + + notification_channel: Email notification settings for a spend alert. + + threshold_amount: The alert threshold amount, in cents. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/organization/spend_alerts", + body=maybe_transform( + { + "currency": currency, + "interval": interval, + "notification_channel": notification_channel, + "threshold_amount": threshold_amount, + }, + spend_alert_create_params.SpendAlertCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationSpendAlert, + ) + + def update( + self, + alert_id: str, + *, + currency: Literal["USD"], + interval: Literal["month"], + notification_channel: spend_alert_update_params.NotificationChannel, + threshold_amount: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationSpendAlert: + """ + Updates an organization spend alert. + + Args: + currency: The currency for the threshold amount. + + interval: The time interval for evaluating spend against the threshold. + + notification_channel: Email notification settings for a spend alert. + + threshold_amount: The alert threshold amount, in cents. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not alert_id: + raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}") + return self._post( + path_template("/organization/spend_alerts/{alert_id}", alert_id=alert_id), + body=maybe_transform( + { + "currency": currency, + "interval": interval, + "notification_channel": notification_channel, + "threshold_amount": threshold_amount, + }, + spend_alert_update_params.SpendAlertUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationSpendAlert, + ) + + def list( + self, + *, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[OrganizationSpendAlert]: + """Lists organization spend alerts. + + Args: + after: Cursor for pagination. + + Provide the ID of the last spend alert from the previous + response to fetch the next page. + + before: Cursor for pagination. Provide the ID of the first spend alert from the previous + response to fetch the previous page. + + limit: A limit on the number of spend alerts to return. Defaults to 20. + + order: Sort order for the returned spend alerts. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/spend_alerts", + page=SyncConversationCursorPage[OrganizationSpendAlert], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "limit": limit, + "order": order, + }, + spend_alert_list_params.SpendAlertListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=OrganizationSpendAlert, + ) + + def delete( + self, + alert_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationSpendAlertDeleted: + """ + Deletes an organization spend alert. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not alert_id: + raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}") + return self._delete( + path_template("/organization/spend_alerts/{alert_id}", alert_id=alert_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationSpendAlertDeleted, + ) + + +class AsyncSpendAlerts(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSpendAlertsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncSpendAlertsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSpendAlertsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncSpendAlertsWithStreamingResponse(self) + + async def create( + self, + *, + currency: Literal["USD"], + interval: Literal["month"], + notification_channel: spend_alert_create_params.NotificationChannel, + threshold_amount: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationSpendAlert: + """ + Creates an organization spend alert. + + Args: + currency: The currency for the threshold amount. + + interval: The time interval for evaluating spend against the threshold. + + notification_channel: Email notification settings for a spend alert. + + threshold_amount: The alert threshold amount, in cents. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/organization/spend_alerts", + body=await async_maybe_transform( + { + "currency": currency, + "interval": interval, + "notification_channel": notification_channel, + "threshold_amount": threshold_amount, + }, + spend_alert_create_params.SpendAlertCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationSpendAlert, + ) + + async def update( + self, + alert_id: str, + *, + currency: Literal["USD"], + interval: Literal["month"], + notification_channel: spend_alert_update_params.NotificationChannel, + threshold_amount: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationSpendAlert: + """ + Updates an organization spend alert. + + Args: + currency: The currency for the threshold amount. + + interval: The time interval for evaluating spend against the threshold. + + notification_channel: Email notification settings for a spend alert. + + threshold_amount: The alert threshold amount, in cents. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not alert_id: + raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}") + return await self._post( + path_template("/organization/spend_alerts/{alert_id}", alert_id=alert_id), + body=await async_maybe_transform( + { + "currency": currency, + "interval": interval, + "notification_channel": notification_channel, + "threshold_amount": threshold_amount, + }, + spend_alert_update_params.SpendAlertUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationSpendAlert, + ) + + def list( + self, + *, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[OrganizationSpendAlert, AsyncConversationCursorPage[OrganizationSpendAlert]]: + """Lists organization spend alerts. + + Args: + after: Cursor for pagination. + + Provide the ID of the last spend alert from the previous + response to fetch the next page. + + before: Cursor for pagination. Provide the ID of the first spend alert from the previous + response to fetch the previous page. + + limit: A limit on the number of spend alerts to return. Defaults to 20. + + order: Sort order for the returned spend alerts. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/spend_alerts", + page=AsyncConversationCursorPage[OrganizationSpendAlert], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "limit": limit, + "order": order, + }, + spend_alert_list_params.SpendAlertListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=OrganizationSpendAlert, + ) + + async def delete( + self, + alert_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationSpendAlertDeleted: + """ + Deletes an organization spend alert. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not alert_id: + raise ValueError(f"Expected a non-empty value for `alert_id` but received {alert_id!r}") + return await self._delete( + path_template("/organization/spend_alerts/{alert_id}", alert_id=alert_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationSpendAlertDeleted, + ) + + +class SpendAlertsWithRawResponse: + def __init__(self, spend_alerts: SpendAlerts) -> None: + self._spend_alerts = spend_alerts + + self.create = _legacy_response.to_raw_response_wrapper( + spend_alerts.create, + ) + self.update = _legacy_response.to_raw_response_wrapper( + spend_alerts.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + spend_alerts.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + spend_alerts.delete, + ) + + +class AsyncSpendAlertsWithRawResponse: + def __init__(self, spend_alerts: AsyncSpendAlerts) -> None: + self._spend_alerts = spend_alerts + + self.create = _legacy_response.async_to_raw_response_wrapper( + spend_alerts.create, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + spend_alerts.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + spend_alerts.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + spend_alerts.delete, + ) + + +class SpendAlertsWithStreamingResponse: + def __init__(self, spend_alerts: SpendAlerts) -> None: + self._spend_alerts = spend_alerts + + self.create = to_streamed_response_wrapper( + spend_alerts.create, + ) + self.update = to_streamed_response_wrapper( + spend_alerts.update, + ) + self.list = to_streamed_response_wrapper( + spend_alerts.list, + ) + self.delete = to_streamed_response_wrapper( + spend_alerts.delete, + ) + + +class AsyncSpendAlertsWithStreamingResponse: + def __init__(self, spend_alerts: AsyncSpendAlerts) -> None: + self._spend_alerts = spend_alerts + + self.create = async_to_streamed_response_wrapper( + spend_alerts.create, + ) + self.update = async_to_streamed_response_wrapper( + spend_alerts.update, + ) + self.list = async_to_streamed_response_wrapper( + spend_alerts.list, + ) + self.delete = async_to_streamed_response_wrapper( + spend_alerts.delete, + ) diff --git a/src/openai/resources/admin/organization/usage.py b/src/openai/resources/admin/organization/usage.py new file mode 100644 index 0000000000..99306c6828 --- /dev/null +++ b/src/openai/resources/admin/organization/usage.py @@ -0,0 +1,2108 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ...._utils import maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...._base_client import make_request_options +from ....types.admin.organization import ( + usage_costs_params, + usage_images_params, + usage_embeddings_params, + usage_completions_params, + usage_moderations_params, + usage_vector_stores_params, + usage_audio_speeches_params, + usage_web_search_calls_params, + usage_file_search_calls_params, + usage_audio_transcriptions_params, + usage_code_interpreter_sessions_params, +) +from ....types.admin.organization.usage_costs_response import UsageCostsResponse +from ....types.admin.organization.usage_images_response import UsageImagesResponse +from ....types.admin.organization.usage_embeddings_response import UsageEmbeddingsResponse +from ....types.admin.organization.usage_completions_response import UsageCompletionsResponse +from ....types.admin.organization.usage_moderations_response import UsageModerationsResponse +from ....types.admin.organization.usage_vector_stores_response import UsageVectorStoresResponse +from ....types.admin.organization.usage_audio_speeches_response import UsageAudioSpeechesResponse +from ....types.admin.organization.usage_web_search_calls_response import UsageWebSearchCallsResponse +from ....types.admin.organization.usage_file_search_calls_response import UsageFileSearchCallsResponse +from ....types.admin.organization.usage_audio_transcriptions_response import UsageAudioTranscriptionsResponse +from ....types.admin.organization.usage_code_interpreter_sessions_response import UsageCodeInterpreterSessionsResponse + +__all__ = ["Usage", "AsyncUsage"] + + +class Usage(SyncAPIResource): + @cached_property + def with_raw_response(self) -> UsageWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return UsageWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> UsageWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return UsageWithStreamingResponse(self) + + def audio_speeches( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageAudioSpeechesResponse: + """ + Get audio speeches usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model` or any combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/usage/audio_speeches", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_audio_speeches_params.UsageAudioSpeechesParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageAudioSpeechesResponse, + ) + + def audio_transcriptions( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageAudioTranscriptionsResponse: + """ + Get audio transcriptions usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model` or any combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/usage/audio_transcriptions", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_audio_transcriptions_params.UsageAudioTranscriptionsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageAudioTranscriptionsResponse, + ) + + def code_interpreter_sessions( + self, + *, + start_time: int, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id"]] | Omit = omit, + limit: int | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageCodeInterpreterSessionsResponse: + """ + Get code interpreter sessions usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/usage/code_interpreter_sessions", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "page": page, + "project_ids": project_ids, + }, + usage_code_interpreter_sessions_params.UsageCodeInterpreterSessionsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageCodeInterpreterSessionsResponse, + ) + + def completions( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + batch: bool | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "batch", "service_tier"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageCompletionsResponse: + """ + Get completions usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + batch: If `true`, return batch jobs only. If `false`, return non-batch jobs only. By + default, return both. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model`, `batch`, `service_tier` or any + combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/usage/completions", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "batch": batch, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_completions_params.UsageCompletionsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageCompletionsResponse, + ) + + def costs( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "line_item", "api_key_id"]] | Omit = omit, + limit: int | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageCostsResponse: + """ + Get costs details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only costs for these API keys. + + bucket_width: Width of each time bucket in response. Currently only `1d` is supported, default + to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the costs by the specified fields. Support fields include `project_id`, + `line_item`, `api_key_id` and any combination of them. + + limit: A limit on the number of buckets to be returned. Limit can range between 1 and + 180, and the default is 7. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only costs for these projects. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/costs", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "page": page, + "project_ids": project_ids, + }, + usage_costs_params.UsageCostsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageCostsResponse, + ) + + def embeddings( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageEmbeddingsResponse: + """ + Get embeddings usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model` or any combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/usage/embeddings", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_embeddings_params.UsageEmbeddingsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageEmbeddingsResponse, + ) + + def file_search_calls( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "vector_store_id"]] | Omit = omit, + limit: int | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + vector_store_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageFileSearchCallsResponse: + """ + Get file search calls usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `vector_store_id` or any combination of + them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + vector_store_ids: Return only usage for these vector stores. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/usage/file_search_calls", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + "vector_store_ids": vector_store_ids, + }, + usage_file_search_calls_params.UsageFileSearchCallsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageFileSearchCallsResponse, + ) + + def images( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "size", "source"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + sizes: List[Literal["256x256", "512x512", "1024x1024", "1792x1792", "1024x1792"]] | Omit = omit, + sources: List[Literal["image.generation", "image.edit", "image.variation"]] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageImagesResponse: + """ + Get images usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model`, `size`, `source` or any + combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + sizes: Return only usages for these image sizes. Possible values are `256x256`, + `512x512`, `1024x1024`, `1792x1792`, `1024x1792` or any combination of them. + + sources: Return only usages for these sources. Possible values are `image.generation`, + `image.edit`, `image.variation` or any combination of them. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/usage/images", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "sizes": sizes, + "sources": sources, + "user_ids": user_ids, + }, + usage_images_params.UsageImagesParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageImagesResponse, + ) + + def moderations( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageModerationsResponse: + """ + Get moderations usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model` or any combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/usage/moderations", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_moderations_params.UsageModerationsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageModerationsResponse, + ) + + def vector_stores( + self, + *, + start_time: int, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id"]] | Omit = omit, + limit: int | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageVectorStoresResponse: + """ + Get vector stores usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/usage/vector_stores", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "page": page, + "project_ids": project_ids, + }, + usage_vector_stores_params.UsageVectorStoresParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageVectorStoresResponse, + ) + + def web_search_calls( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + context_levels: List[Literal["low", "medium", "high"]] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "context_level"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageWebSearchCallsResponse: + """ + Get web search calls usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + context_levels: Return only web search usage for these context levels. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model`, `context_level` or any + combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/organization/usage/web_search_calls", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "context_levels": context_levels, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_web_search_calls_params.UsageWebSearchCallsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageWebSearchCallsResponse, + ) + + +class AsyncUsage(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncUsageWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncUsageWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncUsageWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncUsageWithStreamingResponse(self) + + async def audio_speeches( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageAudioSpeechesResponse: + """ + Get audio speeches usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model` or any combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/usage/audio_speeches", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_audio_speeches_params.UsageAudioSpeechesParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageAudioSpeechesResponse, + ) + + async def audio_transcriptions( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageAudioTranscriptionsResponse: + """ + Get audio transcriptions usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model` or any combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/usage/audio_transcriptions", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_audio_transcriptions_params.UsageAudioTranscriptionsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageAudioTranscriptionsResponse, + ) + + async def code_interpreter_sessions( + self, + *, + start_time: int, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id"]] | Omit = omit, + limit: int | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageCodeInterpreterSessionsResponse: + """ + Get code interpreter sessions usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/usage/code_interpreter_sessions", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "page": page, + "project_ids": project_ids, + }, + usage_code_interpreter_sessions_params.UsageCodeInterpreterSessionsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageCodeInterpreterSessionsResponse, + ) + + async def completions( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + batch: bool | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "batch", "service_tier"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageCompletionsResponse: + """ + Get completions usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + batch: If `true`, return batch jobs only. If `false`, return non-batch jobs only. By + default, return both. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model`, `batch`, `service_tier` or any + combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/usage/completions", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "batch": batch, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_completions_params.UsageCompletionsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageCompletionsResponse, + ) + + async def costs( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "line_item", "api_key_id"]] | Omit = omit, + limit: int | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageCostsResponse: + """ + Get costs details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only costs for these API keys. + + bucket_width: Width of each time bucket in response. Currently only `1d` is supported, default + to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the costs by the specified fields. Support fields include `project_id`, + `line_item`, `api_key_id` and any combination of them. + + limit: A limit on the number of buckets to be returned. Limit can range between 1 and + 180, and the default is 7. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only costs for these projects. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/costs", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "page": page, + "project_ids": project_ids, + }, + usage_costs_params.UsageCostsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageCostsResponse, + ) + + async def embeddings( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageEmbeddingsResponse: + """ + Get embeddings usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model` or any combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/usage/embeddings", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_embeddings_params.UsageEmbeddingsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageEmbeddingsResponse, + ) + + async def file_search_calls( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "vector_store_id"]] | Omit = omit, + limit: int | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + vector_store_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageFileSearchCallsResponse: + """ + Get file search calls usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `vector_store_id` or any combination of + them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + vector_store_ids: Return only usage for these vector stores. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/usage/file_search_calls", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + "vector_store_ids": vector_store_ids, + }, + usage_file_search_calls_params.UsageFileSearchCallsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageFileSearchCallsResponse, + ) + + async def images( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "size", "source"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + sizes: List[Literal["256x256", "512x512", "1024x1024", "1792x1792", "1024x1792"]] | Omit = omit, + sources: List[Literal["image.generation", "image.edit", "image.variation"]] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageImagesResponse: + """ + Get images usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model`, `size`, `source` or any + combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + sizes: Return only usages for these image sizes. Possible values are `256x256`, + `512x512`, `1024x1024`, `1792x1792`, `1024x1792` or any combination of them. + + sources: Return only usages for these sources. Possible values are `image.generation`, + `image.edit`, `image.variation` or any combination of them. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/usage/images", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "sizes": sizes, + "sources": sources, + "user_ids": user_ids, + }, + usage_images_params.UsageImagesParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageImagesResponse, + ) + + async def moderations( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageModerationsResponse: + """ + Get moderations usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model` or any combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/usage/moderations", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_moderations_params.UsageModerationsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageModerationsResponse, + ) + + async def vector_stores( + self, + *, + start_time: int, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id"]] | Omit = omit, + limit: int | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageVectorStoresResponse: + """ + Get vector stores usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/usage/vector_stores", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "bucket_width": bucket_width, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "page": page, + "project_ids": project_ids, + }, + usage_vector_stores_params.UsageVectorStoresParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageVectorStoresResponse, + ) + + async def web_search_calls( + self, + *, + start_time: int, + api_key_ids: SequenceNotStr[str] | Omit = omit, + bucket_width: Literal["1m", "1h", "1d"] | Omit = omit, + context_levels: List[Literal["low", "medium", "high"]] | Omit = omit, + end_time: int | Omit = omit, + group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "context_level"]] | Omit = omit, + limit: int | Omit = omit, + models: SequenceNotStr[str] | Omit = omit, + page: str | Omit = omit, + project_ids: SequenceNotStr[str] | Omit = omit, + user_ids: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageWebSearchCallsResponse: + """ + Get web search calls usage details for the organization. + + Args: + start_time: Start time (Unix seconds) of the query time range, inclusive. + + api_key_ids: Return only usage for these API keys. + + bucket_width: Width of each time bucket in response. Currently `1m`, `1h` and `1d` are + supported, default to `1d`. + + context_levels: Return only web search usage for these context levels. + + end_time: End time (Unix seconds) of the query time range, exclusive. + + group_by: Group the usage data by the specified fields. Support fields include + `project_id`, `user_id`, `api_key_id`, `model`, `context_level` or any + combination of them. + + limit: Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + + models: Return only usage for these models. + + page: A cursor for use in pagination. Corresponding to the `next_page` field from the + previous response. + + project_ids: Return only usage for these projects. + + user_ids: Return only usage for these users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/organization/usage/web_search_calls", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_time": start_time, + "api_key_ids": api_key_ids, + "bucket_width": bucket_width, + "context_levels": context_levels, + "end_time": end_time, + "group_by": group_by, + "limit": limit, + "models": models, + "page": page, + "project_ids": project_ids, + "user_ids": user_ids, + }, + usage_web_search_calls_params.UsageWebSearchCallsParams, + ), + security={"admin_api_key_auth": True}, + ), + cast_to=UsageWebSearchCallsResponse, + ) + + +class UsageWithRawResponse: + def __init__(self, usage: Usage) -> None: + self._usage = usage + + self.audio_speeches = _legacy_response.to_raw_response_wrapper( + usage.audio_speeches, + ) + self.audio_transcriptions = _legacy_response.to_raw_response_wrapper( + usage.audio_transcriptions, + ) + self.code_interpreter_sessions = _legacy_response.to_raw_response_wrapper( + usage.code_interpreter_sessions, + ) + self.completions = _legacy_response.to_raw_response_wrapper( + usage.completions, + ) + self.costs = _legacy_response.to_raw_response_wrapper( + usage.costs, + ) + self.embeddings = _legacy_response.to_raw_response_wrapper( + usage.embeddings, + ) + self.file_search_calls = _legacy_response.to_raw_response_wrapper( + usage.file_search_calls, + ) + self.images = _legacy_response.to_raw_response_wrapper( + usage.images, + ) + self.moderations = _legacy_response.to_raw_response_wrapper( + usage.moderations, + ) + self.vector_stores = _legacy_response.to_raw_response_wrapper( + usage.vector_stores, + ) + self.web_search_calls = _legacy_response.to_raw_response_wrapper( + usage.web_search_calls, + ) + + +class AsyncUsageWithRawResponse: + def __init__(self, usage: AsyncUsage) -> None: + self._usage = usage + + self.audio_speeches = _legacy_response.async_to_raw_response_wrapper( + usage.audio_speeches, + ) + self.audio_transcriptions = _legacy_response.async_to_raw_response_wrapper( + usage.audio_transcriptions, + ) + self.code_interpreter_sessions = _legacy_response.async_to_raw_response_wrapper( + usage.code_interpreter_sessions, + ) + self.completions = _legacy_response.async_to_raw_response_wrapper( + usage.completions, + ) + self.costs = _legacy_response.async_to_raw_response_wrapper( + usage.costs, + ) + self.embeddings = _legacy_response.async_to_raw_response_wrapper( + usage.embeddings, + ) + self.file_search_calls = _legacy_response.async_to_raw_response_wrapper( + usage.file_search_calls, + ) + self.images = _legacy_response.async_to_raw_response_wrapper( + usage.images, + ) + self.moderations = _legacy_response.async_to_raw_response_wrapper( + usage.moderations, + ) + self.vector_stores = _legacy_response.async_to_raw_response_wrapper( + usage.vector_stores, + ) + self.web_search_calls = _legacy_response.async_to_raw_response_wrapper( + usage.web_search_calls, + ) + + +class UsageWithStreamingResponse: + def __init__(self, usage: Usage) -> None: + self._usage = usage + + self.audio_speeches = to_streamed_response_wrapper( + usage.audio_speeches, + ) + self.audio_transcriptions = to_streamed_response_wrapper( + usage.audio_transcriptions, + ) + self.code_interpreter_sessions = to_streamed_response_wrapper( + usage.code_interpreter_sessions, + ) + self.completions = to_streamed_response_wrapper( + usage.completions, + ) + self.costs = to_streamed_response_wrapper( + usage.costs, + ) + self.embeddings = to_streamed_response_wrapper( + usage.embeddings, + ) + self.file_search_calls = to_streamed_response_wrapper( + usage.file_search_calls, + ) + self.images = to_streamed_response_wrapper( + usage.images, + ) + self.moderations = to_streamed_response_wrapper( + usage.moderations, + ) + self.vector_stores = to_streamed_response_wrapper( + usage.vector_stores, + ) + self.web_search_calls = to_streamed_response_wrapper( + usage.web_search_calls, + ) + + +class AsyncUsageWithStreamingResponse: + def __init__(self, usage: AsyncUsage) -> None: + self._usage = usage + + self.audio_speeches = async_to_streamed_response_wrapper( + usage.audio_speeches, + ) + self.audio_transcriptions = async_to_streamed_response_wrapper( + usage.audio_transcriptions, + ) + self.code_interpreter_sessions = async_to_streamed_response_wrapper( + usage.code_interpreter_sessions, + ) + self.completions = async_to_streamed_response_wrapper( + usage.completions, + ) + self.costs = async_to_streamed_response_wrapper( + usage.costs, + ) + self.embeddings = async_to_streamed_response_wrapper( + usage.embeddings, + ) + self.file_search_calls = async_to_streamed_response_wrapper( + usage.file_search_calls, + ) + self.images = async_to_streamed_response_wrapper( + usage.images, + ) + self.moderations = async_to_streamed_response_wrapper( + usage.moderations, + ) + self.vector_stores = async_to_streamed_response_wrapper( + usage.vector_stores, + ) + self.web_search_calls = async_to_streamed_response_wrapper( + usage.web_search_calls, + ) diff --git a/src/openai/resources/admin/organization/users/__init__.py b/src/openai/resources/admin/organization/users/__init__.py new file mode 100644 index 0000000000..d230cb8f34 --- /dev/null +++ b/src/openai/resources/admin/organization/users/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from .users import ( + Users, + AsyncUsers, + UsersWithRawResponse, + AsyncUsersWithRawResponse, + UsersWithStreamingResponse, + AsyncUsersWithStreamingResponse, +) + +__all__ = [ + "Roles", + "AsyncRoles", + "RolesWithRawResponse", + "AsyncRolesWithRawResponse", + "RolesWithStreamingResponse", + "AsyncRolesWithStreamingResponse", + "Users", + "AsyncUsers", + "UsersWithRawResponse", + "AsyncUsersWithRawResponse", + "UsersWithStreamingResponse", + "AsyncUsersWithStreamingResponse", +] diff --git a/src/openai/resources/admin/organization/users/roles.py b/src/openai/resources/admin/organization/users/roles.py new file mode 100644 index 0000000000..6a20ab52ff --- /dev/null +++ b/src/openai/resources/admin/organization/users/roles.py @@ -0,0 +1,491 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..... import _legacy_response +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncNextCursorPage, AsyncNextCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization.users import role_list_params, role_create_params +from .....types.admin.organization.users.role_list_response import RoleListResponse +from .....types.admin.organization.users.role_create_response import RoleCreateResponse +from .....types.admin.organization.users.role_delete_response import RoleDeleteResponse +from .....types.admin.organization.users.role_retrieve_response import RoleRetrieveResponse + +__all__ = ["Roles", "AsyncRoles"] + + +class Roles(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return RolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return RolesWithStreamingResponse(self) + + def create( + self, + user_id: str, + *, + role_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleCreateResponse: + """ + Assigns an organization role to a user within the organization. + + Args: + role_id: Identifier of the role to assign. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._post( + path_template("/organization/users/{user_id}/roles", user_id=user_id), + body=maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleCreateResponse, + ) + + def retrieve( + self, + role_id: str, + *, + user_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleRetrieveResponse: + """ + Retrieves an organization role assigned to a user. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._get( + path_template("/organization/users/{user_id}/roles/{role_id}", user_id=user_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleRetrieveResponse, + ) + + def list( + self, + user_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncNextCursorPage[RoleListResponse]: + """ + Lists the organization roles assigned to a user within the organization. + + Args: + after: Cursor for pagination. Provide the value from the previous response's `next` + field to continue listing organization roles. + + limit: A limit on the number of organization role assignments to return. + + order: Sort order for the returned organization roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._get_api_list( + path_template("/organization/users/{user_id}/roles", user_id=user_id), + page=SyncNextCursorPage[RoleListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=RoleListResponse, + ) + + def delete( + self, + role_id: str, + *, + user_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Unassigns an organization role from a user within the organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return self._delete( + path_template("/organization/users/{user_id}/roles/{role_id}", user_id=user_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class AsyncRoles(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRolesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncRolesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRolesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncRolesWithStreamingResponse(self) + + async def create( + self, + user_id: str, + *, + role_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleCreateResponse: + """ + Assigns an organization role to a user within the organization. + + Args: + role_id: Identifier of the role to assign. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return await self._post( + path_template("/organization/users/{user_id}/roles", user_id=user_id), + body=await async_maybe_transform({"role_id": role_id}, role_create_params.RoleCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleCreateResponse, + ) + + async def retrieve( + self, + role_id: str, + *, + user_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleRetrieveResponse: + """ + Retrieves an organization role assigned to a user. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._get( + path_template("/organization/users/{user_id}/roles/{role_id}", user_id=user_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleRetrieveResponse, + ) + + def list( + self, + user_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[RoleListResponse, AsyncNextCursorPage[RoleListResponse]]: + """ + Lists the organization roles assigned to a user within the organization. + + Args: + after: Cursor for pagination. Provide the value from the previous response's `next` + field to continue listing organization roles. + + limit: A limit on the number of organization role assignments to return. + + order: Sort order for the returned organization roles. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._get_api_list( + path_template("/organization/users/{user_id}/roles", user_id=user_id), + page=AsyncNextCursorPage[RoleListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + role_list_params.RoleListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=RoleListResponse, + ) + + async def delete( + self, + role_id: str, + *, + user_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RoleDeleteResponse: + """ + Unassigns an organization role from a user within the organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + if not role_id: + raise ValueError(f"Expected a non-empty value for `role_id` but received {role_id!r}") + return await self._delete( + path_template("/organization/users/{user_id}/roles/{role_id}", user_id=user_id, role_id=role_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=RoleDeleteResponse, + ) + + +class RolesWithRawResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = _legacy_response.to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + roles.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithRawResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = _legacy_response.async_to_raw_response_wrapper( + roles.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + roles.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + roles.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + roles.delete, + ) + + +class RolesWithStreamingResponse: + def __init__(self, roles: Roles) -> None: + self._roles = roles + + self.create = to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = to_streamed_response_wrapper( + roles.retrieve, + ) + self.list = to_streamed_response_wrapper( + roles.list, + ) + self.delete = to_streamed_response_wrapper( + roles.delete, + ) + + +class AsyncRolesWithStreamingResponse: + def __init__(self, roles: AsyncRoles) -> None: + self._roles = roles + + self.create = async_to_streamed_response_wrapper( + roles.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + roles.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + roles.list, + ) + self.delete = async_to_streamed_response_wrapper( + roles.delete, + ) diff --git a/src/openai/resources/admin/organization/users/users.py b/src/openai/resources/admin/organization/users/users.py new file mode 100644 index 0000000000..94eab69e83 --- /dev/null +++ b/src/openai/resources/admin/organization/users/users.py @@ -0,0 +1,543 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ..... import _legacy_response +from .roles import ( + Roles, + AsyncRoles, + RolesWithRawResponse, + AsyncRolesWithRawResponse, + RolesWithStreamingResponse, + AsyncRolesWithStreamingResponse, +) +from ....._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform +from ....._compat import cached_property +from ....._resource import SyncAPIResource, AsyncAPIResource +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .....pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ....._base_client import AsyncPaginator, make_request_options +from .....types.admin.organization import user_list_params, user_update_params +from .....types.admin.organization.organization_user import OrganizationUser +from .....types.admin.organization.user_delete_response import UserDeleteResponse + +__all__ = ["Users", "AsyncUsers"] + + +class Users(SyncAPIResource): + @cached_property + def roles(self) -> Roles: + return Roles(self._client) + + @cached_property + def with_raw_response(self) -> UsersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return UsersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> UsersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return UsersWithStreamingResponse(self) + + def retrieve( + self, + user_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationUser: + """ + Retrieves a user by their identifier. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._get( + path_template("/organization/users/{user_id}", user_id=user_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationUser, + ) + + def update( + self, + user_id: str, + *, + developer_persona: Optional[str] | Omit = omit, + role: Optional[str] | Omit = omit, + role_id: Optional[str] | Omit = omit, + technical_level: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationUser: + """ + Modifies a user's role in the organization. + + Args: + developer_persona: Developer persona metadata. + + role: `owner` or `reader` + + role_id: Role ID to assign to the user. + + technical_level: Technical level metadata. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._post( + path_template("/organization/users/{user_id}", user_id=user_id), + body=maybe_transform( + { + "developer_persona": developer_persona, + "role": role, + "role_id": role_id, + "technical_level": technical_level, + }, + user_update_params.UserUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationUser, + ) + + def list( + self, + *, + after: str | Omit = omit, + emails: SequenceNotStr[str] | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[OrganizationUser]: + """ + Lists all of the users in the organization. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + emails: Filter by the email address of users. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/users", + page=SyncConversationCursorPage[OrganizationUser], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "emails": emails, + "limit": limit, + }, + user_list_params.UserListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=OrganizationUser, + ) + + def delete( + self, + user_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UserDeleteResponse: + """ + Deletes a user from the organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return self._delete( + path_template("/organization/users/{user_id}", user_id=user_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=UserDeleteResponse, + ) + + +class AsyncUsers(AsyncAPIResource): + @cached_property + def roles(self) -> AsyncRoles: + return AsyncRoles(self._client) + + @cached_property + def with_raw_response(self) -> AsyncUsersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncUsersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncUsersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncUsersWithStreamingResponse(self) + + async def retrieve( + self, + user_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationUser: + """ + Retrieves a user by their identifier. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return await self._get( + path_template("/organization/users/{user_id}", user_id=user_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationUser, + ) + + async def update( + self, + user_id: str, + *, + developer_persona: Optional[str] | Omit = omit, + role: Optional[str] | Omit = omit, + role_id: Optional[str] | Omit = omit, + technical_level: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OrganizationUser: + """ + Modifies a user's role in the organization. + + Args: + developer_persona: Developer persona metadata. + + role: `owner` or `reader` + + role_id: Role ID to assign to the user. + + technical_level: Technical level metadata. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return await self._post( + path_template("/organization/users/{user_id}", user_id=user_id), + body=await async_maybe_transform( + { + "developer_persona": developer_persona, + "role": role, + "role_id": role_id, + "technical_level": technical_level, + }, + user_update_params.UserUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=OrganizationUser, + ) + + def list( + self, + *, + after: str | Omit = omit, + emails: SequenceNotStr[str] | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[OrganizationUser, AsyncConversationCursorPage[OrganizationUser]]: + """ + Lists all of the users in the organization. + + Args: + after: A cursor for use in pagination. `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + emails: Filter by the email address of users. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/users", + page=AsyncConversationCursorPage[OrganizationUser], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "emails": emails, + "limit": limit, + }, + user_list_params.UserListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=OrganizationUser, + ) + + async def delete( + self, + user_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UserDeleteResponse: + """ + Deletes a user from the organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not user_id: + raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") + return await self._delete( + path_template("/organization/users/{user_id}", user_id=user_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=UserDeleteResponse, + ) + + +class UsersWithRawResponse: + def __init__(self, users: Users) -> None: + self._users = users + + self.retrieve = _legacy_response.to_raw_response_wrapper( + users.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + users.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + users.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + users.delete, + ) + + @cached_property + def roles(self) -> RolesWithRawResponse: + return RolesWithRawResponse(self._users.roles) + + +class AsyncUsersWithRawResponse: + def __init__(self, users: AsyncUsers) -> None: + self._users = users + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + users.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + users.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + users.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + users.delete, + ) + + @cached_property + def roles(self) -> AsyncRolesWithRawResponse: + return AsyncRolesWithRawResponse(self._users.roles) + + +class UsersWithStreamingResponse: + def __init__(self, users: Users) -> None: + self._users = users + + self.retrieve = to_streamed_response_wrapper( + users.retrieve, + ) + self.update = to_streamed_response_wrapper( + users.update, + ) + self.list = to_streamed_response_wrapper( + users.list, + ) + self.delete = to_streamed_response_wrapper( + users.delete, + ) + + @cached_property + def roles(self) -> RolesWithStreamingResponse: + return RolesWithStreamingResponse(self._users.roles) + + +class AsyncUsersWithStreamingResponse: + def __init__(self, users: AsyncUsers) -> None: + self._users = users + + self.retrieve = async_to_streamed_response_wrapper( + users.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + users.update, + ) + self.list = async_to_streamed_response_wrapper( + users.list, + ) + self.delete = async_to_streamed_response_wrapper( + users.delete, + ) + + @cached_property + def roles(self) -> AsyncRolesWithStreamingResponse: + return AsyncRolesWithStreamingResponse(self._users.roles) diff --git a/src/openai/resources/audio/audio.py b/src/openai/resources/audio/audio.py index 18bd7b812c..040a058df6 100644 --- a/src/openai/resources/audio/audio.py +++ b/src/openai/resources/audio/audio.py @@ -35,20 +35,23 @@ class Audio(SyncAPIResource): @cached_property def transcriptions(self) -> Transcriptions: + """Turn audio into text or text into audio.""" return Transcriptions(self._client) @cached_property def translations(self) -> Translations: + """Turn audio into text or text into audio.""" return Translations(self._client) @cached_property def speech(self) -> Speech: + """Turn audio into text or text into audio.""" return Speech(self._client) @cached_property def with_raw_response(self) -> AudioWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -68,20 +71,23 @@ def with_streaming_response(self) -> AudioWithStreamingResponse: class AsyncAudio(AsyncAPIResource): @cached_property def transcriptions(self) -> AsyncTranscriptions: + """Turn audio into text or text into audio.""" return AsyncTranscriptions(self._client) @cached_property def translations(self) -> AsyncTranslations: + """Turn audio into text or text into audio.""" return AsyncTranslations(self._client) @cached_property def speech(self) -> AsyncSpeech: + """Turn audio into text or text into audio.""" return AsyncSpeech(self._client) @cached_property def with_raw_response(self) -> AsyncAudioWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -104,14 +110,17 @@ def __init__(self, audio: Audio) -> None: @cached_property def transcriptions(self) -> TranscriptionsWithRawResponse: + """Turn audio into text or text into audio.""" return TranscriptionsWithRawResponse(self._audio.transcriptions) @cached_property def translations(self) -> TranslationsWithRawResponse: + """Turn audio into text or text into audio.""" return TranslationsWithRawResponse(self._audio.translations) @cached_property def speech(self) -> SpeechWithRawResponse: + """Turn audio into text or text into audio.""" return SpeechWithRawResponse(self._audio.speech) @@ -121,14 +130,17 @@ def __init__(self, audio: AsyncAudio) -> None: @cached_property def transcriptions(self) -> AsyncTranscriptionsWithRawResponse: + """Turn audio into text or text into audio.""" return AsyncTranscriptionsWithRawResponse(self._audio.transcriptions) @cached_property def translations(self) -> AsyncTranslationsWithRawResponse: + """Turn audio into text or text into audio.""" return AsyncTranslationsWithRawResponse(self._audio.translations) @cached_property def speech(self) -> AsyncSpeechWithRawResponse: + """Turn audio into text or text into audio.""" return AsyncSpeechWithRawResponse(self._audio.speech) @@ -138,14 +150,17 @@ def __init__(self, audio: Audio) -> None: @cached_property def transcriptions(self) -> TranscriptionsWithStreamingResponse: + """Turn audio into text or text into audio.""" return TranscriptionsWithStreamingResponse(self._audio.transcriptions) @cached_property def translations(self) -> TranslationsWithStreamingResponse: + """Turn audio into text or text into audio.""" return TranslationsWithStreamingResponse(self._audio.translations) @cached_property def speech(self) -> SpeechWithStreamingResponse: + """Turn audio into text or text into audio.""" return SpeechWithStreamingResponse(self._audio.speech) @@ -155,12 +170,15 @@ def __init__(self, audio: AsyncAudio) -> None: @cached_property def transcriptions(self) -> AsyncTranscriptionsWithStreamingResponse: + """Turn audio into text or text into audio.""" return AsyncTranscriptionsWithStreamingResponse(self._audio.transcriptions) @cached_property def translations(self) -> AsyncTranslationsWithStreamingResponse: + """Turn audio into text or text into audio.""" return AsyncTranslationsWithStreamingResponse(self._audio.translations) @cached_property def speech(self) -> AsyncSpeechWithStreamingResponse: + """Turn audio into text or text into audio.""" return AsyncSpeechWithStreamingResponse(self._audio.speech) diff --git a/src/openai/resources/audio/speech.py b/src/openai/resources/audio/speech.py index 6085ae8afe..3c05d690dd 100644 --- a/src/openai/resources/audio/speech.py +++ b/src/openai/resources/audio/speech.py @@ -8,11 +8,8 @@ import httpx from ... import _legacy_response -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -29,10 +26,12 @@ class Speech(SyncAPIResource): + """Turn audio into text or text into audio.""" + @cached_property def with_raw_response(self) -> SpeechWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -53,30 +52,39 @@ def create( *, input: str, model: Union[str, SpeechModel], - voice: Literal["alloy", "echo", "fable", "onyx", "nova", "shimmer"], - response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] | NotGiven = NOT_GIVEN, - speed: float | NotGiven = NOT_GIVEN, + voice: speech_create_params.Voice, + instructions: str | Omit = omit, + response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] | Omit = omit, + speed: float | Omit = omit, + stream_format: Literal["sse", "audio"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Generates audio from the input text. + Returns the audio file content, or a stream of audio events. + Args: input: The text to generate audio for. The maximum length is 4096 characters. model: - One of the available [TTS models](https://platform.openai.com/docs/models/tts): - `tts-1` or `tts-1-hd` + One of the available [TTS models](https://platform.openai.com/docs/models#tts): + `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`, or `gpt-4o-mini-tts-2025-12-15`. - voice: The voice to use when generating the audio. Supported voices are `alloy`, - `echo`, `fable`, `onyx`, `nova`, and `shimmer`. Previews of the voices are - available in the - [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech/voice-options). + voice: The voice to use when generating the audio. Supported built-in voices are + `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`, + `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice + object with an `id`, for example `{ "id": "voice_1234" }`. Previews of the + voices are available in the + [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech#voice-options). + + instructions: Control the voice of your generated audio with additional instructions. Does not + work with `tts-1` or `tts-1-hd`. response_format: The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`. @@ -84,6 +92,9 @@ def create( speed: The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default. + stream_format: The format to stream the audio in. Supported formats are `sse` and `audio`. + `sse` is not supported for `tts-1` or `tts-1-hd`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -100,23 +111,31 @@ def create( "input": input, "model": model, "voice": voice, + "instructions": instructions, "response_format": response_format, "speed": speed, + "stream_format": stream_format, }, speech_create_params.SpeechCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=_legacy_response.HttpxBinaryResponseContent, ) class AsyncSpeech(AsyncAPIResource): + """Turn audio into text or text into audio.""" + @cached_property def with_raw_response(self) -> AsyncSpeechWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -137,30 +156,39 @@ async def create( *, input: str, model: Union[str, SpeechModel], - voice: Literal["alloy", "echo", "fable", "onyx", "nova", "shimmer"], - response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] | NotGiven = NOT_GIVEN, - speed: float | NotGiven = NOT_GIVEN, + voice: speech_create_params.Voice, + instructions: str | Omit = omit, + response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] | Omit = omit, + speed: float | Omit = omit, + stream_format: Literal["sse", "audio"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Generates audio from the input text. + Returns the audio file content, or a stream of audio events. + Args: input: The text to generate audio for. The maximum length is 4096 characters. model: - One of the available [TTS models](https://platform.openai.com/docs/models/tts): - `tts-1` or `tts-1-hd` + One of the available [TTS models](https://platform.openai.com/docs/models#tts): + `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`, or `gpt-4o-mini-tts-2025-12-15`. - voice: The voice to use when generating the audio. Supported voices are `alloy`, - `echo`, `fable`, `onyx`, `nova`, and `shimmer`. Previews of the voices are - available in the - [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech/voice-options). + voice: The voice to use when generating the audio. Supported built-in voices are + `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`, + `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice + object with an `id`, for example `{ "id": "voice_1234" }`. Previews of the + voices are available in the + [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech#voice-options). + + instructions: Control the voice of your generated audio with additional instructions. Does not + work with `tts-1` or `tts-1-hd`. response_format: The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`. @@ -168,6 +196,9 @@ async def create( speed: The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default. + stream_format: The format to stream the audio in. Supported formats are `sse` and `audio`. + `sse` is not supported for `tts-1` or `tts-1-hd`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -184,13 +215,19 @@ async def create( "input": input, "model": model, "voice": voice, + "instructions": instructions, "response_format": response_format, "speed": speed, + "stream_format": stream_format, }, speech_create_params.SpeechCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=_legacy_response.HttpxBinaryResponseContent, ) diff --git a/src/openai/resources/audio/transcriptions.py b/src/openai/resources/audio/transcriptions.py index a6009143d4..ab2480ef11 100644 --- a/src/openai/resources/audio/transcriptions.py +++ b/src/openai/resources/audio/transcriptions.py @@ -2,35 +2,53 @@ from __future__ import annotations -from typing import List, Union, Mapping, cast -from typing_extensions import Literal +import logging +from typing import TYPE_CHECKING, List, Union, Mapping, Optional, cast +from typing_extensions import Literal, overload, assert_never import httpx from ... import _legacy_response -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from ..._utils import ( - extract_files, - maybe_transform, - deepcopy_minimal, - async_maybe_transform, +from ..._files import deepcopy_with_paths +from ..._types import ( + Body, + Omit, + Query, + Headers, + NotGiven, + FileTypes, + SequenceNotStr, + omit, + not_given, ) +from ..._utils import extract_files, required_args, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._streaming import Stream, AsyncStream from ...types.audio import transcription_create_params from ..._base_client import make_request_options from ...types.audio_model import AudioModel from ...types.audio.transcription import Transcription +from ...types.audio_response_format import AudioResponseFormat +from ...types.audio.transcription_include import TranscriptionInclude +from ...types.audio.transcription_verbose import TranscriptionVerbose +from ...types.audio.transcription_diarized import TranscriptionDiarized +from ...types.audio.transcription_stream_event import TranscriptionStreamEvent +from ...types.audio.transcription_create_response import TranscriptionCreateResponse __all__ = ["Transcriptions", "AsyncTranscriptions"] +log: logging.Logger = logging.getLogger("openai.audio.transcriptions") + class Transcriptions(SyncAPIResource): + """Turn audio into text or text into audio.""" + @cached_property def with_raw_response(self) -> TranscriptionsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -46,45 +64,76 @@ def with_streaming_response(self) -> TranscriptionsWithStreamingResponse: """ return TranscriptionsWithStreamingResponse(self) + @overload def create( self, *, file: FileTypes, model: Union[str, AudioModel], - language: str | NotGiven = NOT_GIVEN, - prompt: str | NotGiven = NOT_GIVEN, - response_format: Literal["json", "text", "srt", "verbose_json", "vtt"] | NotGiven = NOT_GIVEN, - temperature: float | NotGiven = NOT_GIVEN, - timestamp_granularities: List[Literal["word", "segment"]] | NotGiven = NOT_GIVEN, + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + language: str | Omit = omit, + prompt: str | Omit = omit, + response_format: Union[Literal["json"], Omit] = omit, + stream: Optional[Literal[False]] | Omit = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Transcription: """ Transcribes audio into the input language. + Returns a transcription object in `json`, `diarized_json`, or `verbose_json` + format, or a stream of transcript events. + Args: file: The audio file object (not file name) to transcribe, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. - model: ID of the model to use. Only `whisper-1` (which is powered by our open source - Whisper V2 model) is currently available. + model: ID of the model to use. The options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1` + (which is powered by our open source Whisper V2 model), and + `gpt-4o-transcribe-diarize`. + + chunking_strategy: Controls how the audio is cut into chunks. When set to `"auto"`, the server + first normalizes loudness and then uses voice activity detection (VAD) to choose + boundaries. `server_vad` object can be provided to tweak VAD detection + parameters manually. If unset, the audio is transcribed as a single block. + + include: Additional information to include in the transcription response. `logprobs` will + return the log probabilities of the tokens in the response to understand the + model's confidence in the transcription. `logprobs` only works with + response_format set to `json` and only with the models `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is + not supported when using `gpt-4o-transcribe-diarize`. language: The language of the input audio. Supplying the input language in - [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format will - improve accuracy and latency. + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. prompt: An optional text to guide the model's style or continue a previous audio segment. The - [prompt](https://platform.openai.com/docs/guides/speech-to-text/prompting) + [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) should match the audio language. - response_format: The format of the transcript output, in one of these options: `json`, `text`, - `srt`, `verbose_json`, or `vtt`. + response_format: The format of the output, in one of these options: `json`, `text`, `srt`, + `verbose_json`, or `vtt`. For `gpt-4o-transcribe` and `gpt-4o-mini-transcribe`, + the only supported format is `json`. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions) + for more information. + + Note: Streaming is not supported for the `whisper-1` model and will be ignored. temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and @@ -101,43 +150,367 @@ def create( extra_headers: Send extra headers extra_query: Add additional query parameters to the request + """ + + @overload + def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + response_format: Literal["verbose_json"], + language: str | Omit = omit, + prompt: str | Omit = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TranscriptionVerbose: ... + + @overload + def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + response_format: Literal["text", "srt", "vtt"], + include: List[TranscriptionInclude] | Omit = omit, + language: str | Omit = omit, + prompt: str | Omit = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> str: ... + + @overload + def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + response_format: Literal["diarized_json"], + known_speaker_names: SequenceNotStr[str] | Omit = omit, + known_speaker_references: SequenceNotStr[str] | Omit = omit, + language: str | Omit = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TranscriptionDiarized: ... + + @overload + def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + stream: Literal[True], + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + known_speaker_names: SequenceNotStr[str] | Omit = omit, + known_speaker_references: SequenceNotStr[str] | Omit = omit, + language: str | Omit = omit, + prompt: str | Omit = omit, + response_format: Union[AudioResponseFormat, Omit] = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Stream[TranscriptionStreamEvent]: + """ + Transcribes audio into the input language. + + Returns a transcription object in `json`, `diarized_json`, or `verbose_json` + format, or a stream of transcript events. + + Args: + file: + The audio file object (not file name) to transcribe, in one of these formats: + flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. + + model: ID of the model to use. The options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1` + (which is powered by our open source Whisper V2 model), and + `gpt-4o-transcribe-diarize`. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions) + for more information. + + Note: Streaming is not supported for the `whisper-1` model and will be ignored. + + chunking_strategy: Controls how the audio is cut into chunks. When set to `"auto"`, the server + first normalizes loudness and then uses voice activity detection (VAD) to choose + boundaries. `server_vad` object can be provided to tweak VAD detection + parameters manually. If unset, the audio is transcribed as a single block. + Required when using `gpt-4o-transcribe-diarize` for inputs longer than 30 + seconds. + + include: Additional information to include in the transcription response. `logprobs` will + return the log probabilities of the tokens in the response to understand the + model's confidence in the transcription. `logprobs` only works with + response_format set to `json` and only with the models `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is + not supported when using `gpt-4o-transcribe-diarize`. + + known_speaker_names: Optional list of speaker names that correspond to the audio samples provided in + `known_speaker_references[]`. Each entry should be a short identifier (for + example `customer` or `agent`). Up to 4 speakers are supported. + + known_speaker_references: Optional list of audio samples (as + [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)) + that contain known speaker references matching `known_speaker_names[]`. Each + sample must be between 2 and 10 seconds, and can use any of the same input audio + formats supported by `file`. + + language: The language of the input audio. Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + + prompt: An optional text to guide the model's style or continue a previous audio + segment. The + [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) + should match the audio language. This field is not supported when using + `gpt-4o-transcribe-diarize`. + + response_format: The format of the output, in one of these options: `json`, `text`, `srt`, + `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and + `gpt-4o-mini-transcribe`, the only supported format is `json`. For + `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and + `diarized_json`, with `diarized_json` required to receive speaker annotations. + + temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the + output more random, while lower values like 0.2 will make it more focused and + deterministic. If set to 0, the model will use + [log probability](https://en.wikipedia.org/wiki/Log_probability) to + automatically increase the temperature until certain thresholds are hit. + + timestamp_granularities: The timestamp granularities to populate for this transcription. + `response_format` must be set `verbose_json` to use timestamp granularities. + Either or both of these options are supported: `word`, or `segment`. Note: There + is no additional latency for segment timestamps, but generating word timestamps + incurs additional latency. This option is not available for + `gpt-4o-transcribe-diarize`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + stream: bool, + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + known_speaker_names: SequenceNotStr[str] | Omit = omit, + known_speaker_references: SequenceNotStr[str] | Omit = omit, + language: str | Omit = omit, + prompt: str | Omit = omit, + response_format: Union[AudioResponseFormat, Omit] = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TranscriptionCreateResponse | Stream[TranscriptionStreamEvent]: + """ + Transcribes audio into the input language. + + Returns a transcription object in `json`, `diarized_json`, or `verbose_json` + format, or a stream of transcript events. + + Args: + file: + The audio file object (not file name) to transcribe, in one of these formats: + flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. + + model: ID of the model to use. The options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1` + (which is powered by our open source Whisper V2 model), and + `gpt-4o-transcribe-diarize`. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions) + for more information. + + Note: Streaming is not supported for the `whisper-1` model and will be ignored. + + chunking_strategy: Controls how the audio is cut into chunks. When set to `"auto"`, the server + first normalizes loudness and then uses voice activity detection (VAD) to choose + boundaries. `server_vad` object can be provided to tweak VAD detection + parameters manually. If unset, the audio is transcribed as a single block. + Required when using `gpt-4o-transcribe-diarize` for inputs longer than 30 + seconds. + + include: Additional information to include in the transcription response. `logprobs` will + return the log probabilities of the tokens in the response to understand the + model's confidence in the transcription. `logprobs` only works with + response_format set to `json` and only with the models `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is + not supported when using `gpt-4o-transcribe-diarize`. + + known_speaker_names: Optional list of speaker names that correspond to the audio samples provided in + `known_speaker_references[]`. Each entry should be a short identifier (for + example `customer` or `agent`). Up to 4 speakers are supported. + + known_speaker_references: Optional list of audio samples (as + [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)) + that contain known speaker references matching `known_speaker_names[]`. Each + sample must be between 2 and 10 seconds, and can use any of the same input audio + formats supported by `file`. + + language: The language of the input audio. Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + + prompt: An optional text to guide the model's style or continue a previous audio + segment. The + [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) + should match the audio language. This field is not supported when using + `gpt-4o-transcribe-diarize`. + + response_format: The format of the output, in one of these options: `json`, `text`, `srt`, + `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and + `gpt-4o-mini-transcribe`, the only supported format is `json`. For + `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and + `diarized_json`, with `diarized_json` required to receive speaker annotations. + + temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the + output more random, while lower values like 0.2 will make it more focused and + deterministic. If set to 0, the model will use + [log probability](https://en.wikipedia.org/wiki/Log_probability) to + automatically increase the temperature until certain thresholds are hit. + + timestamp_granularities: The timestamp granularities to populate for this transcription. + `response_format` must be set `verbose_json` to use timestamp granularities. + Either or both of these options are supported: `word`, or `segment`. Note: There + is no additional latency for segment timestamps, but generating word timestamps + incurs additional latency. This option is not available for + `gpt-4o-transcribe-diarize`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + ... + + @required_args(["file", "model"], ["file", "model", "stream"]) + def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + known_speaker_names: SequenceNotStr[str] | Omit = omit, + known_speaker_references: SequenceNotStr[str] | Omit = omit, + language: str | Omit = omit, + prompt: str | Omit = omit, + response_format: Union[AudioResponseFormat, Omit] = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> str | Transcription | TranscriptionDiarized | TranscriptionVerbose | Stream[TranscriptionStreamEvent]: + body = deepcopy_with_paths( { "file": file, "model": model, + "chunking_strategy": chunking_strategy, + "include": include, + "known_speaker_names": known_speaker_names, + "known_speaker_references": known_speaker_references, "language": language, "prompt": prompt, "response_format": response_format, + "stream": stream, "temperature": temperature, "timestamp_granularities": timestamp_granularities, - } + }, + [["file"]], ) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be # sent to the server will contain a `boundary` parameter, e.g. # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} - return self._post( + return self._post( # type: ignore[return-value] "/audio/transcriptions", - body=maybe_transform(body, transcription_create_params.TranscriptionCreateParams), + body=maybe_transform( + body, + transcription_create_params.TranscriptionCreateParamsStreaming + if stream + else transcription_create_params.TranscriptionCreateParamsNonStreaming, + ), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), - cast_to=Transcription, + cast_to=_get_response_format_type(response_format), + stream=stream or False, + stream_cls=Stream[TranscriptionStreamEvent], ) class AsyncTranscriptions(AsyncAPIResource): + """Turn audio into text or text into audio.""" + @cached_property def with_raw_response(self) -> AsyncTranscriptionsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -153,45 +526,93 @@ def with_streaming_response(self) -> AsyncTranscriptionsWithStreamingResponse: """ return AsyncTranscriptionsWithStreamingResponse(self) + @overload async def create( self, *, file: FileTypes, model: Union[str, AudioModel], - language: str | NotGiven = NOT_GIVEN, - prompt: str | NotGiven = NOT_GIVEN, - response_format: Literal["json", "text", "srt", "verbose_json", "vtt"] | NotGiven = NOT_GIVEN, - temperature: float | NotGiven = NOT_GIVEN, - timestamp_granularities: List[Literal["word", "segment"]] | NotGiven = NOT_GIVEN, + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + known_speaker_names: SequenceNotStr[str] | Omit = omit, + known_speaker_references: SequenceNotStr[str] | Omit = omit, + language: str | Omit = omit, + prompt: str | Omit = omit, + response_format: Union[Literal["json"], Omit] = omit, + stream: Optional[Literal[False]] | Omit = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Transcription: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TranscriptionCreateResponse: """ Transcribes audio into the input language. + Returns a transcription object in `json`, `diarized_json`, or `verbose_json` + format, or a stream of transcript events. + Args: file: The audio file object (not file name) to transcribe, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. - model: ID of the model to use. Only `whisper-1` (which is powered by our open source - Whisper V2 model) is currently available. + model: ID of the model to use. The options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1` + (which is powered by our open source Whisper V2 model), and + `gpt-4o-transcribe-diarize`. + + chunking_strategy: Controls how the audio is cut into chunks. When set to `"auto"`, the server + first normalizes loudness and then uses voice activity detection (VAD) to choose + boundaries. `server_vad` object can be provided to tweak VAD detection + parameters manually. If unset, the audio is transcribed as a single block. + Required when using `gpt-4o-transcribe-diarize` for inputs longer than 30 + seconds. + + include: Additional information to include in the transcription response. `logprobs` will + return the log probabilities of the tokens in the response to understand the + model's confidence in the transcription. `logprobs` only works with + response_format set to `json` and only with the models `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is + not supported when using `gpt-4o-transcribe-diarize`. + + known_speaker_names: Optional list of speaker names that correspond to the audio samples provided in + `known_speaker_references[]`. Each entry should be a short identifier (for + example `customer` or `agent`). Up to 4 speakers are supported. + + known_speaker_references: Optional list of audio samples (as + [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)) + that contain known speaker references matching `known_speaker_names[]`. Each + sample must be between 2 and 10 seconds, and can use any of the same input audio + formats supported by `file`. language: The language of the input audio. Supplying the input language in - [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format will - improve accuracy and latency. + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. prompt: An optional text to guide the model's style or continue a previous audio segment. The - [prompt](https://platform.openai.com/docs/guides/speech-to-text/prompting) - should match the audio language. + [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) + should match the audio language. This field is not supported when using + `gpt-4o-transcribe-diarize`. + + response_format: The format of the output, in one of these options: `json`, `text`, `srt`, + `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and + `gpt-4o-mini-transcribe`, the only supported format is `json`. For + `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and + `diarized_json`, with `diarized_json` required to receive speaker annotations. - response_format: The format of the transcript output, in one of these options: `json`, `text`, - `srt`, `verbose_json`, or `vtt`. + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions) + for more information. + + Note: Streaming is not supported for the `whisper-1` model and will be ignored. temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and @@ -203,7 +624,156 @@ async def create( `response_format` must be set `verbose_json` to use timestamp granularities. Either or both of these options are supported: `word`, or `segment`. Note: There is no additional latency for segment timestamps, but generating word timestamps - incurs additional latency. + incurs additional latency. This option is not available for + `gpt-4o-transcribe-diarize`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + """ + + @overload + async def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + response_format: Literal["verbose_json"], + language: str | Omit = omit, + prompt: str | Omit = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TranscriptionVerbose: ... + + @overload + async def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + response_format: Literal["text", "srt", "vtt"], + language: str | Omit = omit, + prompt: str | Omit = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> str: ... + + @overload + async def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + stream: Literal[True], + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + known_speaker_names: SequenceNotStr[str] | Omit = omit, + known_speaker_references: SequenceNotStr[str] | Omit = omit, + language: str | Omit = omit, + prompt: str | Omit = omit, + response_format: Union[AudioResponseFormat, Omit] = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncStream[TranscriptionStreamEvent]: + """ + Transcribes audio into the input language. + + Returns a transcription object in `json`, `diarized_json`, or `verbose_json` + format, or a stream of transcript events. + + Args: + file: + The audio file object (not file name) to transcribe, in one of these formats: + flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. + + model: ID of the model to use. The options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1` + (which is powered by our open source Whisper V2 model), and + `gpt-4o-transcribe-diarize`. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions) + for more information. + + Note: Streaming is not supported for the `whisper-1` model and will be ignored. + + chunking_strategy: Controls how the audio is cut into chunks. When set to `"auto"`, the server + first normalizes loudness and then uses voice activity detection (VAD) to choose + boundaries. `server_vad` object can be provided to tweak VAD detection + parameters manually. If unset, the audio is transcribed as a single block. + Required when using `gpt-4o-transcribe-diarize` for inputs longer than 30 + seconds. + + include: Additional information to include in the transcription response. `logprobs` will + return the log probabilities of the tokens in the response to understand the + model's confidence in the transcription. `logprobs` only works with + response_format set to `json` and only with the models `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is + not supported when using `gpt-4o-transcribe-diarize`. + + known_speaker_names: Optional list of speaker names that correspond to the audio samples provided in + `known_speaker_references[]`. Each entry should be a short identifier (for + example `customer` or `agent`). Up to 4 speakers are supported. + + known_speaker_references: Optional list of audio samples (as + [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)) + that contain known speaker references matching `known_speaker_names[]`. Each + sample must be between 2 and 10 seconds, and can use any of the same input audio + formats supported by `file`. + + language: The language of the input audio. Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + + prompt: An optional text to guide the model's style or continue a previous audio + segment. The + [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) + should match the audio language. This field is not supported when using + `gpt-4o-transcribe-diarize`. + + response_format: The format of the output, in one of these options: `json`, `text`, `srt`, + `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and + `gpt-4o-mini-transcribe`, the only supported format is `json`. For + `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and + `diarized_json`, with `diarized_json` required to receive speaker annotations. + + temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the + output more random, while lower values like 0.2 will make it more focused and + deterministic. If set to 0, the model will use + [log probability](https://en.wikipedia.org/wiki/Log_probability) to + automatically increase the temperature until certain thresholds are hit. + + timestamp_granularities: The timestamp granularities to populate for this transcription. + `response_format` must be set `verbose_json` to use timestamp granularities. + Either or both of these options are supported: `word`, or `segment`. Note: There + is no additional latency for segment timestamps, but generating word timestamps + incurs additional latency. This option is not available for + `gpt-4o-transcribe-diarize`. extra_headers: Send extra headers @@ -213,16 +783,158 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + ... + + @overload + async def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + stream: bool, + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + known_speaker_names: SequenceNotStr[str] | Omit = omit, + known_speaker_references: SequenceNotStr[str] | Omit = omit, + language: str | Omit = omit, + prompt: str | Omit = omit, + response_format: Union[AudioResponseFormat, Omit] = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TranscriptionCreateResponse | AsyncStream[TranscriptionStreamEvent]: + """ + Transcribes audio into the input language. + + Returns a transcription object in `json`, `diarized_json`, or `verbose_json` + format, or a stream of transcript events. + + Args: + file: + The audio file object (not file name) to transcribe, in one of these formats: + flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. + + model: ID of the model to use. The options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1` + (which is powered by our open source Whisper V2 model), and + `gpt-4o-transcribe-diarize`. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions) + for more information. + + Note: Streaming is not supported for the `whisper-1` model and will be ignored. + + chunking_strategy: Controls how the audio is cut into chunks. When set to `"auto"`, the server + first normalizes loudness and then uses voice activity detection (VAD) to choose + boundaries. `server_vad` object can be provided to tweak VAD detection + parameters manually. If unset, the audio is transcribed as a single block. + Required when using `gpt-4o-transcribe-diarize` for inputs longer than 30 + seconds. + + include: Additional information to include in the transcription response. `logprobs` will + return the log probabilities of the tokens in the response to understand the + model's confidence in the transcription. `logprobs` only works with + response_format set to `json` and only with the models `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is + not supported when using `gpt-4o-transcribe-diarize`. + + known_speaker_names: Optional list of speaker names that correspond to the audio samples provided in + `known_speaker_references[]`. Each entry should be a short identifier (for + example `customer` or `agent`). Up to 4 speakers are supported. + + known_speaker_references: Optional list of audio samples (as + [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)) + that contain known speaker references matching `known_speaker_names[]`. Each + sample must be between 2 and 10 seconds, and can use any of the same input audio + formats supported by `file`. + + language: The language of the input audio. Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + + prompt: An optional text to guide the model's style or continue a previous audio + segment. The + [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) + should match the audio language. This field is not supported when using + `gpt-4o-transcribe-diarize`. + + response_format: The format of the output, in one of these options: `json`, `text`, `srt`, + `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and + `gpt-4o-mini-transcribe`, the only supported format is `json`. For + `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and + `diarized_json`, with `diarized_json` required to receive speaker annotations. + + temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the + output more random, while lower values like 0.2 will make it more focused and + deterministic. If set to 0, the model will use + [log probability](https://en.wikipedia.org/wiki/Log_probability) to + automatically increase the temperature until certain thresholds are hit. + + timestamp_granularities: The timestamp granularities to populate for this transcription. + `response_format` must be set `verbose_json` to use timestamp granularities. + Either or both of these options are supported: `word`, or `segment`. Note: There + is no additional latency for segment timestamps, but generating word timestamps + incurs additional latency. This option is not available for + `gpt-4o-transcribe-diarize`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["file", "model"], ["file", "model", "stream"]) + async def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + chunking_strategy: Optional[transcription_create_params.ChunkingStrategy] | Omit = omit, + include: List[TranscriptionInclude] | Omit = omit, + known_speaker_names: SequenceNotStr[str] | Omit = omit, + known_speaker_references: SequenceNotStr[str] | Omit = omit, + language: str | Omit = omit, + prompt: str | Omit = omit, + response_format: Union[AudioResponseFormat, Omit] = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + temperature: float | Omit = omit, + timestamp_granularities: List[Literal["word", "segment"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Transcription | TranscriptionVerbose | TranscriptionDiarized | str | AsyncStream[TranscriptionStreamEvent]: + body = deepcopy_with_paths( { "file": file, "model": model, + "chunking_strategy": chunking_strategy, + "include": include, + "known_speaker_names": known_speaker_names, + "known_speaker_references": known_speaker_references, "language": language, "prompt": prompt, "response_format": response_format, + "stream": stream, "temperature": temperature, "timestamp_granularities": timestamp_granularities, - } + }, + [["file"]], ) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be @@ -231,12 +943,23 @@ async def create( extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return await self._post( "/audio/transcriptions", - body=await async_maybe_transform(body, transcription_create_params.TranscriptionCreateParams), + body=await async_maybe_transform( + body, + transcription_create_params.TranscriptionCreateParamsStreaming + if stream + else transcription_create_params.TranscriptionCreateParamsNonStreaming, + ), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), - cast_to=Transcription, + cast_to=_get_response_format_type(response_format), + stream=stream or False, + stream_cls=AsyncStream[TranscriptionStreamEvent], ) @@ -274,3 +997,24 @@ def __init__(self, transcriptions: AsyncTranscriptions) -> None: self.create = async_to_streamed_response_wrapper( transcriptions.create, ) + + +def _get_response_format_type( + response_format: AudioResponseFormat | Omit, +) -> type[Transcription | TranscriptionVerbose | TranscriptionDiarized | str]: + if isinstance(response_format, Omit) or response_format is None: # pyright: ignore[reportUnnecessaryComparison] + return Transcription + + if response_format == "json": + return Transcription + elif response_format == "verbose_json": + return TranscriptionVerbose + elif response_format == "diarized_json": + return TranscriptionDiarized + elif response_format == "srt" or response_format == "text" or response_format == "vtt": + return str + elif TYPE_CHECKING: # type: ignore[unreachable] + assert_never(response_format) + else: + log.warn("Unexpected audio response format: %s", response_format) + return Transcription diff --git a/src/openai/resources/audio/translations.py b/src/openai/resources/audio/translations.py index 7ec647fb6b..d0b5738045 100644 --- a/src/openai/resources/audio/translations.py +++ b/src/openai/resources/audio/translations.py @@ -2,18 +2,16 @@ from __future__ import annotations -from typing import Union, Mapping, cast +import logging +from typing import TYPE_CHECKING, Union, Mapping, cast +from typing_extensions import Literal, overload, assert_never import httpx from ... import _legacy_response -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from ..._utils import ( - extract_files, - maybe_transform, - deepcopy_minimal, - async_maybe_transform, -) +from ..._files import deepcopy_with_paths +from ..._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given +from ..._utils import extract_files, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -21,15 +19,21 @@ from ..._base_client import make_request_options from ...types.audio_model import AudioModel from ...types.audio.translation import Translation +from ...types.audio_response_format import AudioResponseFormat +from ...types.audio.translation_verbose import TranslationVerbose __all__ = ["Translations", "AsyncTranslations"] +log: logging.Logger = logging.getLogger("openai.audio.transcriptions") + class Translations(SyncAPIResource): + """Turn audio into text or text into audio.""" + @cached_property def with_raw_response(self) -> TranslationsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -45,21 +49,72 @@ def with_streaming_response(self) -> TranslationsWithStreamingResponse: """ return TranslationsWithStreamingResponse(self) + @overload + def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + response_format: Union[Literal["json"], Omit] = omit, + prompt: str | Omit = omit, + temperature: float | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Translation: ... + + @overload + def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + response_format: Literal["verbose_json"], + prompt: str | Omit = omit, + temperature: float | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TranslationVerbose: ... + + @overload + def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + response_format: Literal["text", "srt", "vtt"], + prompt: str | Omit = omit, + temperature: float | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> str: ... + def create( self, *, file: FileTypes, model: Union[str, AudioModel], - prompt: str | NotGiven = NOT_GIVEN, - response_format: str | NotGiven = NOT_GIVEN, - temperature: float | NotGiven = NOT_GIVEN, + prompt: str | Omit = omit, + response_format: Union[Literal["json", "text", "srt", "verbose_json", "vtt"], Omit] = omit, + temperature: float | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Translation: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Translation | TranslationVerbose | str: """ Translates audio into English. @@ -72,11 +127,11 @@ def create( prompt: An optional text to guide the model's style or continue a previous audio segment. The - [prompt](https://platform.openai.com/docs/guides/speech-to-text/prompting) + [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) should be in English. - response_format: The format of the transcript output, in one of these options: `json`, `text`, - `srt`, `verbose_json`, or `vtt`. + response_format: The format of the output, in one of these options: `json`, `text`, `srt`, + `verbose_json`, or `vtt`. temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and @@ -92,36 +147,43 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + body = deepcopy_with_paths( { "file": file, "model": model, "prompt": prompt, "response_format": response_format, "temperature": temperature, - } + }, + [["file"]], ) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be # sent to the server will contain a `boundary` parameter, e.g. # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} - return self._post( + return self._post( # type: ignore[return-value] "/audio/translations", body=maybe_transform(body, translation_create_params.TranslationCreateParams), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), - cast_to=Translation, + cast_to=_get_response_format_type(response_format), ) class AsyncTranslations(AsyncAPIResource): + """Turn audio into text or text into audio.""" + @cached_property def with_raw_response(self) -> AsyncTranslationsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -137,21 +199,72 @@ def with_streaming_response(self) -> AsyncTranslationsWithStreamingResponse: """ return AsyncTranslationsWithStreamingResponse(self) + @overload async def create( self, *, file: FileTypes, model: Union[str, AudioModel], - prompt: str | NotGiven = NOT_GIVEN, - response_format: str | NotGiven = NOT_GIVEN, - temperature: float | NotGiven = NOT_GIVEN, + response_format: Union[Literal["json"], Omit] = omit, + prompt: str | Omit = omit, + temperature: float | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Translation: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Translation: ... + + @overload + async def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + response_format: Literal["verbose_json"], + prompt: str | Omit = omit, + temperature: float | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TranslationVerbose: ... + + @overload + async def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + response_format: Literal["text", "srt", "vtt"], + prompt: str | Omit = omit, + temperature: float | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> str: ... + + async def create( + self, + *, + file: FileTypes, + model: Union[str, AudioModel], + prompt: str | Omit = omit, + response_format: Union[AudioResponseFormat, Omit] = omit, + temperature: float | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Translation | TranslationVerbose | str: """ Translates audio into English. @@ -164,11 +277,11 @@ async def create( prompt: An optional text to guide the model's style or continue a previous audio segment. The - [prompt](https://platform.openai.com/docs/guides/speech-to-text/prompting) + [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) should be in English. - response_format: The format of the transcript output, in one of these options: `json`, `text`, - `srt`, `verbose_json`, or `vtt`. + response_format: The format of the output, in one of these options: `json`, `text`, `srt`, + `verbose_json`, or `vtt`. temperature: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and @@ -184,14 +297,15 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + body = deepcopy_with_paths( { "file": file, "model": model, "prompt": prompt, "response_format": response_format, "temperature": temperature, - } + }, + [["file"]], ) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be @@ -203,9 +317,13 @@ async def create( body=await async_maybe_transform(body, translation_create_params.TranslationCreateParams), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), - cast_to=Translation, + cast_to=_get_response_format_type(response_format), ) @@ -243,3 +361,22 @@ def __init__(self, translations: AsyncTranslations) -> None: self.create = async_to_streamed_response_wrapper( translations.create, ) + + +def _get_response_format_type( + response_format: AudioResponseFormat | Omit, +) -> type[Translation | TranslationVerbose | str]: + if isinstance(response_format, Omit) or response_format is None: # pyright: ignore[reportUnnecessaryComparison] + return Translation + + if response_format == "json": + return Translation + elif response_format == "verbose_json": + return TranslationVerbose + elif response_format == "srt" or response_format == "text" or response_format == "vtt": + return str + elif TYPE_CHECKING and response_format != "diarized_json": # type: ignore[unreachable] + assert_never(response_format) + else: + log.warning("Unexpected audio response format: %s", response_format) + return Translation diff --git a/src/openai/resources/batches.py b/src/openai/resources/batches.py index a8a0ba4bbc..e7146ef35f 100644 --- a/src/openai/resources/batches.py +++ b/src/openai/resources/batches.py @@ -2,36 +2,33 @@ from __future__ import annotations -from typing import Dict, Optional +from typing import Optional from typing_extensions import Literal import httpx from .. import _legacy_response from ..types import batch_list_params, batch_create_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..pagination import SyncCursorPage, AsyncCursorPage from ..types.batch import Batch -from .._base_client import ( - AsyncPaginator, - make_request_options, -) +from .._base_client import AsyncPaginator, make_request_options +from ..types.shared_params.metadata import Metadata __all__ = ["Batches", "AsyncBatches"] class Batches(SyncAPIResource): + """Create large batches of API requests to run asynchronously.""" + @cached_property def with_raw_response(self) -> BatchesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -51,15 +48,25 @@ def create( self, *, completion_window: Literal["24h"], - endpoint: Literal["/v1/chat/completions", "/v1/embeddings", "/v1/completions"], + endpoint: Literal[ + "/v1/responses", + "/v1/chat/completions", + "/v1/embeddings", + "/v1/completions", + "/v1/moderations", + "/v1/images/generations", + "/v1/images/edits", + "/v1/videos", + ], input_file_id: str, - metadata: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN, + metadata: Optional[Metadata] | Omit = omit, + output_expires_after: batch_create_params.OutputExpiresAfter | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Batch: """ Creates and executes a batch from an uploaded file of requests @@ -69,9 +76,11 @@ def create( is supported. endpoint: The endpoint to be used for all requests in the batch. Currently - `/v1/chat/completions`, `/v1/embeddings`, and `/v1/completions` are supported. - Note that `/v1/embeddings` batches are also restricted to a maximum of 50,000 - embedding inputs across all requests in the batch. + `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions`, + `/v1/moderations`, `/v1/images/generations`, `/v1/images/edits`, and + `/v1/videos` are supported. Note that `/v1/embeddings` batches are also + restricted to a maximum of 50,000 embedding inputs across all requests in the + batch. input_file_id: The ID of an uploaded file that contains requests for the new batch. @@ -81,9 +90,17 @@ def create( Your input file must be formatted as a [JSONL file](https://platform.openai.com/docs/api-reference/batch/request-input), and must be uploaded with the purpose `batch`. The file can contain up to 50,000 - requests, and can be up to 100 MB in size. + requests, and can be up to 200 MB in size. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. - metadata: Optional custom metadata for the batch. + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + output_expires_after: The expiration policy for the output and/or error file that are generated for a + batch. extra_headers: Send extra headers @@ -101,11 +118,16 @@ def create( "endpoint": endpoint, "input_file_id": input_file_id, "metadata": metadata, + "output_expires_after": output_expires_after, }, batch_create_params.BatchCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Batch, ) @@ -119,7 +141,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Batch: """ Retrieves a batch. @@ -136,9 +158,13 @@ def retrieve( if not batch_id: raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") return self._get( - f"/batches/{batch_id}", + path_template("/batches/{batch_id}", batch_id=batch_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Batch, ) @@ -146,14 +172,14 @@ def retrieve( def list( self, *, - after: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Batch]: """List your organization's batches. @@ -191,6 +217,7 @@ def list( }, batch_list_params.BatchListParams, ), + security={"bearer_auth": True}, ), model=Batch, ) @@ -204,7 +231,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Batch: """Cancels an in-progress batch. @@ -224,19 +251,25 @@ def cancel( if not batch_id: raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") return self._post( - f"/batches/{batch_id}/cancel", + path_template("/batches/{batch_id}/cancel", batch_id=batch_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Batch, ) class AsyncBatches(AsyncAPIResource): + """Create large batches of API requests to run asynchronously.""" + @cached_property def with_raw_response(self) -> AsyncBatchesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -256,15 +289,25 @@ async def create( self, *, completion_window: Literal["24h"], - endpoint: Literal["/v1/chat/completions", "/v1/embeddings", "/v1/completions"], + endpoint: Literal[ + "/v1/responses", + "/v1/chat/completions", + "/v1/embeddings", + "/v1/completions", + "/v1/moderations", + "/v1/images/generations", + "/v1/images/edits", + "/v1/videos", + ], input_file_id: str, - metadata: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN, + metadata: Optional[Metadata] | Omit = omit, + output_expires_after: batch_create_params.OutputExpiresAfter | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Batch: """ Creates and executes a batch from an uploaded file of requests @@ -274,9 +317,11 @@ async def create( is supported. endpoint: The endpoint to be used for all requests in the batch. Currently - `/v1/chat/completions`, `/v1/embeddings`, and `/v1/completions` are supported. - Note that `/v1/embeddings` batches are also restricted to a maximum of 50,000 - embedding inputs across all requests in the batch. + `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions`, + `/v1/moderations`, `/v1/images/generations`, `/v1/images/edits`, and + `/v1/videos` are supported. Note that `/v1/embeddings` batches are also + restricted to a maximum of 50,000 embedding inputs across all requests in the + batch. input_file_id: The ID of an uploaded file that contains requests for the new batch. @@ -286,9 +331,17 @@ async def create( Your input file must be formatted as a [JSONL file](https://platform.openai.com/docs/api-reference/batch/request-input), and must be uploaded with the purpose `batch`. The file can contain up to 50,000 - requests, and can be up to 100 MB in size. + requests, and can be up to 200 MB in size. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. - metadata: Optional custom metadata for the batch. + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + output_expires_after: The expiration policy for the output and/or error file that are generated for a + batch. extra_headers: Send extra headers @@ -306,11 +359,16 @@ async def create( "endpoint": endpoint, "input_file_id": input_file_id, "metadata": metadata, + "output_expires_after": output_expires_after, }, batch_create_params.BatchCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Batch, ) @@ -324,7 +382,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Batch: """ Retrieves a batch. @@ -341,9 +399,13 @@ async def retrieve( if not batch_id: raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") return await self._get( - f"/batches/{batch_id}", + path_template("/batches/{batch_id}", batch_id=batch_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Batch, ) @@ -351,14 +413,14 @@ async def retrieve( def list( self, *, - after: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Batch, AsyncCursorPage[Batch]]: """List your organization's batches. @@ -396,6 +458,7 @@ def list( }, batch_list_params.BatchListParams, ), + security={"bearer_auth": True}, ), model=Batch, ) @@ -409,7 +472,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Batch: """Cancels an in-progress batch. @@ -429,9 +492,13 @@ async def cancel( if not batch_id: raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") return await self._post( - f"/batches/{batch_id}/cancel", + path_template("/batches/{batch_id}/cancel", batch_id=batch_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Batch, ) diff --git a/src/openai/resources/beta/__init__.py b/src/openai/resources/beta/__init__.py index 01f5338757..6d6f538670 100644 --- a/src/openai/resources/beta/__init__.py +++ b/src/openai/resources/beta/__init__.py @@ -8,6 +8,14 @@ BetaWithStreamingResponse, AsyncBetaWithStreamingResponse, ) +from .chatkit import ( + ChatKit, + AsyncChatKit, + ChatKitWithRawResponse, + AsyncChatKitWithRawResponse, + ChatKitWithStreamingResponse, + AsyncChatKitWithStreamingResponse, +) from .threads import ( Threads, AsyncThreads, @@ -24,22 +32,14 @@ AssistantsWithStreamingResponse, AsyncAssistantsWithStreamingResponse, ) -from .vector_stores import ( - VectorStores, - AsyncVectorStores, - VectorStoresWithRawResponse, - AsyncVectorStoresWithRawResponse, - VectorStoresWithStreamingResponse, - AsyncVectorStoresWithStreamingResponse, -) __all__ = [ - "VectorStores", - "AsyncVectorStores", - "VectorStoresWithRawResponse", - "AsyncVectorStoresWithRawResponse", - "VectorStoresWithStreamingResponse", - "AsyncVectorStoresWithStreamingResponse", + "ChatKit", + "AsyncChatKit", + "ChatKitWithRawResponse", + "AsyncChatKitWithRawResponse", + "ChatKitWithStreamingResponse", + "AsyncChatKitWithStreamingResponse", "Assistants", "AsyncAssistants", "AssistantsWithRawResponse", diff --git a/src/openai/resources/beta/assistants.py b/src/openai/resources/beta/assistants.py index 5d8c6ec331..6301082fea 100644 --- a/src/openai/resources/beta/assistants.py +++ b/src/openai/resources/beta/assistants.py @@ -2,17 +2,15 @@ from __future__ import annotations +import typing_extensions from typing import Union, Iterable, Optional from typing_extensions import Literal import httpx from ... import _legacy_response -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -23,9 +21,11 @@ assistant_update_params, ) from ..._base_client import AsyncPaginator, make_request_options -from ...types.chat_model import ChatModel from ...types.beta.assistant import Assistant +from ...types.shared.chat_model import ChatModel from ...types.beta.assistant_deleted import AssistantDeleted +from ...types.shared_params.metadata import Metadata +from ...types.shared.reasoning_effort import ReasoningEffort from ...types.beta.assistant_tool_param import AssistantToolParam from ...types.beta.assistant_response_format_option_param import AssistantResponseFormatOptionParam @@ -33,10 +33,12 @@ class Assistants(SyncAPIResource): + """Build Assistants that can call models and use tools.""" + @cached_property def with_raw_response(self) -> AssistantsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -52,25 +54,27 @@ def with_streaming_response(self) -> AssistantsWithStreamingResponse: """ return AssistantsWithStreamingResponse(self) + @typing_extensions.deprecated("deprecated") def create( self, *, model: Union[str, ChatModel], - description: Optional[str] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - name: Optional[str] | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_resources: Optional[assistant_create_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Iterable[AssistantToolParam] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, + description: Optional[str] | Omit = omit, + instructions: Optional[str] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + name: Optional[str] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_resources: Optional[assistant_create_params.ToolResources] | Omit = omit, + tools: Iterable[AssistantToolParam] | Omit = omit, + top_p: Optional[float] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Assistant: """ Create an assistant with a model and instructions. @@ -79,8 +83,8 @@ def create( model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. description: The description of the assistant. The maximum length is 512 characters. @@ -88,15 +92,31 @@ def create( characters. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. name: The name of the assistant. The maximum length is 256 characters. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -152,6 +172,7 @@ def create( "instructions": instructions, "metadata": metadata, "name": name, + "reasoning_effort": reasoning_effort, "response_format": response_format, "temperature": temperature, "tool_resources": tool_resources, @@ -161,11 +182,16 @@ def create( assistant_create_params.AssistantCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Assistant, ) + @typing_extensions.deprecated("deprecated") def retrieve( self, assistant_id: str, @@ -175,7 +201,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Assistant: """ Retrieves an assistant. @@ -193,33 +219,86 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get( - f"/assistants/{assistant_id}", + path_template("/assistants/{assistant_id}", assistant_id=assistant_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Assistant, ) + @typing_extensions.deprecated("deprecated") def update( self, assistant_id: str, *, - description: Optional[str] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: str | NotGiven = NOT_GIVEN, - name: Optional[str] | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_resources: Optional[assistant_update_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Iterable[AssistantToolParam] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, + description: Optional[str] | Omit = omit, + instructions: Optional[str] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[ + str, + Literal[ + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4.5-preview", + "gpt-4.5-preview-2025-02-27", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613", + ], + ] + | Omit = omit, + name: Optional[str] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_resources: Optional[assistant_update_params.ToolResources] | Omit = omit, + tools: Iterable[AssistantToolParam] | Omit = omit, + top_p: Optional[float] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Assistant: """Modifies an assistant. @@ -232,21 +311,37 @@ def update( characters. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. name: The name of the assistant. The maximum length is 256 characters. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -296,7 +391,7 @@ def update( raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/assistants/{assistant_id}", + path_template("/assistants/{assistant_id}", assistant_id=assistant_id), body=maybe_transform( { "description": description, @@ -304,6 +399,7 @@ def update( "metadata": metadata, "model": model, "name": name, + "reasoning_effort": reasoning_effort, "response_format": response_format, "temperature": temperature, "tool_resources": tool_resources, @@ -313,24 +409,29 @@ def update( assistant_update_params.AssistantUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Assistant, ) + @typing_extensions.deprecated("deprecated") def list( self, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Assistant]: """Returns a list of assistants. @@ -344,8 +445,8 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20. @@ -379,10 +480,12 @@ def list( }, assistant_list_params.AssistantListParams, ), + security={"bearer_auth": True}, ), model=Assistant, ) + @typing_extensions.deprecated("deprecated") def delete( self, assistant_id: str, @@ -392,7 +495,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AssistantDeleted: """ Delete an assistant. @@ -410,19 +513,25 @@ def delete( raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._delete( - f"/assistants/{assistant_id}", + path_template("/assistants/{assistant_id}", assistant_id=assistant_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=AssistantDeleted, ) class AsyncAssistants(AsyncAPIResource): + """Build Assistants that can call models and use tools.""" + @cached_property def with_raw_response(self) -> AsyncAssistantsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -438,25 +547,27 @@ def with_streaming_response(self) -> AsyncAssistantsWithStreamingResponse: """ return AsyncAssistantsWithStreamingResponse(self) + @typing_extensions.deprecated("deprecated") async def create( self, *, model: Union[str, ChatModel], - description: Optional[str] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - name: Optional[str] | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_resources: Optional[assistant_create_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Iterable[AssistantToolParam] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, + description: Optional[str] | Omit = omit, + instructions: Optional[str] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + name: Optional[str] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_resources: Optional[assistant_create_params.ToolResources] | Omit = omit, + tools: Iterable[AssistantToolParam] | Omit = omit, + top_p: Optional[float] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Assistant: """ Create an assistant with a model and instructions. @@ -465,8 +576,8 @@ async def create( model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. description: The description of the assistant. The maximum length is 512 characters. @@ -474,15 +585,31 @@ async def create( characters. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. name: The name of the assistant. The maximum length is 256 characters. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -538,6 +665,7 @@ async def create( "instructions": instructions, "metadata": metadata, "name": name, + "reasoning_effort": reasoning_effort, "response_format": response_format, "temperature": temperature, "tool_resources": tool_resources, @@ -547,11 +675,16 @@ async def create( assistant_create_params.AssistantCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Assistant, ) + @typing_extensions.deprecated("deprecated") async def retrieve( self, assistant_id: str, @@ -561,7 +694,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Assistant: """ Retrieves an assistant. @@ -579,33 +712,86 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._get( - f"/assistants/{assistant_id}", + path_template("/assistants/{assistant_id}", assistant_id=assistant_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Assistant, ) + @typing_extensions.deprecated("deprecated") async def update( self, assistant_id: str, *, - description: Optional[str] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: str | NotGiven = NOT_GIVEN, - name: Optional[str] | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_resources: Optional[assistant_update_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Iterable[AssistantToolParam] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, + description: Optional[str] | Omit = omit, + instructions: Optional[str] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[ + str, + Literal[ + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4.5-preview", + "gpt-4.5-preview-2025-02-27", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613", + ], + ] + | Omit = omit, + name: Optional[str] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_resources: Optional[assistant_update_params.ToolResources] | Omit = omit, + tools: Iterable[AssistantToolParam] | Omit = omit, + top_p: Optional[float] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Assistant: """Modifies an assistant. @@ -618,21 +804,37 @@ async def update( characters. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. name: The name of the assistant. The maximum length is 256 characters. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -682,7 +884,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/assistants/{assistant_id}", + path_template("/assistants/{assistant_id}", assistant_id=assistant_id), body=await async_maybe_transform( { "description": description, @@ -690,6 +892,7 @@ async def update( "metadata": metadata, "model": model, "name": name, + "reasoning_effort": reasoning_effort, "response_format": response_format, "temperature": temperature, "tool_resources": tool_resources, @@ -699,24 +902,29 @@ async def update( assistant_update_params.AssistantUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Assistant, ) + @typing_extensions.deprecated("deprecated") def list( self, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Assistant, AsyncCursorPage[Assistant]]: """Returns a list of assistants. @@ -730,8 +938,8 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20. @@ -765,10 +973,12 @@ def list( }, assistant_list_params.AssistantListParams, ), + security={"bearer_auth": True}, ), model=Assistant, ) + @typing_extensions.deprecated("deprecated") async def delete( self, assistant_id: str, @@ -778,7 +988,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AssistantDeleted: """ Delete an assistant. @@ -796,9 +1006,13 @@ async def delete( raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._delete( - f"/assistants/{assistant_id}", + path_template("/assistants/{assistant_id}", assistant_id=assistant_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=AssistantDeleted, ) @@ -808,20 +1022,30 @@ class AssistantsWithRawResponse: def __init__(self, assistants: Assistants) -> None: self._assistants = assistants - self.create = _legacy_response.to_raw_response_wrapper( - assistants.create, + self.create = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + assistants.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = _legacy_response.to_raw_response_wrapper( - assistants.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + assistants.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = _legacy_response.to_raw_response_wrapper( - assistants.update, + self.update = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + assistants.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = _legacy_response.to_raw_response_wrapper( - assistants.list, + self.list = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + assistants.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = _legacy_response.to_raw_response_wrapper( - assistants.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + assistants.delete, # pyright: ignore[reportDeprecated], + ) ) @@ -829,20 +1053,30 @@ class AsyncAssistantsWithRawResponse: def __init__(self, assistants: AsyncAssistants) -> None: self._assistants = assistants - self.create = _legacy_response.async_to_raw_response_wrapper( - assistants.create, + self.create = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + assistants.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = _legacy_response.async_to_raw_response_wrapper( - assistants.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + assistants.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = _legacy_response.async_to_raw_response_wrapper( - assistants.update, + self.update = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + assistants.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = _legacy_response.async_to_raw_response_wrapper( - assistants.list, + self.list = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + assistants.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = _legacy_response.async_to_raw_response_wrapper( - assistants.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + assistants.delete, # pyright: ignore[reportDeprecated], + ) ) @@ -850,20 +1084,30 @@ class AssistantsWithStreamingResponse: def __init__(self, assistants: Assistants) -> None: self._assistants = assistants - self.create = to_streamed_response_wrapper( - assistants.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + assistants.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = to_streamed_response_wrapper( - assistants.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + assistants.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = to_streamed_response_wrapper( - assistants.update, + self.update = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + assistants.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = to_streamed_response_wrapper( - assistants.list, + self.list = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + assistants.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = to_streamed_response_wrapper( - assistants.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + assistants.delete, # pyright: ignore[reportDeprecated], + ) ) @@ -871,18 +1115,28 @@ class AsyncAssistantsWithStreamingResponse: def __init__(self, assistants: AsyncAssistants) -> None: self._assistants = assistants - self.create = async_to_streamed_response_wrapper( - assistants.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + assistants.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = async_to_streamed_response_wrapper( - assistants.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + assistants.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = async_to_streamed_response_wrapper( - assistants.update, + self.update = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + assistants.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = async_to_streamed_response_wrapper( - assistants.list, + self.list = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + assistants.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = async_to_streamed_response_wrapper( - assistants.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + assistants.delete, # pyright: ignore[reportDeprecated], + ) ) diff --git a/src/openai/resources/beta/beta.py b/src/openai/resources/beta/beta.py index a7d3e707c8..388a1c5d1f 100644 --- a/src/openai/resources/beta/beta.py +++ b/src/openai/resources/beta/beta.py @@ -2,16 +2,7 @@ from __future__ import annotations -from .threads import ( - Threads, - AsyncThreads, - ThreadsWithRawResponse, - AsyncThreadsWithRawResponse, - ThreadsWithStreamingResponse, - AsyncThreadsWithStreamingResponse, -) from ..._compat import cached_property -from .chat.chat import Chat, AsyncChat from .assistants import ( Assistants, AsyncAssistants, @@ -21,16 +12,27 @@ AsyncAssistantsWithStreamingResponse, ) from ..._resource import SyncAPIResource, AsyncAPIResource -from .vector_stores import ( - VectorStores, - AsyncVectorStores, - VectorStoresWithRawResponse, - AsyncVectorStoresWithRawResponse, - VectorStoresWithStreamingResponse, - AsyncVectorStoresWithStreamingResponse, +from .chatkit.chatkit import ( + ChatKit, + AsyncChatKit, + ChatKitWithRawResponse, + AsyncChatKitWithRawResponse, + ChatKitWithStreamingResponse, + AsyncChatKitWithStreamingResponse, +) +from .threads.threads import ( + Threads, + AsyncThreads, + ThreadsWithRawResponse, + AsyncThreadsWithRawResponse, + ThreadsWithStreamingResponse, + AsyncThreadsWithStreamingResponse, +) +from ...resources.chat import Chat, AsyncChat +from .realtime.realtime import ( + Realtime, + AsyncRealtime, ) -from .threads.threads import Threads, AsyncThreads -from .vector_stores.vector_stores import VectorStores, AsyncVectorStores __all__ = ["Beta", "AsyncBeta"] @@ -41,21 +43,27 @@ def chat(self) -> Chat: return Chat(self._client) @cached_property - def vector_stores(self) -> VectorStores: - return VectorStores(self._client) + def realtime(self) -> Realtime: + return Realtime(self._client) + + @cached_property + def chatkit(self) -> ChatKit: + return ChatKit(self._client) @cached_property def assistants(self) -> Assistants: + """Build Assistants that can call models and use tools.""" return Assistants(self._client) @cached_property def threads(self) -> Threads: + """Build Assistants that can call models and use tools.""" return Threads(self._client) @cached_property def with_raw_response(self) -> BetaWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -78,21 +86,27 @@ def chat(self) -> AsyncChat: return AsyncChat(self._client) @cached_property - def vector_stores(self) -> AsyncVectorStores: - return AsyncVectorStores(self._client) + def realtime(self) -> AsyncRealtime: + return AsyncRealtime(self._client) + + @cached_property + def chatkit(self) -> AsyncChatKit: + return AsyncChatKit(self._client) @cached_property def assistants(self) -> AsyncAssistants: + """Build Assistants that can call models and use tools.""" return AsyncAssistants(self._client) @cached_property def threads(self) -> AsyncThreads: + """Build Assistants that can call models and use tools.""" return AsyncThreads(self._client) @cached_property def with_raw_response(self) -> AsyncBetaWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -114,15 +128,17 @@ def __init__(self, beta: Beta) -> None: self._beta = beta @cached_property - def vector_stores(self) -> VectorStoresWithRawResponse: - return VectorStoresWithRawResponse(self._beta.vector_stores) + def chatkit(self) -> ChatKitWithRawResponse: + return ChatKitWithRawResponse(self._beta.chatkit) @cached_property def assistants(self) -> AssistantsWithRawResponse: + """Build Assistants that can call models and use tools.""" return AssistantsWithRawResponse(self._beta.assistants) @cached_property def threads(self) -> ThreadsWithRawResponse: + """Build Assistants that can call models and use tools.""" return ThreadsWithRawResponse(self._beta.threads) @@ -131,15 +147,17 @@ def __init__(self, beta: AsyncBeta) -> None: self._beta = beta @cached_property - def vector_stores(self) -> AsyncVectorStoresWithRawResponse: - return AsyncVectorStoresWithRawResponse(self._beta.vector_stores) + def chatkit(self) -> AsyncChatKitWithRawResponse: + return AsyncChatKitWithRawResponse(self._beta.chatkit) @cached_property def assistants(self) -> AsyncAssistantsWithRawResponse: + """Build Assistants that can call models and use tools.""" return AsyncAssistantsWithRawResponse(self._beta.assistants) @cached_property def threads(self) -> AsyncThreadsWithRawResponse: + """Build Assistants that can call models and use tools.""" return AsyncThreadsWithRawResponse(self._beta.threads) @@ -148,15 +166,17 @@ def __init__(self, beta: Beta) -> None: self._beta = beta @cached_property - def vector_stores(self) -> VectorStoresWithStreamingResponse: - return VectorStoresWithStreamingResponse(self._beta.vector_stores) + def chatkit(self) -> ChatKitWithStreamingResponse: + return ChatKitWithStreamingResponse(self._beta.chatkit) @cached_property def assistants(self) -> AssistantsWithStreamingResponse: + """Build Assistants that can call models and use tools.""" return AssistantsWithStreamingResponse(self._beta.assistants) @cached_property def threads(self) -> ThreadsWithStreamingResponse: + """Build Assistants that can call models and use tools.""" return ThreadsWithStreamingResponse(self._beta.threads) @@ -165,13 +185,15 @@ def __init__(self, beta: AsyncBeta) -> None: self._beta = beta @cached_property - def vector_stores(self) -> AsyncVectorStoresWithStreamingResponse: - return AsyncVectorStoresWithStreamingResponse(self._beta.vector_stores) + def chatkit(self) -> AsyncChatKitWithStreamingResponse: + return AsyncChatKitWithStreamingResponse(self._beta.chatkit) @cached_property def assistants(self) -> AsyncAssistantsWithStreamingResponse: + """Build Assistants that can call models and use tools.""" return AsyncAssistantsWithStreamingResponse(self._beta.assistants) @cached_property def threads(self) -> AsyncThreadsWithStreamingResponse: + """Build Assistants that can call models and use tools.""" return AsyncThreadsWithStreamingResponse(self._beta.threads) diff --git a/src/openai/resources/beta/chat/__init__.py b/src/openai/resources/beta/chat/__init__.py deleted file mode 100644 index 072d7867a5..0000000000 --- a/src/openai/resources/beta/chat/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .chat import Chat, AsyncChat -from .completions import Completions, AsyncCompletions - -__all__ = [ - "Completions", - "AsyncCompletions", - "Chat", - "AsyncChat", -] diff --git a/src/openai/resources/beta/chat/chat.py b/src/openai/resources/beta/chat/chat.py deleted file mode 100644 index 6afdcea381..0000000000 --- a/src/openai/resources/beta/chat/chat.py +++ /dev/null @@ -1,21 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from ...._compat import cached_property -from .completions import Completions, AsyncCompletions -from ...._resource import SyncAPIResource, AsyncAPIResource - -__all__ = ["Chat", "AsyncChat"] - - -class Chat(SyncAPIResource): - @cached_property - def completions(self) -> Completions: - return Completions(self._client) - - -class AsyncChat(AsyncAPIResource): - @cached_property - def completions(self) -> AsyncCompletions: - return AsyncCompletions(self._client) diff --git a/src/openai/resources/beta/chat/completions.py b/src/openai/resources/beta/chat/completions.py deleted file mode 100644 index ea3526778d..0000000000 --- a/src/openai/resources/beta/chat/completions.py +++ /dev/null @@ -1,463 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Dict, List, Union, Iterable, Optional -from functools import partial -from typing_extensions import Literal - -import httpx - -from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._streaming import Stream -from ....types.chat import completion_create_params -from ....lib._parsing import ( - ResponseFormatT, - validate_input_tools as _validate_input_tools, - parse_chat_completion as _parse_chat_completion, - type_to_response_format_param as _type_to_response_format, -) -from ....types.chat_model import ChatModel -from ....lib.streaming.chat import ChatCompletionStreamManager, AsyncChatCompletionStreamManager -from ....types.chat.chat_completion_chunk import ChatCompletionChunk -from ....types.chat.parsed_chat_completion import ParsedChatCompletion -from ....types.chat.chat_completion_tool_param import ChatCompletionToolParam -from ....types.chat.chat_completion_message_param import ChatCompletionMessageParam -from ....types.chat.chat_completion_stream_options_param import ChatCompletionStreamOptionsParam -from ....types.chat.chat_completion_tool_choice_option_param import ChatCompletionToolChoiceOptionParam - -__all__ = ["Completions", "AsyncCompletions"] - - -class Completions(SyncAPIResource): - def parse( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - response_format: type[ResponseFormatT] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ParsedChatCompletion[ResponseFormatT]: - """Wrapper over the `client.chat.completions.create()` method that provides richer integrations with Python specific types - & returns a `ParsedChatCompletion` object, which is a subclass of the standard `ChatCompletion` class. - - You can pass a pydantic model to this method and it will automatically convert the model - into a JSON schema, send it to the API and parse the response content back into the given model. - - This method will also automatically parse `function` tool calls if: - - You use the `openai.pydantic_function_tool()` helper method - - You mark your tool schema with `"strict": True` - - Example usage: - ```py - from pydantic import BaseModel - from openai import OpenAI - - - class Step(BaseModel): - explanation: str - output: str - - - class MathResponse(BaseModel): - steps: List[Step] - final_answer: str - - - client = OpenAI() - completion = client.beta.chat.completions.parse( - model="gpt-4o-2024-08-06", - messages=[ - {"role": "system", "content": "You are a helpful math tutor."}, - {"role": "user", "content": "solve 8x + 31 = 2"}, - ], - response_format=MathResponse, - ) - - message = completion.choices[0].message - if message.parsed: - print(message.parsed.steps) - print("answer: ", message.parsed.final_answer) - ``` - """ - _validate_input_tools(tools) - - extra_headers = { - "X-Stainless-Helper-Method": "beta.chat.completions.parse", - **(extra_headers or {}), - } - - raw_completion = self._client.chat.completions.create( - messages=messages, - model=model, - response_format=_type_to_response_format(response_format), - frequency_penalty=frequency_penalty, - function_call=function_call, - functions=functions, - logit_bias=logit_bias, - logprobs=logprobs, - max_completion_tokens=max_completion_tokens, - max_tokens=max_tokens, - n=n, - parallel_tool_calls=parallel_tool_calls, - presence_penalty=presence_penalty, - seed=seed, - service_tier=service_tier, - stop=stop, - stream_options=stream_options, - temperature=temperature, - tool_choice=tool_choice, - tools=tools, - top_logprobs=top_logprobs, - top_p=top_p, - user=user, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - return _parse_chat_completion( - response_format=response_format, - chat_completion=raw_completion, - input_tools=tools, - ) - - def stream( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - response_format: completion_create_params.ResponseFormat | type[ResponseFormatT] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ChatCompletionStreamManager[ResponseFormatT]: - """Wrapper over the `client.chat.completions.create(stream=True)` method that provides a more granular event API - and automatic accumulation of each delta. - - This also supports all of the parsing utilities that `.parse()` does. - - Unlike `.create(stream=True)`, the `.stream()` method requires usage within a context manager to prevent accidental leakage of the response: - - ```py - with client.beta.chat.completions.stream( - model="gpt-4o-2024-08-06", - messages=[...], - ) as stream: - for event in stream: - if event.type == "content.delta": - print(event.delta, flush=True, end="") - ``` - - When the context manager is entered, a `ChatCompletionStream` instance is returned which, like `.create(stream=True)` is an iterator. The full list of events that are yielded by the iterator are outlined in [these docs](https://github.com/openai/openai-python/blob/main/helpers.md#chat-completions-events). - - When the context manager exits, the response will be closed, however the `stream` instance is still available outside - the context manager. - """ - extra_headers = { - "X-Stainless-Helper-Method": "beta.chat.completions.stream", - **(extra_headers or {}), - } - - api_request: partial[Stream[ChatCompletionChunk]] = partial( - self._client.chat.completions.create, - messages=messages, - model=model, - stream=True, - response_format=_type_to_response_format(response_format), - frequency_penalty=frequency_penalty, - function_call=function_call, - functions=functions, - logit_bias=logit_bias, - logprobs=logprobs, - max_completion_tokens=max_completion_tokens, - max_tokens=max_tokens, - n=n, - parallel_tool_calls=parallel_tool_calls, - presence_penalty=presence_penalty, - seed=seed, - service_tier=service_tier, - stop=stop, - stream_options=stream_options, - temperature=temperature, - tool_choice=tool_choice, - tools=tools, - top_logprobs=top_logprobs, - top_p=top_p, - user=user, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - return ChatCompletionStreamManager( - api_request, - response_format=response_format, - input_tools=tools, - ) - - -class AsyncCompletions(AsyncAPIResource): - async def parse( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - response_format: type[ResponseFormatT] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ParsedChatCompletion[ResponseFormatT]: - """Wrapper over the `client.chat.completions.create()` method that provides richer integrations with Python specific types - & returns a `ParsedChatCompletion` object, which is a subclass of the standard `ChatCompletion` class. - - You can pass a pydantic model to this method and it will automatically convert the model - into a JSON schema, send it to the API and parse the response content back into the given model. - - This method will also automatically parse `function` tool calls if: - - You use the `openai.pydantic_function_tool()` helper method - - You mark your tool schema with `"strict": True` - - Example usage: - ```py - from pydantic import BaseModel - from openai import AsyncOpenAI - - - class Step(BaseModel): - explanation: str - output: str - - - class MathResponse(BaseModel): - steps: List[Step] - final_answer: str - - - client = AsyncOpenAI() - completion = await client.beta.chat.completions.parse( - model="gpt-4o-2024-08-06", - messages=[ - {"role": "system", "content": "You are a helpful math tutor."}, - {"role": "user", "content": "solve 8x + 31 = 2"}, - ], - response_format=MathResponse, - ) - - message = completion.choices[0].message - if message.parsed: - print(message.parsed.steps) - print("answer: ", message.parsed.final_answer) - ``` - """ - _validate_input_tools(tools) - - extra_headers = { - "X-Stainless-Helper-Method": "beta.chat.completions.parse", - **(extra_headers or {}), - } - - raw_completion = await self._client.chat.completions.create( - messages=messages, - model=model, - response_format=_type_to_response_format(response_format), - frequency_penalty=frequency_penalty, - function_call=function_call, - functions=functions, - logit_bias=logit_bias, - logprobs=logprobs, - max_completion_tokens=max_completion_tokens, - max_tokens=max_tokens, - n=n, - parallel_tool_calls=parallel_tool_calls, - presence_penalty=presence_penalty, - seed=seed, - service_tier=service_tier, - stop=stop, - stream_options=stream_options, - temperature=temperature, - tool_choice=tool_choice, - tools=tools, - top_logprobs=top_logprobs, - top_p=top_p, - user=user, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - return _parse_chat_completion( - response_format=response_format, - chat_completion=raw_completion, - input_tools=tools, - ) - - def stream( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - response_format: completion_create_params.ResponseFormat | type[ResponseFormatT] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncChatCompletionStreamManager[ResponseFormatT]: - """Wrapper over the `client.chat.completions.create(stream=True)` method that provides a more granular event API - and automatic accumulation of each delta. - - This also supports all of the parsing utilities that `.parse()` does. - - Unlike `.create(stream=True)`, the `.stream()` method requires usage within a context manager to prevent accidental leakage of the response: - - ```py - async with client.beta.chat.completions.stream( - model="gpt-4o-2024-08-06", - messages=[...], - ) as stream: - async for event in stream: - if event.type == "content.delta": - print(event.delta, flush=True, end="") - ``` - - When the context manager is entered, an `AsyncChatCompletionStream` instance is returned which, like `.create(stream=True)` is an async iterator. The full list of events that are yielded by the iterator are outlined in [these docs](https://github.com/openai/openai-python/blob/main/helpers.md#chat-completions-events). - - When the context manager exits, the response will be closed, however the `stream` instance is still available outside - the context manager. - """ - _validate_input_tools(tools) - - extra_headers = { - "X-Stainless-Helper-Method": "beta.chat.completions.stream", - **(extra_headers or {}), - } - - api_request = self._client.chat.completions.create( - messages=messages, - model=model, - stream=True, - response_format=_type_to_response_format(response_format), - frequency_penalty=frequency_penalty, - function_call=function_call, - functions=functions, - logit_bias=logit_bias, - logprobs=logprobs, - max_completion_tokens=max_completion_tokens, - max_tokens=max_tokens, - n=n, - parallel_tool_calls=parallel_tool_calls, - presence_penalty=presence_penalty, - seed=seed, - service_tier=service_tier, - stop=stop, - stream_options=stream_options, - temperature=temperature, - tool_choice=tool_choice, - tools=tools, - top_logprobs=top_logprobs, - top_p=top_p, - user=user, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - return AsyncChatCompletionStreamManager( - api_request, - response_format=response_format, - input_tools=tools, - ) diff --git a/src/openai/resources/beta/chatkit/__init__.py b/src/openai/resources/beta/chatkit/__init__.py new file mode 100644 index 0000000000..05f24d6238 --- /dev/null +++ b/src/openai/resources/beta/chatkit/__init__.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .chatkit import ( + ChatKit, + AsyncChatKit, + ChatKitWithRawResponse, + AsyncChatKitWithRawResponse, + ChatKitWithStreamingResponse, + AsyncChatKitWithStreamingResponse, +) +from .threads import ( + Threads, + AsyncThreads, + ThreadsWithRawResponse, + AsyncThreadsWithRawResponse, + ThreadsWithStreamingResponse, + AsyncThreadsWithStreamingResponse, +) +from .sessions import ( + Sessions, + AsyncSessions, + SessionsWithRawResponse, + AsyncSessionsWithRawResponse, + SessionsWithStreamingResponse, + AsyncSessionsWithStreamingResponse, +) + +__all__ = [ + "Sessions", + "AsyncSessions", + "SessionsWithRawResponse", + "AsyncSessionsWithRawResponse", + "SessionsWithStreamingResponse", + "AsyncSessionsWithStreamingResponse", + "Threads", + "AsyncThreads", + "ThreadsWithRawResponse", + "AsyncThreadsWithRawResponse", + "ThreadsWithStreamingResponse", + "AsyncThreadsWithStreamingResponse", + "ChatKit", + "AsyncChatKit", + "ChatKitWithRawResponse", + "AsyncChatKitWithRawResponse", + "ChatKitWithStreamingResponse", + "AsyncChatKitWithStreamingResponse", +] diff --git a/src/openai/resources/beta/chatkit/chatkit.py b/src/openai/resources/beta/chatkit/chatkit.py new file mode 100644 index 0000000000..5a10a39c7b --- /dev/null +++ b/src/openai/resources/beta/chatkit/chatkit.py @@ -0,0 +1,134 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .threads import ( + Threads, + AsyncThreads, + ThreadsWithRawResponse, + AsyncThreadsWithRawResponse, + ThreadsWithStreamingResponse, + AsyncThreadsWithStreamingResponse, +) +from .sessions import ( + Sessions, + AsyncSessions, + SessionsWithRawResponse, + AsyncSessionsWithRawResponse, + SessionsWithStreamingResponse, + AsyncSessionsWithStreamingResponse, +) +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource + +__all__ = ["ChatKit", "AsyncChatKit"] + + +class ChatKit(SyncAPIResource): + @cached_property + def sessions(self) -> Sessions: + return Sessions(self._client) + + @cached_property + def threads(self) -> Threads: + return Threads(self._client) + + @cached_property + def with_raw_response(self) -> ChatKitWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ChatKitWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ChatKitWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ChatKitWithStreamingResponse(self) + + +class AsyncChatKit(AsyncAPIResource): + @cached_property + def sessions(self) -> AsyncSessions: + return AsyncSessions(self._client) + + @cached_property + def threads(self) -> AsyncThreads: + return AsyncThreads(self._client) + + @cached_property + def with_raw_response(self) -> AsyncChatKitWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncChatKitWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncChatKitWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncChatKitWithStreamingResponse(self) + + +class ChatKitWithRawResponse: + def __init__(self, chatkit: ChatKit) -> None: + self._chatkit = chatkit + + @cached_property + def sessions(self) -> SessionsWithRawResponse: + return SessionsWithRawResponse(self._chatkit.sessions) + + @cached_property + def threads(self) -> ThreadsWithRawResponse: + return ThreadsWithRawResponse(self._chatkit.threads) + + +class AsyncChatKitWithRawResponse: + def __init__(self, chatkit: AsyncChatKit) -> None: + self._chatkit = chatkit + + @cached_property + def sessions(self) -> AsyncSessionsWithRawResponse: + return AsyncSessionsWithRawResponse(self._chatkit.sessions) + + @cached_property + def threads(self) -> AsyncThreadsWithRawResponse: + return AsyncThreadsWithRawResponse(self._chatkit.threads) + + +class ChatKitWithStreamingResponse: + def __init__(self, chatkit: ChatKit) -> None: + self._chatkit = chatkit + + @cached_property + def sessions(self) -> SessionsWithStreamingResponse: + return SessionsWithStreamingResponse(self._chatkit.sessions) + + @cached_property + def threads(self) -> ThreadsWithStreamingResponse: + return ThreadsWithStreamingResponse(self._chatkit.threads) + + +class AsyncChatKitWithStreamingResponse: + def __init__(self, chatkit: AsyncChatKit) -> None: + self._chatkit = chatkit + + @cached_property + def sessions(self) -> AsyncSessionsWithStreamingResponse: + return AsyncSessionsWithStreamingResponse(self._chatkit.sessions) + + @cached_property + def threads(self) -> AsyncThreadsWithStreamingResponse: + return AsyncThreadsWithStreamingResponse(self._chatkit.threads) diff --git a/src/openai/resources/beta/chatkit/sessions.py b/src/openai/resources/beta/chatkit/sessions.py new file mode 100644 index 0000000000..9049b06a40 --- /dev/null +++ b/src/openai/resources/beta/chatkit/sessions.py @@ -0,0 +1,321 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...._base_client import make_request_options +from ....types.beta.chatkit import ( + ChatSessionWorkflowParam, + ChatSessionRateLimitsParam, + ChatSessionExpiresAfterParam, + ChatSessionChatKitConfigurationParam, + session_create_params, +) +from ....types.beta.chatkit.chat_session import ChatSession +from ....types.beta.chatkit.chat_session_workflow_param import ChatSessionWorkflowParam +from ....types.beta.chatkit.chat_session_rate_limits_param import ChatSessionRateLimitsParam +from ....types.beta.chatkit.chat_session_expires_after_param import ChatSessionExpiresAfterParam +from ....types.beta.chatkit.chat_session_chatkit_configuration_param import ChatSessionChatKitConfigurationParam + +__all__ = ["Sessions", "AsyncSessions"] + + +class Sessions(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SessionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return SessionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SessionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return SessionsWithStreamingResponse(self) + + def create( + self, + *, + user: str, + workflow: ChatSessionWorkflowParam, + chatkit_configuration: ChatSessionChatKitConfigurationParam | Omit = omit, + expires_after: ChatSessionExpiresAfterParam | Omit = omit, + rate_limits: ChatSessionRateLimitsParam | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatSession: + """ + Create a ChatKit session. + + Args: + user: A free-form string that identifies your end user; ensures this Session can + access other objects that have the same `user` scope. + + workflow: Workflow that powers the session. + + chatkit_configuration: Optional overrides for ChatKit runtime configuration features + + expires_after: Optional override for session expiration timing in seconds from creation. + Defaults to 10 minutes. + + rate_limits: Optional override for per-minute request limits. When omitted, defaults to 10. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return self._post( + "/chatkit/sessions", + body=maybe_transform( + { + "user": user, + "workflow": workflow, + "chatkit_configuration": chatkit_configuration, + "expires_after": expires_after, + "rate_limits": rate_limits, + }, + session_create_params.SessionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatSession, + ) + + def cancel( + self, + session_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatSession: + """ + Cancel an active ChatKit session and return its most recent metadata. + + Cancelling prevents new requests from using the issued client secret. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not session_id: + raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}") + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return self._post( + path_template("/chatkit/sessions/{session_id}/cancel", session_id=session_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatSession, + ) + + +class AsyncSessions(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSessionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncSessionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSessionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncSessionsWithStreamingResponse(self) + + async def create( + self, + *, + user: str, + workflow: ChatSessionWorkflowParam, + chatkit_configuration: ChatSessionChatKitConfigurationParam | Omit = omit, + expires_after: ChatSessionExpiresAfterParam | Omit = omit, + rate_limits: ChatSessionRateLimitsParam | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatSession: + """ + Create a ChatKit session. + + Args: + user: A free-form string that identifies your end user; ensures this Session can + access other objects that have the same `user` scope. + + workflow: Workflow that powers the session. + + chatkit_configuration: Optional overrides for ChatKit runtime configuration features + + expires_after: Optional override for session expiration timing in seconds from creation. + Defaults to 10 minutes. + + rate_limits: Optional override for per-minute request limits. When omitted, defaults to 10. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return await self._post( + "/chatkit/sessions", + body=await async_maybe_transform( + { + "user": user, + "workflow": workflow, + "chatkit_configuration": chatkit_configuration, + "expires_after": expires_after, + "rate_limits": rate_limits, + }, + session_create_params.SessionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatSession, + ) + + async def cancel( + self, + session_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatSession: + """ + Cancel an active ChatKit session and return its most recent metadata. + + Cancelling prevents new requests from using the issued client secret. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not session_id: + raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}") + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return await self._post( + path_template("/chatkit/sessions/{session_id}/cancel", session_id=session_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatSession, + ) + + +class SessionsWithRawResponse: + def __init__(self, sessions: Sessions) -> None: + self._sessions = sessions + + self.create = _legacy_response.to_raw_response_wrapper( + sessions.create, + ) + self.cancel = _legacy_response.to_raw_response_wrapper( + sessions.cancel, + ) + + +class AsyncSessionsWithRawResponse: + def __init__(self, sessions: AsyncSessions) -> None: + self._sessions = sessions + + self.create = _legacy_response.async_to_raw_response_wrapper( + sessions.create, + ) + self.cancel = _legacy_response.async_to_raw_response_wrapper( + sessions.cancel, + ) + + +class SessionsWithStreamingResponse: + def __init__(self, sessions: Sessions) -> None: + self._sessions = sessions + + self.create = to_streamed_response_wrapper( + sessions.create, + ) + self.cancel = to_streamed_response_wrapper( + sessions.cancel, + ) + + +class AsyncSessionsWithStreamingResponse: + def __init__(self, sessions: AsyncSessions) -> None: + self._sessions = sessions + + self.create = async_to_streamed_response_wrapper( + sessions.create, + ) + self.cancel = async_to_streamed_response_wrapper( + sessions.cancel, + ) diff --git a/src/openai/resources/beta/chatkit/threads.py b/src/openai/resources/beta/chatkit/threads.py new file mode 100644 index 0000000000..05ebf0fb87 --- /dev/null +++ b/src/openai/resources/beta/chatkit/threads.py @@ -0,0 +1,541 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Any, cast +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.beta.chatkit import thread_list_params, thread_list_items_params +from ....types.beta.chatkit.chatkit_thread import ChatKitThread +from ....types.beta.chatkit.thread_delete_response import ThreadDeleteResponse +from ....types.beta.chatkit.chatkit_thread_item_list import Data + +__all__ = ["Threads", "AsyncThreads"] + + +class Threads(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ThreadsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ThreadsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ThreadsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ThreadsWithStreamingResponse(self) + + def retrieve( + self, + thread_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatKitThread: + """ + Retrieve a ChatKit thread by its identifier. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not thread_id: + raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return self._get( + path_template("/chatkit/threads/{thread_id}", thread_id=thread_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatKitThread, + ) + + def list( + self, + *, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[ChatKitThread]: + """ + List ChatKit threads with optional pagination and user filters. + + Args: + after: List items created after this thread item ID. Defaults to null for the first + page. + + before: List items created before this thread item ID. Defaults to null for the newest + results. + + limit: Maximum number of thread items to return. Defaults to 20. + + order: Sort order for results by creation time. Defaults to `desc`. + + user: Filter threads that belong to this user identifier. Defaults to null to return + all users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return self._get_api_list( + "/chatkit/threads", + page=SyncConversationCursorPage[ChatKitThread], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "limit": limit, + "order": order, + "user": user, + }, + thread_list_params.ThreadListParams, + ), + security={"bearer_auth": True}, + ), + model=ChatKitThread, + ) + + def delete( + self, + thread_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ThreadDeleteResponse: + """ + Delete a ChatKit thread along with its items and stored attachments. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not thread_id: + raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return self._delete( + path_template("/chatkit/threads/{thread_id}", thread_id=thread_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ThreadDeleteResponse, + ) + + def list_items( + self, + thread_id: str, + *, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[Data]: + """ + List items that belong to a ChatKit thread. + + Args: + after: List items created after this thread item ID. Defaults to null for the first + page. + + before: List items created before this thread item ID. Defaults to null for the newest + results. + + limit: Maximum number of thread items to return. Defaults to 20. + + order: Sort order for results by creation time. Defaults to `desc`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not thread_id: + raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return self._get_api_list( + path_template("/chatkit/threads/{thread_id}/items", thread_id=thread_id), + page=SyncConversationCursorPage[Data], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "limit": limit, + "order": order, + }, + thread_list_items_params.ThreadListItemsParams, + ), + security={"bearer_auth": True}, + ), + model=cast(Any, Data), # Union types cannot be passed in as arguments in the type system + ) + + +class AsyncThreads(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncThreadsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncThreadsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncThreadsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncThreadsWithStreamingResponse(self) + + async def retrieve( + self, + thread_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatKitThread: + """ + Retrieve a ChatKit thread by its identifier. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not thread_id: + raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return await self._get( + path_template("/chatkit/threads/{thread_id}", thread_id=thread_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatKitThread, + ) + + def list( + self, + *, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ChatKitThread, AsyncConversationCursorPage[ChatKitThread]]: + """ + List ChatKit threads with optional pagination and user filters. + + Args: + after: List items created after this thread item ID. Defaults to null for the first + page. + + before: List items created before this thread item ID. Defaults to null for the newest + results. + + limit: Maximum number of thread items to return. Defaults to 20. + + order: Sort order for results by creation time. Defaults to `desc`. + + user: Filter threads that belong to this user identifier. Defaults to null to return + all users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return self._get_api_list( + "/chatkit/threads", + page=AsyncConversationCursorPage[ChatKitThread], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "limit": limit, + "order": order, + "user": user, + }, + thread_list_params.ThreadListParams, + ), + security={"bearer_auth": True}, + ), + model=ChatKitThread, + ) + + async def delete( + self, + thread_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ThreadDeleteResponse: + """ + Delete a ChatKit thread along with its items and stored attachments. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not thread_id: + raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return await self._delete( + path_template("/chatkit/threads/{thread_id}", thread_id=thread_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ThreadDeleteResponse, + ) + + def list_items( + self, + thread_id: str, + *, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Data, AsyncConversationCursorPage[Data]]: + """ + List items that belong to a ChatKit thread. + + Args: + after: List items created after this thread item ID. Defaults to null for the first + page. + + before: List items created before this thread item ID. Defaults to null for the newest + results. + + limit: Maximum number of thread items to return. Defaults to 20. + + order: Sort order for results by creation time. Defaults to `desc`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not thread_id: + raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") + extra_headers = {"OpenAI-Beta": "chatkit_beta=v1", **(extra_headers or {})} + return self._get_api_list( + path_template("/chatkit/threads/{thread_id}/items", thread_id=thread_id), + page=AsyncConversationCursorPage[Data], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "limit": limit, + "order": order, + }, + thread_list_items_params.ThreadListItemsParams, + ), + security={"bearer_auth": True}, + ), + model=cast(Any, Data), # Union types cannot be passed in as arguments in the type system + ) + + +class ThreadsWithRawResponse: + def __init__(self, threads: Threads) -> None: + self._threads = threads + + self.retrieve = _legacy_response.to_raw_response_wrapper( + threads.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + threads.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + threads.delete, + ) + self.list_items = _legacy_response.to_raw_response_wrapper( + threads.list_items, + ) + + +class AsyncThreadsWithRawResponse: + def __init__(self, threads: AsyncThreads) -> None: + self._threads = threads + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + threads.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + threads.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + threads.delete, + ) + self.list_items = _legacy_response.async_to_raw_response_wrapper( + threads.list_items, + ) + + +class ThreadsWithStreamingResponse: + def __init__(self, threads: Threads) -> None: + self._threads = threads + + self.retrieve = to_streamed_response_wrapper( + threads.retrieve, + ) + self.list = to_streamed_response_wrapper( + threads.list, + ) + self.delete = to_streamed_response_wrapper( + threads.delete, + ) + self.list_items = to_streamed_response_wrapper( + threads.list_items, + ) + + +class AsyncThreadsWithStreamingResponse: + def __init__(self, threads: AsyncThreads) -> None: + self._threads = threads + + self.retrieve = async_to_streamed_response_wrapper( + threads.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + threads.list, + ) + self.delete = async_to_streamed_response_wrapper( + threads.delete, + ) + self.list_items = async_to_streamed_response_wrapper( + threads.list_items, + ) diff --git a/src/openai/resources/beta/realtime/__init__.py b/src/openai/resources/beta/realtime/__init__.py new file mode 100644 index 0000000000..7ab3d9931c --- /dev/null +++ b/src/openai/resources/beta/realtime/__init__.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .realtime import ( + Realtime, + AsyncRealtime, + RealtimeWithRawResponse, + AsyncRealtimeWithRawResponse, + RealtimeWithStreamingResponse, + AsyncRealtimeWithStreamingResponse, +) +from .sessions import ( + Sessions, + AsyncSessions, + SessionsWithRawResponse, + AsyncSessionsWithRawResponse, + SessionsWithStreamingResponse, + AsyncSessionsWithStreamingResponse, +) +from .transcription_sessions import ( + TranscriptionSessions, + AsyncTranscriptionSessions, + TranscriptionSessionsWithRawResponse, + AsyncTranscriptionSessionsWithRawResponse, + TranscriptionSessionsWithStreamingResponse, + AsyncTranscriptionSessionsWithStreamingResponse, +) + +__all__ = [ + "Sessions", + "AsyncSessions", + "SessionsWithRawResponse", + "AsyncSessionsWithRawResponse", + "SessionsWithStreamingResponse", + "AsyncSessionsWithStreamingResponse", + "TranscriptionSessions", + "AsyncTranscriptionSessions", + "TranscriptionSessionsWithRawResponse", + "AsyncTranscriptionSessionsWithRawResponse", + "TranscriptionSessionsWithStreamingResponse", + "AsyncTranscriptionSessionsWithStreamingResponse", + "Realtime", + "AsyncRealtime", + "RealtimeWithRawResponse", + "AsyncRealtimeWithRawResponse", + "RealtimeWithStreamingResponse", + "AsyncRealtimeWithStreamingResponse", +] diff --git a/src/openai/resources/beta/realtime/realtime.py b/src/openai/resources/beta/realtime/realtime.py new file mode 100644 index 0000000000..4fa35963b6 --- /dev/null +++ b/src/openai/resources/beta/realtime/realtime.py @@ -0,0 +1,1094 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import json +import logging +from types import TracebackType +from typing import TYPE_CHECKING, Any, Iterator, cast +from typing_extensions import AsyncIterator + +import httpx +from pydantic import BaseModel + +from .sessions import ( + Sessions, + AsyncSessions, + SessionsWithRawResponse, + AsyncSessionsWithRawResponse, + SessionsWithStreamingResponse, + AsyncSessionsWithStreamingResponse, +) +from ...._types import NOT_GIVEN, Query, Headers, NotGiven +from ...._utils import ( + is_azure_client, + maybe_transform, + strip_not_given, + async_maybe_transform, + is_async_azure_client, +) +from ...._compat import cached_property +from ...._models import construct_type_unchecked +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._exceptions import OpenAIError +from ...._base_client import _merge_mappings +from ....types.beta.realtime import ( + session_update_event_param, + response_create_event_param, + transcription_session_update_param, +) +from .transcription_sessions import ( + TranscriptionSessions, + AsyncTranscriptionSessions, + TranscriptionSessionsWithRawResponse, + AsyncTranscriptionSessionsWithRawResponse, + TranscriptionSessionsWithStreamingResponse, + AsyncTranscriptionSessionsWithStreamingResponse, +) +from ....types.websocket_connection_options import WebsocketConnectionOptions +from ....types.beta.realtime.realtime_client_event import RealtimeClientEvent +from ....types.beta.realtime.realtime_server_event import RealtimeServerEvent +from ....types.beta.realtime.conversation_item_param import ConversationItemParam +from ....types.beta.realtime.realtime_client_event_param import RealtimeClientEventParam + +if TYPE_CHECKING: + from websockets.sync.client import ClientConnection as WebsocketConnection + from websockets.asyncio.client import ClientConnection as AsyncWebsocketConnection + + from ...._client import OpenAI, AsyncOpenAI + +__all__ = ["Realtime", "AsyncRealtime"] + +log: logging.Logger = logging.getLogger(__name__) + + +class Realtime(SyncAPIResource): + @cached_property + def sessions(self) -> Sessions: + return Sessions(self._client) + + @cached_property + def transcription_sessions(self) -> TranscriptionSessions: + return TranscriptionSessions(self._client) + + @cached_property + def with_raw_response(self) -> RealtimeWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return RealtimeWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RealtimeWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return RealtimeWithStreamingResponse(self) + + def connect( + self, + *, + model: str, + extra_query: Query = {}, + extra_headers: Headers = {}, + websocket_connection_options: WebsocketConnectionOptions = {}, + ) -> RealtimeConnectionManager: + """ + The Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as function calling. + + Some notable benefits of the API include: + + - Native speech-to-speech: Skipping an intermediate text format means low latency and nuanced output. + - Natural, steerable voices: The models have natural inflection and can laugh, whisper, and adhere to tone direction. + - Simultaneous multimodal output: Text is useful for moderation; faster-than-realtime audio ensures stable playback. + + The Realtime API is a stateful, event-based API that communicates over a WebSocket. + """ + return RealtimeConnectionManager( + client=self._client, + extra_query=extra_query, + extra_headers=extra_headers, + websocket_connection_options=websocket_connection_options, + model=model, + ) + + +class AsyncRealtime(AsyncAPIResource): + @cached_property + def sessions(self) -> AsyncSessions: + return AsyncSessions(self._client) + + @cached_property + def transcription_sessions(self) -> AsyncTranscriptionSessions: + return AsyncTranscriptionSessions(self._client) + + @cached_property + def with_raw_response(self) -> AsyncRealtimeWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncRealtimeWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRealtimeWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncRealtimeWithStreamingResponse(self) + + def connect( + self, + *, + model: str, + extra_query: Query = {}, + extra_headers: Headers = {}, + websocket_connection_options: WebsocketConnectionOptions = {}, + ) -> AsyncRealtimeConnectionManager: + """ + The Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as function calling. + + Some notable benefits of the API include: + + - Native speech-to-speech: Skipping an intermediate text format means low latency and nuanced output. + - Natural, steerable voices: The models have natural inflection and can laugh, whisper, and adhere to tone direction. + - Simultaneous multimodal output: Text is useful for moderation; faster-than-realtime audio ensures stable playback. + + The Realtime API is a stateful, event-based API that communicates over a WebSocket. + """ + return AsyncRealtimeConnectionManager( + client=self._client, + extra_query=extra_query, + extra_headers=extra_headers, + websocket_connection_options=websocket_connection_options, + model=model, + ) + + +class RealtimeWithRawResponse: + def __init__(self, realtime: Realtime) -> None: + self._realtime = realtime + + @cached_property + def sessions(self) -> SessionsWithRawResponse: + return SessionsWithRawResponse(self._realtime.sessions) + + @cached_property + def transcription_sessions(self) -> TranscriptionSessionsWithRawResponse: + return TranscriptionSessionsWithRawResponse(self._realtime.transcription_sessions) + + +class AsyncRealtimeWithRawResponse: + def __init__(self, realtime: AsyncRealtime) -> None: + self._realtime = realtime + + @cached_property + def sessions(self) -> AsyncSessionsWithRawResponse: + return AsyncSessionsWithRawResponse(self._realtime.sessions) + + @cached_property + def transcription_sessions(self) -> AsyncTranscriptionSessionsWithRawResponse: + return AsyncTranscriptionSessionsWithRawResponse(self._realtime.transcription_sessions) + + +class RealtimeWithStreamingResponse: + def __init__(self, realtime: Realtime) -> None: + self._realtime = realtime + + @cached_property + def sessions(self) -> SessionsWithStreamingResponse: + return SessionsWithStreamingResponse(self._realtime.sessions) + + @cached_property + def transcription_sessions(self) -> TranscriptionSessionsWithStreamingResponse: + return TranscriptionSessionsWithStreamingResponse(self._realtime.transcription_sessions) + + +class AsyncRealtimeWithStreamingResponse: + def __init__(self, realtime: AsyncRealtime) -> None: + self._realtime = realtime + + @cached_property + def sessions(self) -> AsyncSessionsWithStreamingResponse: + return AsyncSessionsWithStreamingResponse(self._realtime.sessions) + + @cached_property + def transcription_sessions(self) -> AsyncTranscriptionSessionsWithStreamingResponse: + return AsyncTranscriptionSessionsWithStreamingResponse(self._realtime.transcription_sessions) + + +class AsyncRealtimeConnection: + """Represents a live websocket connection to the Realtime API""" + + session: AsyncRealtimeSessionResource + response: AsyncRealtimeResponseResource + input_audio_buffer: AsyncRealtimeInputAudioBufferResource + conversation: AsyncRealtimeConversationResource + output_audio_buffer: AsyncRealtimeOutputAudioBufferResource + transcription_session: AsyncRealtimeTranscriptionSessionResource + + _connection: AsyncWebsocketConnection + + def __init__(self, connection: AsyncWebsocketConnection) -> None: + self._connection = connection + + self.session = AsyncRealtimeSessionResource(self) + self.response = AsyncRealtimeResponseResource(self) + self.input_audio_buffer = AsyncRealtimeInputAudioBufferResource(self) + self.conversation = AsyncRealtimeConversationResource(self) + self.output_audio_buffer = AsyncRealtimeOutputAudioBufferResource(self) + self.transcription_session = AsyncRealtimeTranscriptionSessionResource(self) + + async def __aiter__(self) -> AsyncIterator[RealtimeServerEvent]: + """ + An infinite-iterator that will continue to yield events until + the connection is closed. + """ + from websockets.exceptions import ConnectionClosedOK + + try: + while True: + yield await self.recv() + except ConnectionClosedOK: + return + + async def recv(self) -> RealtimeServerEvent: + """ + Receive the next message from the connection and parses it into a `RealtimeServerEvent` object. + + Canceling this method is safe. There's no risk of losing data. + """ + return self.parse_event(await self.recv_bytes()) + + async def recv_bytes(self) -> bytes: + """Receive the next message from the connection as raw bytes. + + Canceling this method is safe. There's no risk of losing data. + + If you want to parse the message into a `RealtimeServerEvent` object like `.recv()` does, + then you can call `.parse_event(data)`. + """ + message = await self._connection.recv(decode=False) + log.debug(f"Received websocket message: %s", message) + return message + + async def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: + data = ( + event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + if isinstance(event, BaseModel) + else json.dumps(await async_maybe_transform(event, RealtimeClientEventParam)) + ) + await self._connection.send(data) + + async def close(self, *, code: int = 1000, reason: str = "") -> None: + await self._connection.close(code=code, reason=reason) + + def parse_event(self, data: str | bytes) -> RealtimeServerEvent: + """ + Converts a raw `str` or `bytes` message into a `RealtimeServerEvent` object. + + This is helpful if you're using `.recv_bytes()`. + """ + return cast( + RealtimeServerEvent, construct_type_unchecked(value=json.loads(data), type_=cast(Any, RealtimeServerEvent)) + ) + + +class AsyncRealtimeConnectionManager: + """ + Context manager over a `AsyncRealtimeConnection` that is returned by `beta.realtime.connect()` + + This context manager ensures that the connection will be closed when it exits. + + --- + + Note that if your application doesn't work well with the context manager approach then you + can call the `.enter()` method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = await client.beta.realtime.connect(...).enter() + # ... + await connection.close() + ``` + """ + + def __init__( + self, + *, + client: AsyncOpenAI, + model: str, + extra_query: Query, + extra_headers: Headers, + websocket_connection_options: WebsocketConnectionOptions, + ) -> None: + self.__client = client + self.__model = model + self.__connection: AsyncRealtimeConnection | None = None + self.__extra_query = extra_query + self.__extra_headers = extra_headers + self.__websocket_connection_options = websocket_connection_options + + async def __aenter__(self) -> AsyncRealtimeConnection: + """ + 👋 If your application doesn't work well with the context manager approach then you + can call this method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = await client.beta.realtime.connect(...).enter() + # ... + await connection.close() + ``` + """ + try: + from websockets.asyncio.client import connect + except ImportError as exc: + raise OpenAIError("You need to install `openai[realtime]` to use this method") from exc + + extra_query = self.__extra_query + await self.__client._refresh_api_key() + auth_headers = self.__client.auth_headers + if is_async_azure_client(self.__client): + url, auth_headers = await self.__client._configure_realtime(self.__model, extra_query) + else: + url = self._prepare_url().copy_with( + params={ + **self.__client.base_url.params, + "model": self.__model, + **extra_query, + }, + ) + log.debug("Connecting to %s", url) + if self.__websocket_connection_options: + log.debug("Connection options: %s", self.__websocket_connection_options) + + self.__connection = AsyncRealtimeConnection( + await connect( + str(url), + user_agent_header=self.__client.user_agent, + additional_headers=_merge_mappings( + { + **auth_headers, + "OpenAI-Beta": "realtime=v1", + }, + self.__extra_headers, + ), + **self.__websocket_connection_options, + ) + ) + + return self.__connection + + enter = __aenter__ + + def _prepare_url(self) -> httpx.URL: + if self.__client.websocket_base_url is not None: + base_url = httpx.URL(self.__client.websocket_base_url) + else: + base_url = self.__client._base_url.copy_with(scheme="wss") + + merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime" + return base_url.copy_with(raw_path=merge_raw_path) + + async def __aexit__( + self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None + ) -> None: + if self.__connection is not None: + await self.__connection.close() + + +class RealtimeConnection: + """Represents a live websocket connection to the Realtime API""" + + session: RealtimeSessionResource + response: RealtimeResponseResource + input_audio_buffer: RealtimeInputAudioBufferResource + conversation: RealtimeConversationResource + output_audio_buffer: RealtimeOutputAudioBufferResource + transcription_session: RealtimeTranscriptionSessionResource + + _connection: WebsocketConnection + + def __init__(self, connection: WebsocketConnection) -> None: + self._connection = connection + + self.session = RealtimeSessionResource(self) + self.response = RealtimeResponseResource(self) + self.input_audio_buffer = RealtimeInputAudioBufferResource(self) + self.conversation = RealtimeConversationResource(self) + self.output_audio_buffer = RealtimeOutputAudioBufferResource(self) + self.transcription_session = RealtimeTranscriptionSessionResource(self) + + def __iter__(self) -> Iterator[RealtimeServerEvent]: + """ + An infinite-iterator that will continue to yield events until + the connection is closed. + """ + from websockets.exceptions import ConnectionClosedOK + + try: + while True: + yield self.recv() + except ConnectionClosedOK: + return + + def recv(self) -> RealtimeServerEvent: + """ + Receive the next message from the connection and parses it into a `RealtimeServerEvent` object. + + Canceling this method is safe. There's no risk of losing data. + """ + return self.parse_event(self.recv_bytes()) + + def recv_bytes(self) -> bytes: + """Receive the next message from the connection as raw bytes. + + Canceling this method is safe. There's no risk of losing data. + + If you want to parse the message into a `RealtimeServerEvent` object like `.recv()` does, + then you can call `.parse_event(data)`. + """ + message = self._connection.recv(decode=False) + log.debug(f"Received websocket message: %s", message) + return message + + def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: + data = ( + event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + if isinstance(event, BaseModel) + else json.dumps(maybe_transform(event, RealtimeClientEventParam)) + ) + self._connection.send(data) + + def close(self, *, code: int = 1000, reason: str = "") -> None: + self._connection.close(code=code, reason=reason) + + def parse_event(self, data: str | bytes) -> RealtimeServerEvent: + """ + Converts a raw `str` or `bytes` message into a `RealtimeServerEvent` object. + + This is helpful if you're using `.recv_bytes()`. + """ + return cast( + RealtimeServerEvent, construct_type_unchecked(value=json.loads(data), type_=cast(Any, RealtimeServerEvent)) + ) + + +class RealtimeConnectionManager: + """ + Context manager over a `RealtimeConnection` that is returned by `beta.realtime.connect()` + + This context manager ensures that the connection will be closed when it exits. + + --- + + Note that if your application doesn't work well with the context manager approach then you + can call the `.enter()` method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = client.beta.realtime.connect(...).enter() + # ... + connection.close() + ``` + """ + + def __init__( + self, + *, + client: OpenAI, + model: str, + extra_query: Query, + extra_headers: Headers, + websocket_connection_options: WebsocketConnectionOptions, + ) -> None: + self.__client = client + self.__model = model + self.__connection: RealtimeConnection | None = None + self.__extra_query = extra_query + self.__extra_headers = extra_headers + self.__websocket_connection_options = websocket_connection_options + + def __enter__(self) -> RealtimeConnection: + """ + 👋 If your application doesn't work well with the context manager approach then you + can call this method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = client.beta.realtime.connect(...).enter() + # ... + connection.close() + ``` + """ + try: + from websockets.sync.client import connect + except ImportError as exc: + raise OpenAIError("You need to install `openai[realtime]` to use this method") from exc + + extra_query = self.__extra_query + self.__client._refresh_api_key() + auth_headers = self.__client.auth_headers + if is_azure_client(self.__client): + url, auth_headers = self.__client._configure_realtime(self.__model, extra_query) + else: + url = self._prepare_url().copy_with( + params={ + **self.__client.base_url.params, + "model": self.__model, + **extra_query, + }, + ) + log.debug("Connecting to %s", url) + if self.__websocket_connection_options: + log.debug("Connection options: %s", self.__websocket_connection_options) + + self.__connection = RealtimeConnection( + connect( + str(url), + user_agent_header=self.__client.user_agent, + additional_headers=_merge_mappings( + { + **auth_headers, + "OpenAI-Beta": "realtime=v1", + }, + self.__extra_headers, + ), + **self.__websocket_connection_options, + ) + ) + + return self.__connection + + enter = __enter__ + + def _prepare_url(self) -> httpx.URL: + if self.__client.websocket_base_url is not None: + base_url = httpx.URL(self.__client.websocket_base_url) + else: + base_url = self.__client._base_url.copy_with(scheme="wss") + + merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime" + return base_url.copy_with(raw_path=merge_raw_path) + + def __exit__( + self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None + ) -> None: + if self.__connection is not None: + self.__connection.close() + + +class BaseRealtimeConnectionResource: + def __init__(self, connection: RealtimeConnection) -> None: + self._connection = connection + + +class RealtimeSessionResource(BaseRealtimeConnectionResource): + def update(self, *, session: session_update_event_param.Session, event_id: str | NotGiven = NOT_GIVEN) -> None: + """ + Send this event to update the session’s default configuration. + The client may send this event at any time to update any field, + except for `voice`. However, note that once a session has been + initialized with a particular `model`, it can’t be changed to + another model using `session.update`. + + When the server receives a `session.update`, it will respond + with a `session.updated` event showing the full, effective configuration. + Only the fields that are present are updated. To clear a field like + `instructions`, pass an empty string. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "session.update", "session": session, "event_id": event_id}), + ) + ) + + +class RealtimeResponseResource(BaseRealtimeConnectionResource): + def create( + self, + *, + event_id: str | NotGiven = NOT_GIVEN, + response: response_create_event_param.Response | NotGiven = NOT_GIVEN, + ) -> None: + """ + This event instructs the server to create a Response, which means triggering + model inference. When in Server VAD mode, the server will create Responses + automatically. + + A Response will include at least one Item, and may have two, in which case + the second will be a function call. These Items will be appended to the + conversation history. + + The server will respond with a `response.created` event, events for Items + and content created, and finally a `response.done` event to indicate the + Response is complete. + + The `response.create` event includes inference configuration like + `instructions`, and `temperature`. These fields will override the Session's + configuration for this Response only. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "response.create", "event_id": event_id, "response": response}), + ) + ) + + def cancel(self, *, event_id: str | NotGiven = NOT_GIVEN, response_id: str | NotGiven = NOT_GIVEN) -> None: + """Send this event to cancel an in-progress response. + + The server will respond + with a `response.done` event with a status of `response.status=cancelled`. If + there is no response to cancel, the server will respond with an error. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "response.cancel", "event_id": event_id, "response_id": response_id}), + ) + ) + + +class RealtimeInputAudioBufferResource(BaseRealtimeConnectionResource): + def clear(self, *, event_id: str | NotGiven = NOT_GIVEN) -> None: + """Send this event to clear the audio bytes in the buffer. + + The server will + respond with an `input_audio_buffer.cleared` event. + """ + self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "input_audio_buffer.clear", "event_id": event_id})) + ) + + def commit(self, *, event_id: str | NotGiven = NOT_GIVEN) -> None: + """ + Send this event to commit the user input audio buffer, which will create a + new user message item in the conversation. This event will produce an error + if the input audio buffer is empty. When in Server VAD mode, the client does + not need to send this event, the server will commit the audio buffer + automatically. + + Committing the input audio buffer will trigger input audio transcription + (if enabled in session configuration), but it will not create a response + from the model. The server will respond with an `input_audio_buffer.committed` + event. + """ + self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "input_audio_buffer.commit", "event_id": event_id})) + ) + + def append(self, *, audio: str, event_id: str | NotGiven = NOT_GIVEN) -> None: + """Send this event to append audio bytes to the input audio buffer. + + The audio + buffer is temporary storage you can write to and later commit. In Server VAD + mode, the audio buffer is used to detect speech and the server will decide + when to commit. When Server VAD is disabled, you must commit the audio buffer + manually. + + The client may choose how much audio to place in each event up to a maximum + of 15 MiB, for example streaming smaller chunks from the client may allow the + VAD to be more responsive. Unlike made other client events, the server will + not send a confirmation response to this event. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "input_audio_buffer.append", "audio": audio, "event_id": event_id}), + ) + ) + + +class RealtimeConversationResource(BaseRealtimeConnectionResource): + @cached_property + def item(self) -> RealtimeConversationItemResource: + return RealtimeConversationItemResource(self._connection) + + +class RealtimeConversationItemResource(BaseRealtimeConnectionResource): + def delete(self, *, item_id: str, event_id: str | NotGiven = NOT_GIVEN) -> None: + """Send this event when you want to remove any item from the conversation + history. + + The server will respond with a `conversation.item.deleted` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "conversation.item.delete", "item_id": item_id, "event_id": event_id}), + ) + ) + + def create( + self, + *, + item: ConversationItemParam, + event_id: str | NotGiven = NOT_GIVEN, + previous_item_id: str | NotGiven = NOT_GIVEN, + ) -> None: + """ + Add a new Item to the Conversation's context, including messages, function + calls, and function call responses. This event can be used both to populate a + "history" of the conversation and to add new items mid-stream, but has the + current limitation that it cannot populate assistant audio messages. + + If successful, the server will respond with a `conversation.item.created` + event, otherwise an `error` event will be sent. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given( + { + "type": "conversation.item.create", + "item": item, + "event_id": event_id, + "previous_item_id": previous_item_id, + } + ), + ) + ) + + def truncate( + self, *, audio_end_ms: int, content_index: int, item_id: str, event_id: str | NotGiven = NOT_GIVEN + ) -> None: + """Send this event to truncate a previous assistant message’s audio. + + The server + will produce audio faster than realtime, so this event is useful when the user + interrupts to truncate audio that has already been sent to the client but not + yet played. This will synchronize the server's understanding of the audio with + the client's playback. + + Truncating audio will delete the server-side text transcript to ensure there + is not text in the context that hasn't been heard by the user. + + If successful, the server will respond with a `conversation.item.truncated` + event. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given( + { + "type": "conversation.item.truncate", + "audio_end_ms": audio_end_ms, + "content_index": content_index, + "item_id": item_id, + "event_id": event_id, + } + ), + ) + ) + + def retrieve(self, *, item_id: str, event_id: str | NotGiven = NOT_GIVEN) -> None: + """ + Send this event when you want to retrieve the server's representation of a specific item in the conversation history. This is useful, for example, to inspect user audio after noise cancellation and VAD. + The server will respond with a `conversation.item.retrieved` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "conversation.item.retrieve", "item_id": item_id, "event_id": event_id}), + ) + ) + + +class RealtimeOutputAudioBufferResource(BaseRealtimeConnectionResource): + def clear(self, *, event_id: str | NotGiven = NOT_GIVEN) -> None: + """**WebRTC Only:** Emit to cut off the current audio response. + + This will trigger the server to + stop generating audio and emit a `output_audio_buffer.cleared` event. This + event should be preceded by a `response.cancel` client event to stop the + generation of the current response. + [Learn more](https://platform.openai.com/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc). + """ + self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "output_audio_buffer.clear", "event_id": event_id})) + ) + + +class RealtimeTranscriptionSessionResource(BaseRealtimeConnectionResource): + def update( + self, *, session: transcription_session_update_param.Session, event_id: str | NotGiven = NOT_GIVEN + ) -> None: + """Send this event to update a transcription session.""" + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "transcription_session.update", "session": session, "event_id": event_id}), + ) + ) + + +class BaseAsyncRealtimeConnectionResource: + def __init__(self, connection: AsyncRealtimeConnection) -> None: + self._connection = connection + + +class AsyncRealtimeSessionResource(BaseAsyncRealtimeConnectionResource): + async def update( + self, *, session: session_update_event_param.Session, event_id: str | NotGiven = NOT_GIVEN + ) -> None: + """ + Send this event to update the session’s default configuration. + The client may send this event at any time to update any field, + except for `voice`. However, note that once a session has been + initialized with a particular `model`, it can’t be changed to + another model using `session.update`. + + When the server receives a `session.update`, it will respond + with a `session.updated` event showing the full, effective configuration. + Only the fields that are present are updated. To clear a field like + `instructions`, pass an empty string. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "session.update", "session": session, "event_id": event_id}), + ) + ) + + +class AsyncRealtimeResponseResource(BaseAsyncRealtimeConnectionResource): + async def create( + self, + *, + event_id: str | NotGiven = NOT_GIVEN, + response: response_create_event_param.Response | NotGiven = NOT_GIVEN, + ) -> None: + """ + This event instructs the server to create a Response, which means triggering + model inference. When in Server VAD mode, the server will create Responses + automatically. + + A Response will include at least one Item, and may have two, in which case + the second will be a function call. These Items will be appended to the + conversation history. + + The server will respond with a `response.created` event, events for Items + and content created, and finally a `response.done` event to indicate the + Response is complete. + + The `response.create` event includes inference configuration like + `instructions`, and `temperature`. These fields will override the Session's + configuration for this Response only. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "response.create", "event_id": event_id, "response": response}), + ) + ) + + async def cancel(self, *, event_id: str | NotGiven = NOT_GIVEN, response_id: str | NotGiven = NOT_GIVEN) -> None: + """Send this event to cancel an in-progress response. + + The server will respond + with a `response.done` event with a status of `response.status=cancelled`. If + there is no response to cancel, the server will respond with an error. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "response.cancel", "event_id": event_id, "response_id": response_id}), + ) + ) + + +class AsyncRealtimeInputAudioBufferResource(BaseAsyncRealtimeConnectionResource): + async def clear(self, *, event_id: str | NotGiven = NOT_GIVEN) -> None: + """Send this event to clear the audio bytes in the buffer. + + The server will + respond with an `input_audio_buffer.cleared` event. + """ + await self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "input_audio_buffer.clear", "event_id": event_id})) + ) + + async def commit(self, *, event_id: str | NotGiven = NOT_GIVEN) -> None: + """ + Send this event to commit the user input audio buffer, which will create a + new user message item in the conversation. This event will produce an error + if the input audio buffer is empty. When in Server VAD mode, the client does + not need to send this event, the server will commit the audio buffer + automatically. + + Committing the input audio buffer will trigger input audio transcription + (if enabled in session configuration), but it will not create a response + from the model. The server will respond with an `input_audio_buffer.committed` + event. + """ + await self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "input_audio_buffer.commit", "event_id": event_id})) + ) + + async def append(self, *, audio: str, event_id: str | NotGiven = NOT_GIVEN) -> None: + """Send this event to append audio bytes to the input audio buffer. + + The audio + buffer is temporary storage you can write to and later commit. In Server VAD + mode, the audio buffer is used to detect speech and the server will decide + when to commit. When Server VAD is disabled, you must commit the audio buffer + manually. + + The client may choose how much audio to place in each event up to a maximum + of 15 MiB, for example streaming smaller chunks from the client may allow the + VAD to be more responsive. Unlike made other client events, the server will + not send a confirmation response to this event. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "input_audio_buffer.append", "audio": audio, "event_id": event_id}), + ) + ) + + +class AsyncRealtimeConversationResource(BaseAsyncRealtimeConnectionResource): + @cached_property + def item(self) -> AsyncRealtimeConversationItemResource: + return AsyncRealtimeConversationItemResource(self._connection) + + +class AsyncRealtimeConversationItemResource(BaseAsyncRealtimeConnectionResource): + async def delete(self, *, item_id: str, event_id: str | NotGiven = NOT_GIVEN) -> None: + """Send this event when you want to remove any item from the conversation + history. + + The server will respond with a `conversation.item.deleted` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "conversation.item.delete", "item_id": item_id, "event_id": event_id}), + ) + ) + + async def create( + self, + *, + item: ConversationItemParam, + event_id: str | NotGiven = NOT_GIVEN, + previous_item_id: str | NotGiven = NOT_GIVEN, + ) -> None: + """ + Add a new Item to the Conversation's context, including messages, function + calls, and function call responses. This event can be used both to populate a + "history" of the conversation and to add new items mid-stream, but has the + current limitation that it cannot populate assistant audio messages. + + If successful, the server will respond with a `conversation.item.created` + event, otherwise an `error` event will be sent. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given( + { + "type": "conversation.item.create", + "item": item, + "event_id": event_id, + "previous_item_id": previous_item_id, + } + ), + ) + ) + + async def truncate( + self, *, audio_end_ms: int, content_index: int, item_id: str, event_id: str | NotGiven = NOT_GIVEN + ) -> None: + """Send this event to truncate a previous assistant message’s audio. + + The server + will produce audio faster than realtime, so this event is useful when the user + interrupts to truncate audio that has already been sent to the client but not + yet played. This will synchronize the server's understanding of the audio with + the client's playback. + + Truncating audio will delete the server-side text transcript to ensure there + is not text in the context that hasn't been heard by the user. + + If successful, the server will respond with a `conversation.item.truncated` + event. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given( + { + "type": "conversation.item.truncate", + "audio_end_ms": audio_end_ms, + "content_index": content_index, + "item_id": item_id, + "event_id": event_id, + } + ), + ) + ) + + async def retrieve(self, *, item_id: str, event_id: str | NotGiven = NOT_GIVEN) -> None: + """ + Send this event when you want to retrieve the server's representation of a specific item in the conversation history. This is useful, for example, to inspect user audio after noise cancellation and VAD. + The server will respond with a `conversation.item.retrieved` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "conversation.item.retrieve", "item_id": item_id, "event_id": event_id}), + ) + ) + + +class AsyncRealtimeOutputAudioBufferResource(BaseAsyncRealtimeConnectionResource): + async def clear(self, *, event_id: str | NotGiven = NOT_GIVEN) -> None: + """**WebRTC Only:** Emit to cut off the current audio response. + + This will trigger the server to + stop generating audio and emit a `output_audio_buffer.cleared` event. This + event should be preceded by a `response.cancel` client event to stop the + generation of the current response. + [Learn more](https://platform.openai.com/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc). + """ + await self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "output_audio_buffer.clear", "event_id": event_id})) + ) + + +class AsyncRealtimeTranscriptionSessionResource(BaseAsyncRealtimeConnectionResource): + async def update( + self, *, session: transcription_session_update_param.Session, event_id: str | NotGiven = NOT_GIVEN + ) -> None: + """Send this event to update a transcription session.""" + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "transcription_session.update", "session": session, "event_id": event_id}), + ) + ) diff --git a/src/openai/resources/beta/realtime/sessions.py b/src/openai/resources/beta/realtime/sessions.py new file mode 100644 index 0000000000..9b85e02d17 --- /dev/null +++ b/src/openai/resources/beta/realtime/sessions.py @@ -0,0 +1,424 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ...._utils import maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...._base_client import make_request_options +from ....types.beta.realtime import session_create_params +from ....types.beta.realtime.session_create_response import SessionCreateResponse + +__all__ = ["Sessions", "AsyncSessions"] + + +class Sessions(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SessionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return SessionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SessionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return SessionsWithStreamingResponse(self) + + def create( + self, + *, + client_secret: session_create_params.ClientSecret | NotGiven = NOT_GIVEN, + input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN, + input_audio_noise_reduction: session_create_params.InputAudioNoiseReduction | NotGiven = NOT_GIVEN, + input_audio_transcription: session_create_params.InputAudioTranscription | NotGiven = NOT_GIVEN, + instructions: str | NotGiven = NOT_GIVEN, + max_response_output_tokens: Union[int, Literal["inf"]] | NotGiven = NOT_GIVEN, + modalities: List[Literal["text", "audio"]] | NotGiven = NOT_GIVEN, + model: Literal[ + "gpt-realtime", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + ] + | NotGiven = NOT_GIVEN, + output_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN, + speed: float | NotGiven = NOT_GIVEN, + temperature: float | NotGiven = NOT_GIVEN, + tool_choice: str | NotGiven = NOT_GIVEN, + tools: Iterable[session_create_params.Tool] | NotGiven = NOT_GIVEN, + tracing: session_create_params.Tracing | NotGiven = NOT_GIVEN, + turn_detection: session_create_params.TurnDetection | NotGiven = NOT_GIVEN, + voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"]] + | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionCreateResponse: + """ + Create an ephemeral API token for use in client-side applications with the + Realtime API. Can be configured with the same session parameters as the + `session.update` client event. + + It responds with a session object, plus a `client_secret` key which contains a + usable ephemeral API token that can be used to authenticate browser clients for + the Realtime API. + + Args: + client_secret: Configuration options for the generated client secret. + + input_audio_format: The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For + `pcm16`, input audio must be 16-bit PCM at a 24kHz sample rate, single channel + (mono), and little-endian byte order. + + input_audio_noise_reduction: Configuration for input audio noise reduction. This can be set to `null` to turn + off. Noise reduction filters audio added to the input audio buffer before it is + sent to VAD and the model. Filtering the audio can improve VAD and turn + detection accuracy (reducing false positives) and model performance by improving + perception of the input audio. + + input_audio_transcription: Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously through + [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) + and should be treated as guidance of input audio content rather than precisely + what the model heard. The client can optionally set the language and prompt for + transcription, these offer additional guidance to the transcription service. + + instructions: The default system instructions (i.e. system message) prepended to model calls. + This field allows the client to guide the model on desired responses. The model + can be instructed on response content and format, (e.g. "be extremely succinct", + "act friendly", "here are examples of good responses") and on audio behavior + (e.g. "talk quickly", "inject emotion into your voice", "laugh frequently"). The + instructions are not guaranteed to be followed by the model, but they provide + guidance to the model on the desired behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + + max_response_output_tokens: Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + + modalities: The set of modalities the model can respond with. To disable audio, set this to + ["text"]. + + model: The Realtime model used for this session. + + output_audio_format: The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. + For `pcm16`, output audio is sampled at a rate of 24kHz. + + speed: The speed of the model's spoken response. 1.0 is the default speed. 0.25 is the + minimum speed. 1.5 is the maximum speed. This value can only be changed in + between model turns, not while a response is in progress. + + temperature: Sampling temperature for the model, limited to [0.6, 1.2]. For audio models a + temperature of 0.8 is highly recommended for best performance. + + tool_choice: How the model chooses tools. Options are `auto`, `none`, `required`, or specify + a function. + + tools: Tools (functions) available to the model. + + tracing: Configuration options for tracing. Set to null to disable tracing. Once tracing + is enabled for a session, the configuration cannot be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + + turn_detection: Configuration for turn detection, ether Server VAD or Semantic VAD. This can be + set to `null` to turn off, in which case the client must manually trigger model + response. Server VAD means that the model will detect the start and end of + speech based on audio volume and respond at the end of user speech. Semantic VAD + is more advanced and uses a turn detection model (in conjunction with VAD) to + semantically estimate whether the user has finished speaking, then dynamically + sets a timeout based on this probability. For example, if user audio trails off + with "uhhm", the model will score a low probability of turn end and wait longer + for the user to continue speaking. This can be useful for more natural + conversations, but may have a higher latency. + + voice: The voice the model uses to respond. Voice cannot be changed during the session + once the model has responded with audio at least once. Current voice options are + `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, and `verse`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return self._post( + "/realtime/sessions", + body=maybe_transform( + { + "client_secret": client_secret, + "input_audio_format": input_audio_format, + "input_audio_noise_reduction": input_audio_noise_reduction, + "input_audio_transcription": input_audio_transcription, + "instructions": instructions, + "max_response_output_tokens": max_response_output_tokens, + "modalities": modalities, + "model": model, + "output_audio_format": output_audio_format, + "speed": speed, + "temperature": temperature, + "tool_choice": tool_choice, + "tools": tools, + "tracing": tracing, + "turn_detection": turn_detection, + "voice": voice, + }, + session_create_params.SessionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SessionCreateResponse, + ) + + +class AsyncSessions(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSessionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncSessionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSessionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncSessionsWithStreamingResponse(self) + + async def create( + self, + *, + client_secret: session_create_params.ClientSecret | NotGiven = NOT_GIVEN, + input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN, + input_audio_noise_reduction: session_create_params.InputAudioNoiseReduction | NotGiven = NOT_GIVEN, + input_audio_transcription: session_create_params.InputAudioTranscription | NotGiven = NOT_GIVEN, + instructions: str | NotGiven = NOT_GIVEN, + max_response_output_tokens: Union[int, Literal["inf"]] | NotGiven = NOT_GIVEN, + modalities: List[Literal["text", "audio"]] | NotGiven = NOT_GIVEN, + model: Literal[ + "gpt-realtime", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + ] + | NotGiven = NOT_GIVEN, + output_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN, + speed: float | NotGiven = NOT_GIVEN, + temperature: float | NotGiven = NOT_GIVEN, + tool_choice: str | NotGiven = NOT_GIVEN, + tools: Iterable[session_create_params.Tool] | NotGiven = NOT_GIVEN, + tracing: session_create_params.Tracing | NotGiven = NOT_GIVEN, + turn_detection: session_create_params.TurnDetection | NotGiven = NOT_GIVEN, + voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"]] + | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionCreateResponse: + """ + Create an ephemeral API token for use in client-side applications with the + Realtime API. Can be configured with the same session parameters as the + `session.update` client event. + + It responds with a session object, plus a `client_secret` key which contains a + usable ephemeral API token that can be used to authenticate browser clients for + the Realtime API. + + Args: + client_secret: Configuration options for the generated client secret. + + input_audio_format: The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For + `pcm16`, input audio must be 16-bit PCM at a 24kHz sample rate, single channel + (mono), and little-endian byte order. + + input_audio_noise_reduction: Configuration for input audio noise reduction. This can be set to `null` to turn + off. Noise reduction filters audio added to the input audio buffer before it is + sent to VAD and the model. Filtering the audio can improve VAD and turn + detection accuracy (reducing false positives) and model performance by improving + perception of the input audio. + + input_audio_transcription: Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously through + [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) + and should be treated as guidance of input audio content rather than precisely + what the model heard. The client can optionally set the language and prompt for + transcription, these offer additional guidance to the transcription service. + + instructions: The default system instructions (i.e. system message) prepended to model calls. + This field allows the client to guide the model on desired responses. The model + can be instructed on response content and format, (e.g. "be extremely succinct", + "act friendly", "here are examples of good responses") and on audio behavior + (e.g. "talk quickly", "inject emotion into your voice", "laugh frequently"). The + instructions are not guaranteed to be followed by the model, but they provide + guidance to the model on the desired behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + + max_response_output_tokens: Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + + modalities: The set of modalities the model can respond with. To disable audio, set this to + ["text"]. + + model: The Realtime model used for this session. + + output_audio_format: The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. + For `pcm16`, output audio is sampled at a rate of 24kHz. + + speed: The speed of the model's spoken response. 1.0 is the default speed. 0.25 is the + minimum speed. 1.5 is the maximum speed. This value can only be changed in + between model turns, not while a response is in progress. + + temperature: Sampling temperature for the model, limited to [0.6, 1.2]. For audio models a + temperature of 0.8 is highly recommended for best performance. + + tool_choice: How the model chooses tools. Options are `auto`, `none`, `required`, or specify + a function. + + tools: Tools (functions) available to the model. + + tracing: Configuration options for tracing. Set to null to disable tracing. Once tracing + is enabled for a session, the configuration cannot be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + + turn_detection: Configuration for turn detection, ether Server VAD or Semantic VAD. This can be + set to `null` to turn off, in which case the client must manually trigger model + response. Server VAD means that the model will detect the start and end of + speech based on audio volume and respond at the end of user speech. Semantic VAD + is more advanced and uses a turn detection model (in conjunction with VAD) to + semantically estimate whether the user has finished speaking, then dynamically + sets a timeout based on this probability. For example, if user audio trails off + with "uhhm", the model will score a low probability of turn end and wait longer + for the user to continue speaking. This can be useful for more natural + conversations, but may have a higher latency. + + voice: The voice the model uses to respond. Voice cannot be changed during the session + once the model has responded with audio at least once. Current voice options are + `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, and `verse`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return await self._post( + "/realtime/sessions", + body=await async_maybe_transform( + { + "client_secret": client_secret, + "input_audio_format": input_audio_format, + "input_audio_noise_reduction": input_audio_noise_reduction, + "input_audio_transcription": input_audio_transcription, + "instructions": instructions, + "max_response_output_tokens": max_response_output_tokens, + "modalities": modalities, + "model": model, + "output_audio_format": output_audio_format, + "speed": speed, + "temperature": temperature, + "tool_choice": tool_choice, + "tools": tools, + "tracing": tracing, + "turn_detection": turn_detection, + "voice": voice, + }, + session_create_params.SessionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SessionCreateResponse, + ) + + +class SessionsWithRawResponse: + def __init__(self, sessions: Sessions) -> None: + self._sessions = sessions + + self.create = _legacy_response.to_raw_response_wrapper( + sessions.create, + ) + + +class AsyncSessionsWithRawResponse: + def __init__(self, sessions: AsyncSessions) -> None: + self._sessions = sessions + + self.create = _legacy_response.async_to_raw_response_wrapper( + sessions.create, + ) + + +class SessionsWithStreamingResponse: + def __init__(self, sessions: Sessions) -> None: + self._sessions = sessions + + self.create = to_streamed_response_wrapper( + sessions.create, + ) + + +class AsyncSessionsWithStreamingResponse: + def __init__(self, sessions: AsyncSessions) -> None: + self._sessions = sessions + + self.create = async_to_streamed_response_wrapper( + sessions.create, + ) diff --git a/src/openai/resources/beta/realtime/transcription_sessions.py b/src/openai/resources/beta/realtime/transcription_sessions.py new file mode 100644 index 0000000000..54fe7d5a6c --- /dev/null +++ b/src/openai/resources/beta/realtime/transcription_sessions.py @@ -0,0 +1,282 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ...._utils import maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...._base_client import make_request_options +from ....types.beta.realtime import transcription_session_create_params +from ....types.beta.realtime.transcription_session import TranscriptionSession + +__all__ = ["TranscriptionSessions", "AsyncTranscriptionSessions"] + + +class TranscriptionSessions(SyncAPIResource): + @cached_property + def with_raw_response(self) -> TranscriptionSessionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return TranscriptionSessionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> TranscriptionSessionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return TranscriptionSessionsWithStreamingResponse(self) + + def create( + self, + *, + client_secret: transcription_session_create_params.ClientSecret | NotGiven = NOT_GIVEN, + include: List[str] | NotGiven = NOT_GIVEN, + input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN, + input_audio_noise_reduction: transcription_session_create_params.InputAudioNoiseReduction + | NotGiven = NOT_GIVEN, + input_audio_transcription: transcription_session_create_params.InputAudioTranscription | NotGiven = NOT_GIVEN, + modalities: List[Literal["text", "audio"]] | NotGiven = NOT_GIVEN, + turn_detection: transcription_session_create_params.TurnDetection | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> TranscriptionSession: + """ + Create an ephemeral API token for use in client-side applications with the + Realtime API specifically for realtime transcriptions. Can be configured with + the same session parameters as the `transcription_session.update` client event. + + It responds with a session object, plus a `client_secret` key which contains a + usable ephemeral API token that can be used to authenticate browser clients for + the Realtime API. + + Args: + client_secret: Configuration options for the generated client secret. + + include: + The set of items to include in the transcription. Current available items are: + + - `item.input_audio_transcription.logprobs` + + input_audio_format: The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For + `pcm16`, input audio must be 16-bit PCM at a 24kHz sample rate, single channel + (mono), and little-endian byte order. + + input_audio_noise_reduction: Configuration for input audio noise reduction. This can be set to `null` to turn + off. Noise reduction filters audio added to the input audio buffer before it is + sent to VAD and the model. Filtering the audio can improve VAD and turn + detection accuracy (reducing false positives) and model performance by improving + perception of the input audio. + + input_audio_transcription: Configuration for input audio transcription. The client can optionally set the + language and prompt for transcription, these offer additional guidance to the + transcription service. + + modalities: The set of modalities the model can respond with. To disable audio, set this to + ["text"]. + + turn_detection: Configuration for turn detection, ether Server VAD or Semantic VAD. This can be + set to `null` to turn off, in which case the client must manually trigger model + response. Server VAD means that the model will detect the start and end of + speech based on audio volume and respond at the end of user speech. Semantic VAD + is more advanced and uses a turn detection model (in conjunction with VAD) to + semantically estimate whether the user has finished speaking, then dynamically + sets a timeout based on this probability. For example, if user audio trails off + with "uhhm", the model will score a low probability of turn end and wait longer + for the user to continue speaking. This can be useful for more natural + conversations, but may have a higher latency. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return self._post( + "/realtime/transcription_sessions", + body=maybe_transform( + { + "client_secret": client_secret, + "include": include, + "input_audio_format": input_audio_format, + "input_audio_noise_reduction": input_audio_noise_reduction, + "input_audio_transcription": input_audio_transcription, + "modalities": modalities, + "turn_detection": turn_detection, + }, + transcription_session_create_params.TranscriptionSessionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TranscriptionSession, + ) + + +class AsyncTranscriptionSessions(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncTranscriptionSessionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncTranscriptionSessionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncTranscriptionSessionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncTranscriptionSessionsWithStreamingResponse(self) + + async def create( + self, + *, + client_secret: transcription_session_create_params.ClientSecret | NotGiven = NOT_GIVEN, + include: List[str] | NotGiven = NOT_GIVEN, + input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] | NotGiven = NOT_GIVEN, + input_audio_noise_reduction: transcription_session_create_params.InputAudioNoiseReduction + | NotGiven = NOT_GIVEN, + input_audio_transcription: transcription_session_create_params.InputAudioTranscription | NotGiven = NOT_GIVEN, + modalities: List[Literal["text", "audio"]] | NotGiven = NOT_GIVEN, + turn_detection: transcription_session_create_params.TurnDetection | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> TranscriptionSession: + """ + Create an ephemeral API token for use in client-side applications with the + Realtime API specifically for realtime transcriptions. Can be configured with + the same session parameters as the `transcription_session.update` client event. + + It responds with a session object, plus a `client_secret` key which contains a + usable ephemeral API token that can be used to authenticate browser clients for + the Realtime API. + + Args: + client_secret: Configuration options for the generated client secret. + + include: + The set of items to include in the transcription. Current available items are: + + - `item.input_audio_transcription.logprobs` + + input_audio_format: The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For + `pcm16`, input audio must be 16-bit PCM at a 24kHz sample rate, single channel + (mono), and little-endian byte order. + + input_audio_noise_reduction: Configuration for input audio noise reduction. This can be set to `null` to turn + off. Noise reduction filters audio added to the input audio buffer before it is + sent to VAD and the model. Filtering the audio can improve VAD and turn + detection accuracy (reducing false positives) and model performance by improving + perception of the input audio. + + input_audio_transcription: Configuration for input audio transcription. The client can optionally set the + language and prompt for transcription, these offer additional guidance to the + transcription service. + + modalities: The set of modalities the model can respond with. To disable audio, set this to + ["text"]. + + turn_detection: Configuration for turn detection, ether Server VAD or Semantic VAD. This can be + set to `null` to turn off, in which case the client must manually trigger model + response. Server VAD means that the model will detect the start and end of + speech based on audio volume and respond at the end of user speech. Semantic VAD + is more advanced and uses a turn detection model (in conjunction with VAD) to + semantically estimate whether the user has finished speaking, then dynamically + sets a timeout based on this probability. For example, if user audio trails off + with "uhhm", the model will score a low probability of turn end and wait longer + for the user to continue speaking. This can be useful for more natural + conversations, but may have a higher latency. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return await self._post( + "/realtime/transcription_sessions", + body=await async_maybe_transform( + { + "client_secret": client_secret, + "include": include, + "input_audio_format": input_audio_format, + "input_audio_noise_reduction": input_audio_noise_reduction, + "input_audio_transcription": input_audio_transcription, + "modalities": modalities, + "turn_detection": turn_detection, + }, + transcription_session_create_params.TranscriptionSessionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TranscriptionSession, + ) + + +class TranscriptionSessionsWithRawResponse: + def __init__(self, transcription_sessions: TranscriptionSessions) -> None: + self._transcription_sessions = transcription_sessions + + self.create = _legacy_response.to_raw_response_wrapper( + transcription_sessions.create, + ) + + +class AsyncTranscriptionSessionsWithRawResponse: + def __init__(self, transcription_sessions: AsyncTranscriptionSessions) -> None: + self._transcription_sessions = transcription_sessions + + self.create = _legacy_response.async_to_raw_response_wrapper( + transcription_sessions.create, + ) + + +class TranscriptionSessionsWithStreamingResponse: + def __init__(self, transcription_sessions: TranscriptionSessions) -> None: + self._transcription_sessions = transcription_sessions + + self.create = to_streamed_response_wrapper( + transcription_sessions.create, + ) + + +class AsyncTranscriptionSessionsWithStreamingResponse: + def __init__(self, transcription_sessions: AsyncTranscriptionSessions) -> None: + self._transcription_sessions = transcription_sessions + + self.create = async_to_streamed_response_wrapper( + transcription_sessions.create, + ) diff --git a/src/openai/resources/beta/threads/messages.py b/src/openai/resources/beta/threads/messages.py index 4901174329..e57e783577 100644 --- a/src/openai/resources/beta/threads/messages.py +++ b/src/openai/resources/beta/threads/messages.py @@ -2,17 +2,15 @@ from __future__ import annotations +import typing_extensions from typing import Union, Iterable, Optional from typing_extensions import Literal import httpx from .... import _legacy_response -from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ...._utils import ( - maybe_transform, - async_maybe_transform, -) +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -23,6 +21,7 @@ ) from ....types.beta.threads import message_list_params, message_create_params, message_update_params from ....types.beta.threads.message import Message +from ....types.shared_params.metadata import Metadata from ....types.beta.threads.message_deleted import MessageDeleted from ....types.beta.threads.message_content_part_param import MessageContentPartParam @@ -30,10 +29,12 @@ class Messages(SyncAPIResource): + """Build Assistants that can call models and use tools.""" + @cached_property def with_raw_response(self) -> MessagesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -49,20 +50,21 @@ def with_streaming_response(self) -> MessagesWithStreamingResponse: """ return MessagesWithStreamingResponse(self) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def create( self, thread_id: str, *, content: Union[str, Iterable[MessageContentPartParam]], role: Literal["user", "assistant"], - attachments: Optional[Iterable[message_create_params.Attachment]] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, + attachments: Optional[Iterable[message_create_params.Attachment]] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Message: """ Create a message. @@ -81,9 +83,11 @@ def create( attachments: A list of files attached to the message, and the tools they should be added to. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. extra_headers: Send extra headers @@ -97,7 +101,7 @@ def create( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/threads/{thread_id}/messages", + path_template("/threads/{thread_id}/messages", thread_id=thread_id), body=maybe_transform( { "content": content, @@ -108,11 +112,16 @@ def create( message_create_params.MessageCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Message, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def retrieve( self, message_id: str, @@ -123,7 +132,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Message: """ Retrieve a message. @@ -143,34 +152,41 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get( - f"/threads/{thread_id}/messages/{message_id}", + path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Message, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def update( self, message_id: str, *, thread_id: str, - metadata: Optional[object] | NotGiven = NOT_GIVEN, + metadata: Optional[Metadata] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Message: """ Modifies a message. Args: metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. extra_headers: Send extra headers @@ -186,29 +202,34 @@ def update( raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/threads/{thread_id}/messages/{message_id}", + path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id), body=maybe_transform({"metadata": metadata}, message_update_params.MessageUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Message, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def list( self, thread_id: str, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - run_id: str | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + run_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Message]: """ Returns a list of messages for a given thread. @@ -221,8 +242,8 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20. @@ -244,7 +265,7 @@ def list( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get_api_list( - f"/threads/{thread_id}/messages", + path_template("/threads/{thread_id}/messages", thread_id=thread_id), page=SyncCursorPage[Message], options=make_request_options( extra_headers=extra_headers, @@ -261,10 +282,12 @@ def list( }, message_list_params.MessageListParams, ), + security={"bearer_auth": True}, ), model=Message, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def delete( self, message_id: str, @@ -275,7 +298,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MessageDeleted: """ Deletes a message. @@ -295,19 +318,25 @@ def delete( raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._delete( - f"/threads/{thread_id}/messages/{message_id}", + path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=MessageDeleted, ) class AsyncMessages(AsyncAPIResource): + """Build Assistants that can call models and use tools.""" + @cached_property def with_raw_response(self) -> AsyncMessagesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -323,20 +352,21 @@ def with_streaming_response(self) -> AsyncMessagesWithStreamingResponse: """ return AsyncMessagesWithStreamingResponse(self) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create( self, thread_id: str, *, content: Union[str, Iterable[MessageContentPartParam]], role: Literal["user", "assistant"], - attachments: Optional[Iterable[message_create_params.Attachment]] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, + attachments: Optional[Iterable[message_create_params.Attachment]] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Message: """ Create a message. @@ -355,9 +385,11 @@ async def create( attachments: A list of files attached to the message, and the tools they should be added to. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. extra_headers: Send extra headers @@ -371,7 +403,7 @@ async def create( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/threads/{thread_id}/messages", + path_template("/threads/{thread_id}/messages", thread_id=thread_id), body=await async_maybe_transform( { "content": content, @@ -382,11 +414,16 @@ async def create( message_create_params.MessageCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Message, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def retrieve( self, message_id: str, @@ -397,7 +434,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Message: """ Retrieve a message. @@ -417,34 +454,41 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._get( - f"/threads/{thread_id}/messages/{message_id}", + path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Message, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def update( self, message_id: str, *, thread_id: str, - metadata: Optional[object] | NotGiven = NOT_GIVEN, + metadata: Optional[Metadata] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Message: """ Modifies a message. Args: metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. extra_headers: Send extra headers @@ -460,29 +504,34 @@ async def update( raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/threads/{thread_id}/messages/{message_id}", + path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id), body=await async_maybe_transform({"metadata": metadata}, message_update_params.MessageUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Message, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def list( self, thread_id: str, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - run_id: str | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + run_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Message, AsyncCursorPage[Message]]: """ Returns a list of messages for a given thread. @@ -495,8 +544,8 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20. @@ -518,7 +567,7 @@ def list( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get_api_list( - f"/threads/{thread_id}/messages", + path_template("/threads/{thread_id}/messages", thread_id=thread_id), page=AsyncCursorPage[Message], options=make_request_options( extra_headers=extra_headers, @@ -535,10 +584,12 @@ def list( }, message_list_params.MessageListParams, ), + security={"bearer_auth": True}, ), model=Message, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def delete( self, message_id: str, @@ -549,7 +600,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MessageDeleted: """ Deletes a message. @@ -569,9 +620,13 @@ async def delete( raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._delete( - f"/threads/{thread_id}/messages/{message_id}", + path_template("/threads/{thread_id}/messages/{message_id}", thread_id=thread_id, message_id=message_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=MessageDeleted, ) @@ -581,20 +636,30 @@ class MessagesWithRawResponse: def __init__(self, messages: Messages) -> None: self._messages = messages - self.create = _legacy_response.to_raw_response_wrapper( - messages.create, + self.create = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + messages.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = _legacy_response.to_raw_response_wrapper( - messages.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + messages.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = _legacy_response.to_raw_response_wrapper( - messages.update, + self.update = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + messages.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = _legacy_response.to_raw_response_wrapper( - messages.list, + self.list = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + messages.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = _legacy_response.to_raw_response_wrapper( - messages.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + messages.delete, # pyright: ignore[reportDeprecated], + ) ) @@ -602,20 +667,30 @@ class AsyncMessagesWithRawResponse: def __init__(self, messages: AsyncMessages) -> None: self._messages = messages - self.create = _legacy_response.async_to_raw_response_wrapper( - messages.create, + self.create = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + messages.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = _legacy_response.async_to_raw_response_wrapper( - messages.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + messages.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = _legacy_response.async_to_raw_response_wrapper( - messages.update, + self.update = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + messages.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = _legacy_response.async_to_raw_response_wrapper( - messages.list, + self.list = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + messages.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = _legacy_response.async_to_raw_response_wrapper( - messages.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + messages.delete, # pyright: ignore[reportDeprecated], + ) ) @@ -623,20 +698,30 @@ class MessagesWithStreamingResponse: def __init__(self, messages: Messages) -> None: self._messages = messages - self.create = to_streamed_response_wrapper( - messages.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + messages.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = to_streamed_response_wrapper( - messages.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + messages.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = to_streamed_response_wrapper( - messages.update, + self.update = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + messages.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = to_streamed_response_wrapper( - messages.list, + self.list = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + messages.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = to_streamed_response_wrapper( - messages.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + messages.delete, # pyright: ignore[reportDeprecated], + ) ) @@ -644,18 +729,28 @@ class AsyncMessagesWithStreamingResponse: def __init__(self, messages: AsyncMessages) -> None: self._messages = messages - self.create = async_to_streamed_response_wrapper( - messages.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + messages.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = async_to_streamed_response_wrapper( - messages.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + messages.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = async_to_streamed_response_wrapper( - messages.update, + self.update = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + messages.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = async_to_streamed_response_wrapper( - messages.list, + self.list = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + messages.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = async_to_streamed_response_wrapper( - messages.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + messages.delete, # pyright: ignore[reportDeprecated], + ) ) diff --git a/src/openai/resources/beta/threads/runs/runs.py b/src/openai/resources/beta/threads/runs/runs.py index 3fb1cc77aa..385d4c680c 100644 --- a/src/openai/resources/beta/threads/runs/runs.py +++ b/src/openai/resources/beta/threads/runs/runs.py @@ -3,9 +3,9 @@ from __future__ import annotations import typing_extensions -from typing import List, Union, Iterable, Optional, overload +from typing import List, Union, Iterable, Optional from functools import partial -from typing_extensions import Literal +from typing_extensions import Literal, overload import httpx @@ -18,9 +18,10 @@ StepsWithStreamingResponse, AsyncStepsWithStreamingResponse, ) -from ....._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ....._types import NOT_GIVEN, Body, Omit, Query, Headers, NotGiven, omit, not_given from ....._utils import ( is_given, + path_template, required_args, maybe_transform, async_maybe_transform, @@ -39,7 +40,6 @@ AsyncAssistantEventHandlerT, AsyncAssistantStreamManager, ) -from .....types.chat_model import ChatModel from .....types.beta.threads import ( run_list_params, run_create_params, @@ -47,6 +47,9 @@ run_submit_tool_outputs_params, ) from .....types.beta.threads.run import Run +from .....types.shared.chat_model import ChatModel +from .....types.shared_params.metadata import Metadata +from .....types.shared.reasoning_effort import ReasoningEffort from .....types.beta.assistant_tool_param import AssistantToolParam from .....types.beta.assistant_stream_event import AssistantStreamEvent from .....types.beta.threads.runs.run_step_include import RunStepInclude @@ -57,14 +60,17 @@ class Runs(SyncAPIResource): + """Build Assistants that can call models and use tools.""" + @cached_property def steps(self) -> Steps: + """Build Assistants that can call models and use tools.""" return Steps(self._client) @cached_property def with_raw_response(self) -> RunsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -81,33 +87,35 @@ def with_streaming_response(self) -> RunsWithStreamingResponse: return RunsWithStreamingResponse(self) @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def create( self, thread_id: str, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ Create a run. @@ -122,7 +130,7 @@ def create( to fetch the file search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. additional_instructions: Appends additional instructions at the end of the instructions for the run. This @@ -148,9 +156,11 @@ def create( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -158,12 +168,26 @@ def create( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -208,7 +232,7 @@ def create( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -221,33 +245,35 @@ def create( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def create( self, thread_id: str, *, assistant_id: str, stream: Literal[True], - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Stream[AssistantStreamEvent]: """ Create a run. @@ -266,7 +292,7 @@ def create( to fetch the file search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. additional_instructions: Appends additional instructions at the end of the instructions for the run. This @@ -292,9 +318,11 @@ def create( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -302,12 +330,26 @@ def create( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -348,7 +390,7 @@ def create( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -361,33 +403,35 @@ def create( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def create( self, thread_id: str, *, assistant_id: str, stream: bool, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: """ Create a run. @@ -406,7 +450,7 @@ def create( to fetch the file search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. additional_instructions: Appends additional instructions at the end of the instructions for the run. This @@ -432,9 +476,11 @@ def create( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -442,12 +488,26 @@ def create( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -488,7 +548,7 @@ def create( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -500,40 +560,42 @@ def create( """ ... + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") @required_args(["assistant_id"], ["assistant_id", "stream"]) def create( self, thread_id: str, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | Literal[True] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: if not thread_id: raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/threads/{thread_id}/runs", + path_template("/threads/{thread_id}/runs", thread_id=thread_id), body=maybe_transform( { "assistant_id": assistant_id, @@ -545,6 +607,7 @@ def create( "metadata": metadata, "model": model, "parallel_tool_calls": parallel_tool_calls, + "reasoning_effort": reasoning_effort, "response_format": response_format, "stream": stream, "temperature": temperature, @@ -553,7 +616,7 @@ def create( "top_p": top_p, "truncation_strategy": truncation_strategy, }, - run_create_params.RunCreateParams, + run_create_params.RunCreateParamsStreaming if stream else run_create_params.RunCreateParamsNonStreaming, ), options=make_request_options( extra_headers=extra_headers, @@ -561,12 +624,15 @@ def create( extra_body=extra_body, timeout=timeout, query=maybe_transform({"include": include}, run_create_params.RunCreateParams), + security={"bearer_auth": True}, + synthesize_event_and_data=True, ), cast_to=Run, stream=stream or False, stream_cls=Stream[AssistantStreamEvent], ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def retrieve( self, run_id: str, @@ -577,7 +643,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ Retrieves a run. @@ -597,34 +663,41 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get( - f"/threads/{thread_id}/runs/{run_id}", + path_template("/threads/{thread_id}/runs/{run_id}", thread_id=thread_id, run_id=run_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def update( self, run_id: str, *, thread_id: str, - metadata: Optional[object] | NotGiven = NOT_GIVEN, + metadata: Optional[Metadata] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ Modifies a run. Args: metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. extra_headers: Send extra headers @@ -640,28 +713,33 @@ def update( raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/threads/{thread_id}/runs/{run_id}", + path_template("/threads/{thread_id}/runs/{run_id}", thread_id=thread_id, run_id=run_id), body=maybe_transform({"metadata": metadata}, run_update_params.RunUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def list( self, thread_id: str, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Run]: """ Returns a list of runs belonging to a thread. @@ -674,8 +752,8 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20. @@ -695,7 +773,7 @@ def list( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get_api_list( - f"/threads/{thread_id}/runs", + path_template("/threads/{thread_id}/runs", thread_id=thread_id), page=SyncCursorPage[Run], options=make_request_options( extra_headers=extra_headers, @@ -711,10 +789,12 @@ def list( }, run_list_params.RunListParams, ), + security={"bearer_auth": True}, ), model=Run, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def cancel( self, run_id: str, @@ -725,7 +805,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ Cancels a run that is `in_progress`. @@ -745,33 +825,39 @@ def cancel( raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/threads/{thread_id}/runs/{run_id}/cancel", + path_template("/threads/{thread_id}/runs/{run_id}/cancel", thread_id=thread_id, run_id=run_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def create_and_poll( self, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, + poll_interval_ms: int | Omit = omit, thread_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -785,7 +871,7 @@ def create_and_poll( lifecycles can be found here: https://platform.openai.com/docs/assistants/how-it-works/runs-and-run-steps """ - run = self.create( + run = self.create( # pyright: ignore[reportDeprecated] thread_id=thread_id, assistant_id=assistant_id, include=include, @@ -800,6 +886,7 @@ def create_and_poll( temperature=temperature, tool_choice=tool_choice, parallel_tool_calls=parallel_tool_calls, + reasoning_effort=reasoning_effort, # We assume we are not streaming when polling stream=False, tools=tools, @@ -810,7 +897,7 @@ def create_and_poll( extra_body=extra_body, timeout=timeout, ) - return self.poll( + return self.poll( # pyright: ignore[reportDeprecated] run.id, thread_id=thread_id, extra_headers=extra_headers, @@ -826,20 +913,21 @@ def create_and_stream( self, *, assistant_id: str, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -857,20 +945,21 @@ def create_and_stream( self, *, assistant_id: str, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, event_handler: AssistantEventHandlerT, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -888,20 +977,21 @@ def create_and_stream( self, *, assistant_id: str, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, event_handler: AssistantEventHandlerT | None = None, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -923,7 +1013,7 @@ def create_and_stream( } make_request = partial( self._post, - f"/threads/{thread_id}/runs", + path_template("/threads/{thread_id}/runs", thread_id=thread_id), body=maybe_transform( { "assistant_id": assistant_id, @@ -941,12 +1031,17 @@ def create_and_stream( "tools": tools, "truncation_strategy": truncation_strategy, "parallel_tool_calls": parallel_tool_calls, + "reasoning_effort": reasoning_effort, "top_p": top_p, }, run_create_params.RunCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, stream=True, @@ -954,6 +1049,7 @@ def create_and_stream( ) return AssistantStreamManager(make_request, event_handler=event_handler or AssistantEventHandler()) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def poll( self, run_id: str, @@ -961,8 +1057,8 @@ def poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + poll_interval_ms: int | Omit = omit, ) -> Run: """ A helper to poll a run status until it reaches a terminal state. More @@ -976,7 +1072,7 @@ def poll( terminal_states = {"requires_action", "cancelled", "completed", "failed", "expired", "incomplete"} while True: - response = self.with_raw_response.retrieve( + response = self.with_raw_response.retrieve( # pyright: ignore[reportDeprecated] thread_id=thread_id, run_id=run_id, extra_headers=extra_headers, @@ -1000,25 +1096,27 @@ def poll( self._sleep(poll_interval_ms / 1000) @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def stream( self, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1031,25 +1129,27 @@ def stream( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def stream( self, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, event_handler: AssistantEventHandlerT, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1062,25 +1162,27 @@ def stream( """Create a Run stream""" ... + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def stream( self, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, event_handler: AssistantEventHandlerT | None = None, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1102,11 +1204,10 @@ def stream( } make_request = partial( self._post, - f"/threads/{thread_id}/runs", + path_template("/threads/{thread_id}/runs", thread_id=thread_id), body=maybe_transform( { "assistant_id": assistant_id, - "include": include, "additional_instructions": additional_instructions, "additional_messages": additional_messages, "instructions": instructions, @@ -1120,13 +1221,19 @@ def stream( "stream": True, "tools": tools, "parallel_tool_calls": parallel_tool_calls, + "reasoning_effort": reasoning_effort, "truncation_strategy": truncation_strategy, "top_p": top_p, }, run_create_params.RunCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"include": include}, run_create_params.RunCreateParams), + security={"bearer_auth": True}, ), cast_to=Run, stream=True, @@ -1135,19 +1242,20 @@ def stream( return AssistantStreamManager(make_request, event_handler=event_handler or AssistantEventHandler()) @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs( self, run_id: str, *, thread_id: str, tool_outputs: Iterable[run_submit_tool_outputs_params.ToolOutput], - stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN, + stream: Optional[Literal[False]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -1173,6 +1281,7 @@ def submit_tool_outputs( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs( self, run_id: str, @@ -1185,7 +1294,7 @@ def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Stream[AssistantStreamEvent]: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -1211,6 +1320,7 @@ def submit_tool_outputs( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs( self, run_id: str, @@ -1223,7 +1333,7 @@ def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -1248,20 +1358,22 @@ def submit_tool_outputs( """ ... + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") @required_args(["thread_id", "tool_outputs"], ["thread_id", "stream", "tool_outputs"]) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs( self, run_id: str, *, thread_id: str, tool_outputs: Iterable[run_submit_tool_outputs_params.ToolOutput], - stream: Optional[Literal[False]] | Literal[True] | NotGiven = NOT_GIVEN, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: if not thread_id: raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") @@ -1269,29 +1381,37 @@ def submit_tool_outputs( raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/threads/{thread_id}/runs/{run_id}/submit_tool_outputs", + path_template("/threads/{thread_id}/runs/{run_id}/submit_tool_outputs", thread_id=thread_id, run_id=run_id), body=maybe_transform( { "tool_outputs": tool_outputs, "stream": stream, }, - run_submit_tool_outputs_params.RunSubmitToolOutputsParams, + run_submit_tool_outputs_params.RunSubmitToolOutputsParamsStreaming + if stream + else run_submit_tool_outputs_params.RunSubmitToolOutputsParamsNonStreaming, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + synthesize_event_and_data=True, ), cast_to=Run, stream=stream or False, stream_cls=Stream[AssistantStreamEvent], ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs_and_poll( self, *, tool_outputs: Iterable[run_submit_tool_outputs_params.ToolOutput], run_id: str, thread_id: str, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + poll_interval_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -1304,7 +1424,7 @@ def submit_tool_outputs_and_poll( More information on Run lifecycles can be found here: https://platform.openai.com/docs/assistants/how-it-works/runs-and-run-steps """ - run = self.submit_tool_outputs( + run = self.submit_tool_outputs( # pyright: ignore[reportDeprecated] run_id=run_id, thread_id=thread_id, tool_outputs=tool_outputs, @@ -1314,7 +1434,7 @@ def submit_tool_outputs_and_poll( extra_body=extra_body, timeout=timeout, ) - return self.poll( + return self.poll( # pyright: ignore[reportDeprecated] run_id=run.id, thread_id=thread_id, extra_headers=extra_headers, @@ -1325,6 +1445,7 @@ def submit_tool_outputs_and_poll( ) @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs_stream( self, *, @@ -1346,6 +1467,7 @@ def submit_tool_outputs_stream( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs_stream( self, *, @@ -1367,6 +1489,7 @@ def submit_tool_outputs_stream( """ ... + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs_stream( self, *, @@ -1400,7 +1523,7 @@ def submit_tool_outputs_stream( } request = partial( self._post, - f"/threads/{thread_id}/runs/{run_id}/submit_tool_outputs", + path_template("/threads/{thread_id}/runs/{run_id}/submit_tool_outputs", thread_id=thread_id, run_id=run_id), body=maybe_transform( { "tool_outputs": tool_outputs, @@ -1409,7 +1532,11 @@ def submit_tool_outputs_stream( run_submit_tool_outputs_params.RunSubmitToolOutputsParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, stream=True, @@ -1419,14 +1546,17 @@ def submit_tool_outputs_stream( class AsyncRuns(AsyncAPIResource): + """Build Assistants that can call models and use tools.""" + @cached_property def steps(self) -> AsyncSteps: + """Build Assistants that can call models and use tools.""" return AsyncSteps(self._client) @cached_property def with_raw_response(self) -> AsyncRunsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -1443,33 +1573,35 @@ def with_streaming_response(self) -> AsyncRunsWithStreamingResponse: return AsyncRunsWithStreamingResponse(self) @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create( self, thread_id: str, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ Create a run. @@ -1484,7 +1616,7 @@ async def create( to fetch the file search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. additional_instructions: Appends additional instructions at the end of the instructions for the run. This @@ -1510,9 +1642,11 @@ async def create( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -1520,12 +1654,26 @@ async def create( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -1570,7 +1718,7 @@ async def create( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -1583,33 +1731,35 @@ async def create( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create( self, thread_id: str, *, assistant_id: str, stream: Literal[True], - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncStream[AssistantStreamEvent]: """ Create a run. @@ -1628,7 +1778,7 @@ async def create( to fetch the file search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. additional_instructions: Appends additional instructions at the end of the instructions for the run. This @@ -1654,9 +1804,11 @@ async def create( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -1664,12 +1816,26 @@ async def create( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -1710,7 +1876,7 @@ async def create( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -1723,33 +1889,35 @@ async def create( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create( self, thread_id: str, *, assistant_id: str, stream: bool, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: """ Create a run. @@ -1768,7 +1936,7 @@ async def create( to fetch the file search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. additional_instructions: Appends additional instructions at the end of the instructions for the run. This @@ -1794,9 +1962,11 @@ async def create( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -1804,12 +1974,26 @@ async def create( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -1850,7 +2034,7 @@ async def create( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -1862,44 +2046,46 @@ async def create( """ ... + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") @required_args(["assistant_id"], ["assistant_id", "stream"]) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create( self, thread_id: str, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | Literal[True] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: if not thread_id: raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/threads/{thread_id}/runs", + path_template("/threads/{thread_id}/runs", thread_id=thread_id), body=await async_maybe_transform( { "assistant_id": assistant_id, - "include": include, "additional_instructions": additional_instructions, "additional_messages": additional_messages, "instructions": instructions, @@ -1908,6 +2094,7 @@ async def create( "metadata": metadata, "model": model, "parallel_tool_calls": parallel_tool_calls, + "reasoning_effort": reasoning_effort, "response_format": response_format, "stream": stream, "temperature": temperature, @@ -1916,7 +2103,7 @@ async def create( "top_p": top_p, "truncation_strategy": truncation_strategy, }, - run_create_params.RunCreateParams, + run_create_params.RunCreateParamsStreaming if stream else run_create_params.RunCreateParamsNonStreaming, ), options=make_request_options( extra_headers=extra_headers, @@ -1924,12 +2111,15 @@ async def create( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform({"include": include}, run_create_params.RunCreateParams), + security={"bearer_auth": True}, + synthesize_event_and_data=True, ), cast_to=Run, stream=stream or False, stream_cls=AsyncStream[AssistantStreamEvent], ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def retrieve( self, run_id: str, @@ -1940,7 +2130,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ Retrieves a run. @@ -1960,34 +2150,41 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._get( - f"/threads/{thread_id}/runs/{run_id}", + path_template("/threads/{thread_id}/runs/{run_id}", thread_id=thread_id, run_id=run_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def update( self, run_id: str, *, thread_id: str, - metadata: Optional[object] | NotGiven = NOT_GIVEN, + metadata: Optional[Metadata] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ Modifies a run. Args: metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. extra_headers: Send extra headers @@ -2003,28 +2200,33 @@ async def update( raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/threads/{thread_id}/runs/{run_id}", + path_template("/threads/{thread_id}/runs/{run_id}", thread_id=thread_id, run_id=run_id), body=await async_maybe_transform({"metadata": metadata}, run_update_params.RunUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def list( self, thread_id: str, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Run, AsyncCursorPage[Run]]: """ Returns a list of runs belonging to a thread. @@ -2037,8 +2239,8 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20. @@ -2058,7 +2260,7 @@ def list( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get_api_list( - f"/threads/{thread_id}/runs", + path_template("/threads/{thread_id}/runs", thread_id=thread_id), page=AsyncCursorPage[Run], options=make_request_options( extra_headers=extra_headers, @@ -2074,10 +2276,12 @@ def list( }, run_list_params.RunListParams, ), + security={"bearer_auth": True}, ), model=Run, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def cancel( self, run_id: str, @@ -2088,7 +2292,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ Cancels a run that is `in_progress`. @@ -2108,33 +2312,39 @@ async def cancel( raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/threads/{thread_id}/runs/{run_id}/cancel", + path_template("/threads/{thread_id}/runs/{run_id}/cancel", thread_id=thread_id, run_id=run_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create_and_poll( self, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, + poll_interval_ms: int | Omit = omit, thread_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -2148,7 +2358,7 @@ async def create_and_poll( lifecycles can be found here: https://platform.openai.com/docs/assistants/how-it-works/runs-and-run-steps """ - run = await self.create( + run = await self.create( # pyright: ignore[reportDeprecated] thread_id=thread_id, assistant_id=assistant_id, include=include, @@ -2163,6 +2373,7 @@ async def create_and_poll( temperature=temperature, tool_choice=tool_choice, parallel_tool_calls=parallel_tool_calls, + reasoning_effort=reasoning_effort, # We assume we are not streaming when polling stream=False, tools=tools, @@ -2173,7 +2384,7 @@ async def create_and_poll( extra_body=extra_body, timeout=timeout, ) - return await self.poll( + return await self.poll( # pyright: ignore[reportDeprecated] run.id, thread_id=thread_id, extra_headers=extra_headers, @@ -2189,20 +2400,20 @@ def create_and_stream( self, *, assistant_id: str, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -2220,20 +2431,20 @@ def create_and_stream( self, *, assistant_id: str, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, event_handler: AsyncAssistantEventHandlerT, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -2251,20 +2462,20 @@ def create_and_stream( self, *, assistant_id: str, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, event_handler: AsyncAssistantEventHandlerT | None = None, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -2288,7 +2499,7 @@ def create_and_stream( **(extra_headers or {}), } request = self._post( - f"/threads/{thread_id}/runs", + path_template("/threads/{thread_id}/runs", thread_id=thread_id), body=maybe_transform( { "assistant_id": assistant_id, @@ -2311,7 +2522,11 @@ def create_and_stream( run_create_params.RunCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, stream=True, @@ -2319,6 +2534,7 @@ def create_and_stream( ) return AsyncAssistantStreamManager(request, event_handler=event_handler or AsyncAssistantEventHandler()) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def poll( self, run_id: str, @@ -2326,8 +2542,8 @@ async def poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + poll_interval_ms: int | Omit = omit, ) -> Run: """ A helper to poll a run status until it reaches a terminal state. More @@ -2341,7 +2557,7 @@ async def poll( terminal_states = {"requires_action", "cancelled", "completed", "failed", "expired", "incomplete"} while True: - response = await self.with_raw_response.retrieve( + response = await self.with_raw_response.retrieve( # pyright: ignore[reportDeprecated] thread_id=thread_id, run_id=run_id, extra_headers=extra_headers, @@ -2365,24 +2581,26 @@ async def poll( await self._sleep(poll_interval_ms / 1000) @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def stream( self, *, assistant_id: str, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -2395,25 +2613,27 @@ def stream( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def stream( self, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, event_handler: AsyncAssistantEventHandlerT, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -2426,25 +2646,27 @@ def stream( """Create a Run stream""" ... + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def stream( self, *, assistant_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, - additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | NotGiven = NOT_GIVEN, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[AssistantToolParam]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[run_create_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, + additional_instructions: Optional[str] | Omit = omit, + additional_messages: Optional[Iterable[run_create_params.AdditionalMessage]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[run_create_params.TruncationStrategy] | Omit = omit, thread_id: str, event_handler: AsyncAssistantEventHandlerT | None = None, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -2468,11 +2690,10 @@ def stream( **(extra_headers or {}), } request = self._post( - f"/threads/{thread_id}/runs", + path_template("/threads/{thread_id}/runs", thread_id=thread_id), body=maybe_transform( { "assistant_id": assistant_id, - "include": include, "additional_instructions": additional_instructions, "additional_messages": additional_messages, "instructions": instructions, @@ -2486,13 +2707,19 @@ def stream( "stream": True, "tools": tools, "parallel_tool_calls": parallel_tool_calls, + "reasoning_effort": reasoning_effort, "truncation_strategy": truncation_strategy, "top_p": top_p, }, run_create_params.RunCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"include": include}, run_create_params.RunCreateParams), + security={"bearer_auth": True}, ), cast_to=Run, stream=True, @@ -2501,19 +2728,20 @@ def stream( return AsyncAssistantStreamManager(request, event_handler=event_handler or AsyncAssistantEventHandler()) @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def submit_tool_outputs( self, run_id: str, *, thread_id: str, tool_outputs: Iterable[run_submit_tool_outputs_params.ToolOutput], - stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN, + stream: Optional[Literal[False]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -2539,6 +2767,7 @@ async def submit_tool_outputs( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def submit_tool_outputs( self, run_id: str, @@ -2551,7 +2780,7 @@ async def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncStream[AssistantStreamEvent]: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -2577,6 +2806,7 @@ async def submit_tool_outputs( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def submit_tool_outputs( self, run_id: str, @@ -2589,7 +2819,7 @@ async def submit_tool_outputs( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: """ When a run has the `status: "requires_action"` and `required_action.type` is @@ -2614,20 +2844,22 @@ async def submit_tool_outputs( """ ... + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") @required_args(["thread_id", "tool_outputs"], ["thread_id", "stream", "tool_outputs"]) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def submit_tool_outputs( self, run_id: str, *, thread_id: str, tool_outputs: Iterable[run_submit_tool_outputs_params.ToolOutput], - stream: Optional[Literal[False]] | Literal[True] | NotGiven = NOT_GIVEN, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: if not thread_id: raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") @@ -2635,29 +2867,37 @@ async def submit_tool_outputs( raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/threads/{thread_id}/runs/{run_id}/submit_tool_outputs", + path_template("/threads/{thread_id}/runs/{run_id}/submit_tool_outputs", thread_id=thread_id, run_id=run_id), body=await async_maybe_transform( { "tool_outputs": tool_outputs, "stream": stream, }, - run_submit_tool_outputs_params.RunSubmitToolOutputsParams, + run_submit_tool_outputs_params.RunSubmitToolOutputsParamsStreaming + if stream + else run_submit_tool_outputs_params.RunSubmitToolOutputsParamsNonStreaming, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + synthesize_event_and_data=True, ), cast_to=Run, stream=stream or False, stream_cls=AsyncStream[AssistantStreamEvent], ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def submit_tool_outputs_and_poll( self, *, tool_outputs: Iterable[run_submit_tool_outputs_params.ToolOutput], run_id: str, thread_id: str, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + poll_interval_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -2670,7 +2910,7 @@ async def submit_tool_outputs_and_poll( More information on Run lifecycles can be found here: https://platform.openai.com/docs/assistants/how-it-works/runs-and-run-steps """ - run = await self.submit_tool_outputs( + run = await self.submit_tool_outputs( # pyright: ignore[reportDeprecated] run_id=run_id, thread_id=thread_id, tool_outputs=tool_outputs, @@ -2680,7 +2920,7 @@ async def submit_tool_outputs_and_poll( extra_body=extra_body, timeout=timeout, ) - return await self.poll( + return await self.poll( # pyright: ignore[reportDeprecated] run_id=run.id, thread_id=thread_id, extra_headers=extra_headers, @@ -2691,6 +2931,7 @@ async def submit_tool_outputs_and_poll( ) @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs_stream( self, *, @@ -2712,6 +2953,7 @@ def submit_tool_outputs_stream( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs_stream( self, *, @@ -2733,6 +2975,7 @@ def submit_tool_outputs_stream( """ ... + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def submit_tool_outputs_stream( self, *, @@ -2768,7 +3011,7 @@ def submit_tool_outputs_stream( **(extra_headers or {}), } request = self._post( - f"/threads/{thread_id}/runs/{run_id}/submit_tool_outputs", + path_template("/threads/{thread_id}/runs/{run_id}/submit_tool_outputs", thread_id=thread_id, run_id=run_id), body=maybe_transform( { "tool_outputs": tool_outputs, @@ -2777,7 +3020,11 @@ def submit_tool_outputs_stream( run_submit_tool_outputs_params.RunSubmitToolOutputsParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, stream=True, @@ -2790,27 +3037,40 @@ class RunsWithRawResponse: def __init__(self, runs: Runs) -> None: self._runs = runs - self.create = _legacy_response.to_raw_response_wrapper( - runs.create, + self.create = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + runs.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = _legacy_response.to_raw_response_wrapper( - runs.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + runs.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = _legacy_response.to_raw_response_wrapper( - runs.update, + self.update = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + runs.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = _legacy_response.to_raw_response_wrapper( - runs.list, + self.list = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + runs.list, # pyright: ignore[reportDeprecated], + ) ) - self.cancel = _legacy_response.to_raw_response_wrapper( - runs.cancel, + self.cancel = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + runs.cancel, # pyright: ignore[reportDeprecated], + ) ) - self.submit_tool_outputs = _legacy_response.to_raw_response_wrapper( - runs.submit_tool_outputs, + self.submit_tool_outputs = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + runs.submit_tool_outputs, # pyright: ignore[reportDeprecated], + ) ) @cached_property def steps(self) -> StepsWithRawResponse: + """Build Assistants that can call models and use tools.""" return StepsWithRawResponse(self._runs.steps) @@ -2818,27 +3078,40 @@ class AsyncRunsWithRawResponse: def __init__(self, runs: AsyncRuns) -> None: self._runs = runs - self.create = _legacy_response.async_to_raw_response_wrapper( - runs.create, + self.create = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + runs.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = _legacy_response.async_to_raw_response_wrapper( - runs.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + runs.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = _legacy_response.async_to_raw_response_wrapper( - runs.update, + self.update = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + runs.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = _legacy_response.async_to_raw_response_wrapper( - runs.list, + self.list = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + runs.list, # pyright: ignore[reportDeprecated], + ) ) - self.cancel = _legacy_response.async_to_raw_response_wrapper( - runs.cancel, + self.cancel = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + runs.cancel, # pyright: ignore[reportDeprecated], + ) ) - self.submit_tool_outputs = _legacy_response.async_to_raw_response_wrapper( - runs.submit_tool_outputs, + self.submit_tool_outputs = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + runs.submit_tool_outputs, # pyright: ignore[reportDeprecated], + ) ) @cached_property def steps(self) -> AsyncStepsWithRawResponse: + """Build Assistants that can call models and use tools.""" return AsyncStepsWithRawResponse(self._runs.steps) @@ -2846,27 +3119,40 @@ class RunsWithStreamingResponse: def __init__(self, runs: Runs) -> None: self._runs = runs - self.create = to_streamed_response_wrapper( - runs.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + runs.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = to_streamed_response_wrapper( - runs.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + runs.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = to_streamed_response_wrapper( - runs.update, + self.update = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + runs.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = to_streamed_response_wrapper( - runs.list, + self.list = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + runs.list, # pyright: ignore[reportDeprecated], + ) ) - self.cancel = to_streamed_response_wrapper( - runs.cancel, + self.cancel = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + runs.cancel, # pyright: ignore[reportDeprecated], + ) ) - self.submit_tool_outputs = to_streamed_response_wrapper( - runs.submit_tool_outputs, + self.submit_tool_outputs = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + runs.submit_tool_outputs, # pyright: ignore[reportDeprecated], + ) ) @cached_property def steps(self) -> StepsWithStreamingResponse: + """Build Assistants that can call models and use tools.""" return StepsWithStreamingResponse(self._runs.steps) @@ -2874,25 +3160,38 @@ class AsyncRunsWithStreamingResponse: def __init__(self, runs: AsyncRuns) -> None: self._runs = runs - self.create = async_to_streamed_response_wrapper( - runs.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + runs.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = async_to_streamed_response_wrapper( - runs.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + runs.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = async_to_streamed_response_wrapper( - runs.update, + self.update = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + runs.update, # pyright: ignore[reportDeprecated], + ) ) - self.list = async_to_streamed_response_wrapper( - runs.list, + self.list = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + runs.list, # pyright: ignore[reportDeprecated], + ) ) - self.cancel = async_to_streamed_response_wrapper( - runs.cancel, + self.cancel = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + runs.cancel, # pyright: ignore[reportDeprecated], + ) ) - self.submit_tool_outputs = async_to_streamed_response_wrapper( - runs.submit_tool_outputs, + self.submit_tool_outputs = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + runs.submit_tool_outputs, # pyright: ignore[reportDeprecated], + ) ) @cached_property def steps(self) -> AsyncStepsWithStreamingResponse: + """Build Assistants that can call models and use tools.""" return AsyncStepsWithStreamingResponse(self._runs.steps) diff --git a/src/openai/resources/beta/threads/runs/steps.py b/src/openai/resources/beta/threads/runs/steps.py index 5d6d55f9d9..a251d27bf5 100644 --- a/src/openai/resources/beta/threads/runs/steps.py +++ b/src/openai/resources/beta/threads/runs/steps.py @@ -2,17 +2,15 @@ from __future__ import annotations +import typing_extensions from typing import List from typing_extensions import Literal import httpx from ..... import _legacy_response -from ....._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ....._utils import ( - maybe_transform, - async_maybe_transform, -) +from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ....._utils import path_template, maybe_transform, async_maybe_transform from ....._compat import cached_property from ....._resource import SyncAPIResource, AsyncAPIResource from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -26,10 +24,12 @@ class Steps(SyncAPIResource): + """Build Assistants that can call models and use tools.""" + @cached_property def with_raw_response(self) -> StepsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -45,19 +45,20 @@ def with_streaming_response(self) -> StepsWithStreamingResponse: """ return StepsWithStreamingResponse(self) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def retrieve( self, step_id: str, *, thread_id: str, run_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> RunStep: """ Retrieves a run step. @@ -68,7 +69,7 @@ def retrieve( to fetch the file search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. extra_headers: Send extra headers @@ -87,33 +88,40 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `step_id` but received {step_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get( - f"/threads/{thread_id}/runs/{run_id}/steps/{step_id}", + path_template( + "/threads/{thread_id}/runs/{run_id}/steps/{step_id}", + thread_id=thread_id, + run_id=run_id, + step_id=step_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, query=maybe_transform({"include": include}, step_retrieve_params.StepRetrieveParams), + security={"bearer_auth": True}, ), cast_to=RunStep, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def list( self, run_id: str, *, thread_id: str, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + include: List[RunStepInclude] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[RunStep]: """ Returns a list of run steps belonging to a run. @@ -126,15 +134,15 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. include: A list of additional fields to include in the response. Currently the only supported value is `step_details.tool_calls[*].file_search.results[*].content` to fetch the file search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. limit: A limit on the number of objects to be returned. Limit can range between 1 and @@ -157,7 +165,7 @@ def list( raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get_api_list( - f"/threads/{thread_id}/runs/{run_id}/steps", + path_template("/threads/{thread_id}/runs/{run_id}/steps", thread_id=thread_id, run_id=run_id), page=SyncCursorPage[RunStep], options=make_request_options( extra_headers=extra_headers, @@ -174,16 +182,19 @@ def list( }, step_list_params.StepListParams, ), + security={"bearer_auth": True}, ), model=RunStep, ) class AsyncSteps(AsyncAPIResource): + """Build Assistants that can call models and use tools.""" + @cached_property def with_raw_response(self) -> AsyncStepsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -199,19 +210,20 @@ def with_streaming_response(self) -> AsyncStepsWithStreamingResponse: """ return AsyncStepsWithStreamingResponse(self) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def retrieve( self, step_id: str, *, thread_id: str, run_id: str, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, + include: List[RunStepInclude] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> RunStep: """ Retrieves a run step. @@ -222,7 +234,7 @@ async def retrieve( to fetch the file search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. extra_headers: Send extra headers @@ -241,33 +253,40 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `step_id` but received {step_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._get( - f"/threads/{thread_id}/runs/{run_id}/steps/{step_id}", + path_template( + "/threads/{thread_id}/runs/{run_id}/steps/{step_id}", + thread_id=thread_id, + run_id=run_id, + step_id=step_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, query=await async_maybe_transform({"include": include}, step_retrieve_params.StepRetrieveParams), + security={"bearer_auth": True}, ), cast_to=RunStep, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def list( self, run_id: str, *, thread_id: str, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - include: List[RunStepInclude] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + include: List[RunStepInclude] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[RunStep, AsyncCursorPage[RunStep]]: """ Returns a list of run steps belonging to a run. @@ -280,15 +299,15 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. include: A list of additional fields to include in the response. Currently the only supported value is `step_details.tool_calls[*].file_search.results[*].content` to fetch the file search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. limit: A limit on the number of objects to be returned. Limit can range between 1 and @@ -311,7 +330,7 @@ def list( raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get_api_list( - f"/threads/{thread_id}/runs/{run_id}/steps", + path_template("/threads/{thread_id}/runs/{run_id}/steps", thread_id=thread_id, run_id=run_id), page=AsyncCursorPage[RunStep], options=make_request_options( extra_headers=extra_headers, @@ -328,6 +347,7 @@ def list( }, step_list_params.StepListParams, ), + security={"bearer_auth": True}, ), model=RunStep, ) @@ -337,11 +357,15 @@ class StepsWithRawResponse: def __init__(self, steps: Steps) -> None: self._steps = steps - self.retrieve = _legacy_response.to_raw_response_wrapper( - steps.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + steps.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = _legacy_response.to_raw_response_wrapper( - steps.list, + self.list = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + steps.list, # pyright: ignore[reportDeprecated], + ) ) @@ -349,11 +373,15 @@ class AsyncStepsWithRawResponse: def __init__(self, steps: AsyncSteps) -> None: self._steps = steps - self.retrieve = _legacy_response.async_to_raw_response_wrapper( - steps.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + steps.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = _legacy_response.async_to_raw_response_wrapper( - steps.list, + self.list = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + steps.list, # pyright: ignore[reportDeprecated], + ) ) @@ -361,11 +389,15 @@ class StepsWithStreamingResponse: def __init__(self, steps: Steps) -> None: self._steps = steps - self.retrieve = to_streamed_response_wrapper( - steps.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + steps.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = to_streamed_response_wrapper( - steps.list, + self.list = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + steps.list, # pyright: ignore[reportDeprecated], + ) ) @@ -373,9 +405,13 @@ class AsyncStepsWithStreamingResponse: def __init__(self, steps: AsyncSteps) -> None: self._steps = steps - self.retrieve = async_to_streamed_response_wrapper( - steps.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + steps.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = async_to_streamed_response_wrapper( - steps.list, + self.list = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + steps.list, # pyright: ignore[reportDeprecated], + ) ) diff --git a/src/openai/resources/beta/threads/threads.py b/src/openai/resources/beta/threads/threads.py index 49b0e4b37e..d9425c64bd 100644 --- a/src/openai/resources/beta/threads/threads.py +++ b/src/openai/resources/beta/threads/threads.py @@ -2,21 +2,14 @@ from __future__ import annotations -from typing import Union, Iterable, Optional, overload +import typing_extensions +from typing import Union, Iterable, Optional from functools import partial -from typing_extensions import Literal +from typing_extensions import Literal, overload import httpx from .... import _legacy_response -from .runs import ( - Runs, - AsyncRuns, - RunsWithRawResponse, - AsyncRunsWithRawResponse, - RunsWithStreamingResponse, - AsyncRunsWithStreamingResponse, -) from .messages import ( Messages, AsyncMessages, @@ -25,13 +18,16 @@ MessagesWithStreamingResponse, AsyncMessagesWithStreamingResponse, ) -from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ...._utils import ( - required_args, - maybe_transform, - async_maybe_transform, +from ...._types import NOT_GIVEN, Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, required_args, maybe_transform, async_maybe_transform +from .runs.runs import ( + Runs, + AsyncRuns, + RunsWithRawResponse, + AsyncRunsWithRawResponse, + RunsWithStreamingResponse, + AsyncRunsWithStreamingResponse, ) -from .runs.runs import Runs, AsyncRuns from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -50,10 +46,12 @@ AsyncAssistantEventHandlerT, AsyncAssistantStreamManager, ) -from ....types.chat_model import ChatModel from ....types.beta.thread import Thread from ....types.beta.threads.run import Run +from ....types.shared.chat_model import ChatModel from ....types.beta.thread_deleted import ThreadDeleted +from ....types.shared_params.metadata import Metadata +from ....types.beta.assistant_tool_param import AssistantToolParam from ....types.beta.assistant_stream_event import AssistantStreamEvent from ....types.beta.assistant_tool_choice_option_param import AssistantToolChoiceOptionParam from ....types.beta.assistant_response_format_option_param import AssistantResponseFormatOptionParam @@ -62,18 +60,22 @@ class Threads(SyncAPIResource): + """Build Assistants that can call models and use tools.""" + @cached_property def runs(self) -> Runs: + """Build Assistants that can call models and use tools.""" return Runs(self._client) @cached_property def messages(self) -> Messages: + """Build Assistants that can call models and use tools.""" return Messages(self._client) @cached_property def with_raw_response(self) -> ThreadsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -89,18 +91,19 @@ def with_streaming_response(self) -> ThreadsWithStreamingResponse: """ return ThreadsWithStreamingResponse(self) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def create( self, *, - messages: Iterable[thread_create_params.Message] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_params.ToolResources] | NotGiven = NOT_GIVEN, + messages: Iterable[thread_create_params.Message] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + tool_resources: Optional[thread_create_params.ToolResources] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Thread: """ Create a thread. @@ -110,9 +113,11 @@ def create( start the thread with. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. tool_resources: A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the @@ -139,11 +144,16 @@ def create( thread_create_params.ThreadCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Thread, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def retrieve( self, thread_id: str, @@ -153,7 +163,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Thread: """ Retrieves a thread. @@ -171,34 +181,41 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get( - f"/threads/{thread_id}", + path_template("/threads/{thread_id}", thread_id=thread_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Thread, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def update( self, thread_id: str, *, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_update_params.ToolResources] | NotGiven = NOT_GIVEN, + metadata: Optional[Metadata] | Omit = omit, + tool_resources: Optional[thread_update_params.ToolResources] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Thread: """ Modifies a thread. Args: metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. tool_resources: A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the @@ -217,7 +234,7 @@ def update( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/threads/{thread_id}", + path_template("/threads/{thread_id}", thread_id=thread_id), body=maybe_transform( { "metadata": metadata, @@ -226,11 +243,16 @@ def update( thread_update_params.ThreadUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Thread, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def delete( self, thread_id: str, @@ -240,7 +262,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ThreadDeleted: """ Delete a thread. @@ -258,39 +280,44 @@ def delete( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._delete( - f"/threads/{thread_id}", + path_template("/threads/{thread_id}", thread_id=thread_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ThreadDeleted, ) @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def create_and_run( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ Create a thread and run it in one request. @@ -316,9 +343,11 @@ def create_and_run( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -326,12 +355,12 @@ def create_and_run( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -358,7 +387,8 @@ def create_and_run( make the output more random, while lower values like 0.2 will make it more focused and deterministic. - thread: If no thread is provided, an empty thread will be created. + thread: Options to create a new thread. If no thread is provided when running a request, + an empty thread will be created. tool_choice: Controls which (if any) tool is called by the model. `none` means the model will not call any tools and instead generates a message. `auto` is the default value @@ -383,7 +413,7 @@ def create_and_run( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -396,31 +426,32 @@ def create_and_run( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def create_and_run( self, *, assistant_id: str, stream: Literal[True], - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Stream[AssistantStreamEvent]: """ Create a thread and run it in one request. @@ -450,9 +481,11 @@ def create_and_run( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -460,12 +493,12 @@ def create_and_run( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -488,7 +521,8 @@ def create_and_run( make the output more random, while lower values like 0.2 will make it more focused and deterministic. - thread: If no thread is provided, an empty thread will be created. + thread: Options to create a new thread. If no thread is provided when running a request, + an empty thread will be created. tool_choice: Controls which (if any) tool is called by the model. `none` means the model will not call any tools and instead generates a message. `auto` is the default value @@ -513,7 +547,7 @@ def create_and_run( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -526,31 +560,32 @@ def create_and_run( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def create_and_run( self, *, assistant_id: str, stream: bool, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: """ Create a thread and run it in one request. @@ -580,9 +615,11 @@ def create_and_run( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -590,12 +627,12 @@ def create_and_run( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -618,7 +655,8 @@ def create_and_run( make the output more random, while lower values like 0.2 will make it more focused and deterministic. - thread: If no thread is provided, an empty thread will be created. + thread: Options to create a new thread. If no thread is provided when running a request, + an empty thread will be created. tool_choice: Controls which (if any) tool is called by the model. `none` means the model will not call any tools and instead generates a message. `auto` is the default value @@ -643,7 +681,7 @@ def create_and_run( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -655,32 +693,34 @@ def create_and_run( """ ... + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") @required_args(["assistant_id"], ["assistant_id", "stream"]) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") def create_and_run( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | Literal[True] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | Stream[AssistantStreamEvent]: extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( @@ -704,10 +744,17 @@ def create_and_run( "top_p": top_p, "truncation_strategy": truncation_strategy, }, - thread_create_and_run_params.ThreadCreateAndRunParams, + thread_create_and_run_params.ThreadCreateAndRunParamsStreaming + if stream + else thread_create_and_run_params.ThreadCreateAndRunParamsNonStreaming, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + synthesize_event_and_data=True, ), cast_to=Run, stream=stream or False, @@ -718,21 +765,21 @@ def create_and_run_poll( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, + poll_interval_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -745,7 +792,7 @@ def create_and_run_poll( More information on Run lifecycles can be found here: https://platform.openai.com/docs/assistants/how-it-works/runs-and-run-steps """ - run = self.create_and_run( + run = self.create_and_run( # pyright: ignore[reportDeprecated] assistant_id=assistant_id, instructions=instructions, max_completion_tokens=max_completion_tokens, @@ -767,27 +814,27 @@ def create_and_run_poll( extra_body=extra_body, timeout=timeout, ) - return self.runs.poll(run.id, run.thread_id, extra_headers, extra_query, extra_body, timeout, poll_interval_ms) + return self.runs.poll(run.id, run.thread_id, extra_headers, extra_query, extra_body, timeout, poll_interval_ms) # pyright: ignore[reportDeprecated] @overload def create_and_run_stream( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -803,20 +850,20 @@ def create_and_run_stream( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, event_handler: AssistantEventHandlerT, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -832,20 +879,20 @@ def create_and_run_stream( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, event_handler: AssistantEventHandlerT | None = None, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -886,7 +933,11 @@ def create_and_run_stream( thread_create_and_run_params.ThreadCreateAndRunParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, stream=True, @@ -896,18 +947,22 @@ def create_and_run_stream( class AsyncThreads(AsyncAPIResource): + """Build Assistants that can call models and use tools.""" + @cached_property def runs(self) -> AsyncRuns: + """Build Assistants that can call models and use tools.""" return AsyncRuns(self._client) @cached_property def messages(self) -> AsyncMessages: + """Build Assistants that can call models and use tools.""" return AsyncMessages(self._client) @cached_property def with_raw_response(self) -> AsyncThreadsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -923,18 +978,19 @@ def with_streaming_response(self) -> AsyncThreadsWithStreamingResponse: """ return AsyncThreadsWithStreamingResponse(self) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create( self, *, - messages: Iterable[thread_create_params.Message] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_params.ToolResources] | NotGiven = NOT_GIVEN, + messages: Iterable[thread_create_params.Message] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + tool_resources: Optional[thread_create_params.ToolResources] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Thread: """ Create a thread. @@ -944,9 +1000,11 @@ async def create( start the thread with. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. tool_resources: A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the @@ -973,11 +1031,16 @@ async def create( thread_create_params.ThreadCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Thread, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def retrieve( self, thread_id: str, @@ -987,7 +1050,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Thread: """ Retrieves a thread. @@ -1005,34 +1068,41 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._get( - f"/threads/{thread_id}", + path_template("/threads/{thread_id}", thread_id=thread_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Thread, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def update( self, thread_id: str, *, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_update_params.ToolResources] | NotGiven = NOT_GIVEN, + metadata: Optional[Metadata] | Omit = omit, + tool_resources: Optional[thread_update_params.ToolResources] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Thread: """ Modifies a thread. Args: metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. tool_resources: A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the @@ -1051,7 +1121,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/threads/{thread_id}", + path_template("/threads/{thread_id}", thread_id=thread_id), body=await async_maybe_transform( { "metadata": metadata, @@ -1060,11 +1130,16 @@ async def update( thread_update_params.ThreadUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Thread, ) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def delete( self, thread_id: str, @@ -1074,7 +1149,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ThreadDeleted: """ Delete a thread. @@ -1092,39 +1167,44 @@ async def delete( raise ValueError(f"Expected a non-empty value for `thread_id` but received {thread_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._delete( - f"/threads/{thread_id}", + path_template("/threads/{thread_id}", thread_id=thread_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ThreadDeleted, ) @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create_and_run( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run: """ Create a thread and run it in one request. @@ -1150,9 +1230,11 @@ async def create_and_run( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -1160,12 +1242,12 @@ async def create_and_run( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -1192,7 +1274,8 @@ async def create_and_run( make the output more random, while lower values like 0.2 will make it more focused and deterministic. - thread: If no thread is provided, an empty thread will be created. + thread: Options to create a new thread. If no thread is provided when running a request, + an empty thread will be created. tool_choice: Controls which (if any) tool is called by the model. `none` means the model will not call any tools and instead generates a message. `auto` is the default value @@ -1217,7 +1300,7 @@ async def create_and_run( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -1230,31 +1313,32 @@ async def create_and_run( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create_and_run( self, *, assistant_id: str, stream: Literal[True], - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncStream[AssistantStreamEvent]: """ Create a thread and run it in one request. @@ -1284,9 +1368,11 @@ async def create_and_run( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -1294,12 +1380,12 @@ async def create_and_run( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -1322,7 +1408,8 @@ async def create_and_run( make the output more random, while lower values like 0.2 will make it more focused and deterministic. - thread: If no thread is provided, an empty thread will be created. + thread: Options to create a new thread. If no thread is provided when running a request, + an empty thread will be created. tool_choice: Controls which (if any) tool is called by the model. `none` means the model will not call any tools and instead generates a message. `auto` is the default value @@ -1347,7 +1434,7 @@ async def create_and_run( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -1360,31 +1447,32 @@ async def create_and_run( ... @overload + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create_and_run( self, *, assistant_id: str, stream: bool, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: """ Create a thread and run it in one request. @@ -1414,9 +1502,11 @@ async def create_and_run( `incomplete_details` for more info. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. model: The ID of the [Model](https://platform.openai.com/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the @@ -1424,12 +1514,12 @@ async def create_and_run( assistant will be used. parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. response_format: Specifies the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -1452,7 +1542,8 @@ async def create_and_run( make the output more random, while lower values like 0.2 will make it more focused and deterministic. - thread: If no thread is provided, an empty thread will be created. + thread: Options to create a new thread. If no thread is provided when running a request, + an empty thread will be created. tool_choice: Controls which (if any) tool is called by the model. `none` means the model will not call any tools and instead generates a message. `auto` is the default value @@ -1477,7 +1568,7 @@ async def create_and_run( We generally recommend altering this or temperature but not both. truncation_strategy: Controls for how a thread will be truncated prior to the run. Use this to - control the intial context window of the run. + control the initial context window of the run. extra_headers: Send extra headers @@ -1489,32 +1580,34 @@ async def create_and_run( """ ... + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") @required_args(["assistant_id"], ["assistant_id", "stream"]) + @typing_extensions.deprecated("The Assistants API is deprecated in favor of the Responses API") async def create_and_run( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | Literal[True] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Run | AsyncStream[AssistantStreamEvent]: extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( @@ -1538,10 +1631,17 @@ async def create_and_run( "top_p": top_p, "truncation_strategy": truncation_strategy, }, - thread_create_and_run_params.ThreadCreateAndRunParams, + thread_create_and_run_params.ThreadCreateAndRunParamsStreaming + if stream + else thread_create_and_run_params.ThreadCreateAndRunParamsNonStreaming, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + synthesize_event_and_data=True, ), cast_to=Run, stream=stream or False, @@ -1552,21 +1652,21 @@ async def create_and_run_poll( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, + poll_interval_ms: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -1579,7 +1679,7 @@ async def create_and_run_poll( More information on Run lifecycles can be found here: https://platform.openai.com/docs/assistants/how-it-works/runs-and-run-steps """ - run = await self.create_and_run( + run = await self.create_and_run( # pyright: ignore[reportDeprecated] assistant_id=assistant_id, instructions=instructions, max_completion_tokens=max_completion_tokens, @@ -1601,7 +1701,7 @@ async def create_and_run_poll( extra_body=extra_body, timeout=timeout, ) - return await self.runs.poll( + return await self.runs.poll( # pyright: ignore[reportDeprecated] run.id, run.thread_id, extra_headers, extra_query, extra_body, timeout, poll_interval_ms ) @@ -1610,20 +1710,20 @@ def create_and_run_stream( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -1639,20 +1739,20 @@ def create_and_run_stream( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, event_handler: AsyncAssistantEventHandlerT, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1668,20 +1768,20 @@ def create_and_run_stream( self, *, assistant_id: str, - instructions: Optional[str] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_prompt_tokens: Optional[int] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - model: Union[str, ChatModel, None] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - response_format: Optional[AssistantResponseFormatOptionParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - thread: thread_create_and_run_params.Thread | NotGiven = NOT_GIVEN, - tool_choice: Optional[AssistantToolChoiceOptionParam] | NotGiven = NOT_GIVEN, - tool_resources: Optional[thread_create_and_run_params.ToolResources] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[thread_create_and_run_params.Tool]] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | NotGiven = NOT_GIVEN, + instructions: Optional[str] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_prompt_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: Union[str, ChatModel, None] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + response_format: Optional[AssistantResponseFormatOptionParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + thread: thread_create_and_run_params.Thread | Omit = omit, + tool_choice: Optional[AssistantToolChoiceOptionParam] | Omit = omit, + tool_resources: Optional[thread_create_and_run_params.ToolResources] | Omit = omit, + tools: Optional[Iterable[AssistantToolParam]] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation_strategy: Optional[thread_create_and_run_params.TruncationStrategy] | Omit = omit, event_handler: AsyncAssistantEventHandlerT | None = None, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1724,7 +1824,11 @@ def create_and_run_stream( thread_create_and_run_params.ThreadCreateAndRunParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Run, stream=True, @@ -1737,28 +1841,40 @@ class ThreadsWithRawResponse: def __init__(self, threads: Threads) -> None: self._threads = threads - self.create = _legacy_response.to_raw_response_wrapper( - threads.create, + self.create = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + threads.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = _legacy_response.to_raw_response_wrapper( - threads.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + threads.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = _legacy_response.to_raw_response_wrapper( - threads.update, + self.update = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + threads.update, # pyright: ignore[reportDeprecated], + ) ) - self.delete = _legacy_response.to_raw_response_wrapper( - threads.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + threads.delete, # pyright: ignore[reportDeprecated], + ) ) - self.create_and_run = _legacy_response.to_raw_response_wrapper( - threads.create_and_run, + self.create_and_run = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + threads.create_and_run, # pyright: ignore[reportDeprecated], + ) ) @cached_property def runs(self) -> RunsWithRawResponse: + """Build Assistants that can call models and use tools.""" return RunsWithRawResponse(self._threads.runs) @cached_property def messages(self) -> MessagesWithRawResponse: + """Build Assistants that can call models and use tools.""" return MessagesWithRawResponse(self._threads.messages) @@ -1766,28 +1882,40 @@ class AsyncThreadsWithRawResponse: def __init__(self, threads: AsyncThreads) -> None: self._threads = threads - self.create = _legacy_response.async_to_raw_response_wrapper( - threads.create, + self.create = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + threads.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = _legacy_response.async_to_raw_response_wrapper( - threads.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + threads.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = _legacy_response.async_to_raw_response_wrapper( - threads.update, + self.update = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + threads.update, # pyright: ignore[reportDeprecated], + ) ) - self.delete = _legacy_response.async_to_raw_response_wrapper( - threads.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + threads.delete, # pyright: ignore[reportDeprecated], + ) ) - self.create_and_run = _legacy_response.async_to_raw_response_wrapper( - threads.create_and_run, + self.create_and_run = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + threads.create_and_run, # pyright: ignore[reportDeprecated], + ) ) @cached_property def runs(self) -> AsyncRunsWithRawResponse: + """Build Assistants that can call models and use tools.""" return AsyncRunsWithRawResponse(self._threads.runs) @cached_property def messages(self) -> AsyncMessagesWithRawResponse: + """Build Assistants that can call models and use tools.""" return AsyncMessagesWithRawResponse(self._threads.messages) @@ -1795,28 +1923,40 @@ class ThreadsWithStreamingResponse: def __init__(self, threads: Threads) -> None: self._threads = threads - self.create = to_streamed_response_wrapper( - threads.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + threads.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = to_streamed_response_wrapper( - threads.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + threads.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = to_streamed_response_wrapper( - threads.update, + self.update = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + threads.update, # pyright: ignore[reportDeprecated], + ) ) - self.delete = to_streamed_response_wrapper( - threads.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + threads.delete, # pyright: ignore[reportDeprecated], + ) ) - self.create_and_run = to_streamed_response_wrapper( - threads.create_and_run, + self.create_and_run = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + threads.create_and_run, # pyright: ignore[reportDeprecated], + ) ) @cached_property def runs(self) -> RunsWithStreamingResponse: + """Build Assistants that can call models and use tools.""" return RunsWithStreamingResponse(self._threads.runs) @cached_property def messages(self) -> MessagesWithStreamingResponse: + """Build Assistants that can call models and use tools.""" return MessagesWithStreamingResponse(self._threads.messages) @@ -1824,26 +1964,38 @@ class AsyncThreadsWithStreamingResponse: def __init__(self, threads: AsyncThreads) -> None: self._threads = threads - self.create = async_to_streamed_response_wrapper( - threads.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + threads.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = async_to_streamed_response_wrapper( - threads.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + threads.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.update = async_to_streamed_response_wrapper( - threads.update, + self.update = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + threads.update, # pyright: ignore[reportDeprecated], + ) ) - self.delete = async_to_streamed_response_wrapper( - threads.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + threads.delete, # pyright: ignore[reportDeprecated], + ) ) - self.create_and_run = async_to_streamed_response_wrapper( - threads.create_and_run, + self.create_and_run = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + threads.create_and_run, # pyright: ignore[reportDeprecated], + ) ) @cached_property def runs(self) -> AsyncRunsWithStreamingResponse: + """Build Assistants that can call models and use tools.""" return AsyncRunsWithStreamingResponse(self._threads.runs) @cached_property def messages(self) -> AsyncMessagesWithStreamingResponse: + """Build Assistants that can call models and use tools.""" return AsyncMessagesWithStreamingResponse(self._threads.messages) diff --git a/src/openai/resources/chat/chat.py b/src/openai/resources/chat/chat.py index dc23a15a8e..2c921e7480 100644 --- a/src/openai/resources/chat/chat.py +++ b/src/openai/resources/chat/chat.py @@ -4,7 +4,7 @@ from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from .completions import ( +from .completions.completions import ( Completions, AsyncCompletions, CompletionsWithRawResponse, @@ -19,12 +19,15 @@ class Chat(SyncAPIResource): @cached_property def completions(self) -> Completions: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ return Completions(self._client) @cached_property def with_raw_response(self) -> ChatWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -44,12 +47,15 @@ def with_streaming_response(self) -> ChatWithStreamingResponse: class AsyncChat(AsyncAPIResource): @cached_property def completions(self) -> AsyncCompletions: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ return AsyncCompletions(self._client) @cached_property def with_raw_response(self) -> AsyncChatWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -72,6 +78,9 @@ def __init__(self, chat: Chat) -> None: @cached_property def completions(self) -> CompletionsWithRawResponse: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ return CompletionsWithRawResponse(self._chat.completions) @@ -81,6 +90,9 @@ def __init__(self, chat: AsyncChat) -> None: @cached_property def completions(self) -> AsyncCompletionsWithRawResponse: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ return AsyncCompletionsWithRawResponse(self._chat.completions) @@ -90,6 +102,9 @@ def __init__(self, chat: Chat) -> None: @cached_property def completions(self) -> CompletionsWithStreamingResponse: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ return CompletionsWithStreamingResponse(self._chat.completions) @@ -99,4 +114,7 @@ def __init__(self, chat: AsyncChat) -> None: @cached_property def completions(self) -> AsyncCompletionsWithStreamingResponse: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ return AsyncCompletionsWithStreamingResponse(self._chat.completions) diff --git a/src/openai/resources/chat/completions.py b/src/openai/resources/chat/completions.py deleted file mode 100644 index e9267b1f03..0000000000 --- a/src/openai/resources/chat/completions.py +++ /dev/null @@ -1,1492 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import inspect -from typing import Dict, List, Union, Iterable, Optional, overload -from typing_extensions import Literal - -import httpx -import pydantic - -from ... import _legacy_response -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - required_args, - maybe_transform, - async_maybe_transform, -) -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ..._streaming import Stream, AsyncStream -from ...types.chat import completion_create_params -from ..._base_client import make_request_options -from ...types.chat_model import ChatModel -from ...types.chat.chat_completion import ChatCompletion -from ...types.chat.chat_completion_chunk import ChatCompletionChunk -from ...types.chat.chat_completion_tool_param import ChatCompletionToolParam -from ...types.chat.chat_completion_message_param import ChatCompletionMessageParam -from ...types.chat.chat_completion_stream_options_param import ChatCompletionStreamOptionsParam -from ...types.chat.chat_completion_tool_choice_option_param import ChatCompletionToolChoiceOptionParam - -__all__ = ["Completions", "AsyncCompletions"] - - -class Completions(SyncAPIResource): - @cached_property - def with_raw_response(self) -> CompletionsWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers - """ - return CompletionsWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> CompletionsWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/openai/openai-python#with_streaming_response - """ - return CompletionsWithStreamingResponse(self) - - @overload - def create( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - response_format: completion_create_params.ResponseFormat | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ChatCompletion: - """ - Creates a model response for the given chat conversation. - - Args: - messages: A list of messages comprising the conversation so far. - [Example Python code](https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models). - - model: ID of the model to use. See the - [model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility) - table for details on which models work with the Chat API. - - frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their - existing frequency in the text so far, decreasing the model's likelihood to - repeat the same line verbatim. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - function_call: Deprecated in favor of `tool_choice`. - - Controls which (if any) function is called by the model. `none` means the model - will not call a function and instead generates a message. `auto` means the model - can pick between generating a message or calling a function. Specifying a - particular function via `{"name": "my_function"}` forces the model to call that - function. - - `none` is the default when no functions are present. `auto` is the default if - functions are present. - - functions: Deprecated in favor of `tools`. - - A list of functions the model may generate JSON inputs for. - - logit_bias: Modify the likelihood of specified tokens appearing in the completion. - - Accepts a JSON object that maps tokens (specified by their token ID in the - tokenizer) to an associated bias value from -100 to 100. Mathematically, the - bias is added to the logits generated by the model prior to sampling. The exact - effect will vary per model, but values between -1 and 1 should decrease or - increase likelihood of selection; values like -100 or 100 should result in a ban - or exclusive selection of the relevant token. - - logprobs: Whether to return log probabilities of the output tokens or not. If true, - returns the log probabilities of each output token returned in the `content` of - `message`. - - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output tokens and - [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). - - max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat - completion. This value can be used to control - [costs](https://openai.com/api/pricing/) for text generated via API. - - This value is now deprecated in favor of `max_completion_tokens`, and is not - compatible with - [o1 series models](https://platform.openai.com/docs/guides/reasoning). - - n: How many chat completion choices to generate for each input message. Note that - you will be charged based on the number of generated tokens across all of the - choices. Keep `n` as `1` to minimize costs. - - parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) - during tool use. - - presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on - whether they appear in the text so far, increasing the model's likelihood to - talk about new topics. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - response_format: An object specifying the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4o mini](https://platform.openai.com/docs/models/gpt-4o-mini), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) and - all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. - - Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured - Outputs which ensures the model will match your supplied JSON schema. Learn more - in the - [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). - - Setting to `{ "type": "json_object" }` enables JSON mode, which ensures the - message the model generates is valid JSON. - - **Important:** when using JSON mode, you **must** also instruct the model to - produce JSON yourself via a system or user message. Without this, the model may - generate an unending stream of whitespace until the generation reaches the token - limit, resulting in a long-running and seemingly "stuck" request. Also note that - the message content may be partially cut off if `finish_reason="length"`, which - indicates the generation exceeded `max_tokens` or the conversation exceeded the - max context length. - - seed: This feature is in Beta. If specified, our system will make a best effort to - sample deterministically, such that repeated requests with the same `seed` and - parameters should return the same result. Determinism is not guaranteed, and you - should refer to the `system_fingerprint` response parameter to monitor changes - in the backend. - - service_tier: Specifies the latency tier to use for processing the request. This parameter is - relevant for customers subscribed to the scale tier service: - - - If set to 'auto', and the Project is Scale tier enabled, the system will - utilize scale tier credits until they are exhausted. - - If set to 'auto', and the Project is not Scale tier enabled, the request will - be processed using the default service tier with a lower uptime SLA and no - latency guarentee. - - If set to 'default', the request will be processed using the default service - tier with a lower uptime SLA and no latency guarentee. - - When not set, the default behavior is 'auto'. - - When this parameter is set, the response body will include the `service_tier` - utilized. - - stop: Up to 4 sequences where the API will stop generating further tokens. - - stream: If set, partial message deltas will be sent, like in ChatGPT. Tokens will be - sent as data-only - [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available, with the stream terminated by a `data: [DONE]` - message. - [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). - - stream_options: Options for streaming response. Only set this when you set `stream: true`. - - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will - make the output more random, while lower values like 0.2 will make it more - focused and deterministic. - - We generally recommend altering this or `top_p` but not both. - - tool_choice: Controls which (if any) tool is called by the model. `none` means the model will - not call any tool and instead generates a message. `auto` means the model can - pick between generating a message or calling one or more tools. `required` means - the model must call one or more tools. Specifying a particular tool via - `{"type": "function", "function": {"name": "my_function"}}` forces the model to - call that tool. - - `none` is the default when no tools are present. `auto` is the default if tools - are present. - - tools: A list of tools the model may call. Currently, only functions are supported as a - tool. Use this to provide a list of functions the model may generate JSON inputs - for. A max of 128 functions are supported. - - top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to - return at each token position, each with an associated log probability. - `logprobs` must be set to `true` if this parameter is used. - - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. - - We generally recommend altering this or `temperature` but not both. - - user: A unique identifier representing your end-user, which can help OpenAI to monitor - and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - ... - - @overload - def create( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - stream: Literal[True], - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - response_format: completion_create_params.ResponseFormat | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Stream[ChatCompletionChunk]: - """ - Creates a model response for the given chat conversation. - - Args: - messages: A list of messages comprising the conversation so far. - [Example Python code](https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models). - - model: ID of the model to use. See the - [model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility) - table for details on which models work with the Chat API. - - stream: If set, partial message deltas will be sent, like in ChatGPT. Tokens will be - sent as data-only - [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available, with the stream terminated by a `data: [DONE]` - message. - [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). - - frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their - existing frequency in the text so far, decreasing the model's likelihood to - repeat the same line verbatim. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - function_call: Deprecated in favor of `tool_choice`. - - Controls which (if any) function is called by the model. `none` means the model - will not call a function and instead generates a message. `auto` means the model - can pick between generating a message or calling a function. Specifying a - particular function via `{"name": "my_function"}` forces the model to call that - function. - - `none` is the default when no functions are present. `auto` is the default if - functions are present. - - functions: Deprecated in favor of `tools`. - - A list of functions the model may generate JSON inputs for. - - logit_bias: Modify the likelihood of specified tokens appearing in the completion. - - Accepts a JSON object that maps tokens (specified by their token ID in the - tokenizer) to an associated bias value from -100 to 100. Mathematically, the - bias is added to the logits generated by the model prior to sampling. The exact - effect will vary per model, but values between -1 and 1 should decrease or - increase likelihood of selection; values like -100 or 100 should result in a ban - or exclusive selection of the relevant token. - - logprobs: Whether to return log probabilities of the output tokens or not. If true, - returns the log probabilities of each output token returned in the `content` of - `message`. - - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output tokens and - [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). - - max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat - completion. This value can be used to control - [costs](https://openai.com/api/pricing/) for text generated via API. - - This value is now deprecated in favor of `max_completion_tokens`, and is not - compatible with - [o1 series models](https://platform.openai.com/docs/guides/reasoning). - - n: How many chat completion choices to generate for each input message. Note that - you will be charged based on the number of generated tokens across all of the - choices. Keep `n` as `1` to minimize costs. - - parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) - during tool use. - - presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on - whether they appear in the text so far, increasing the model's likelihood to - talk about new topics. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - response_format: An object specifying the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4o mini](https://platform.openai.com/docs/models/gpt-4o-mini), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) and - all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. - - Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured - Outputs which ensures the model will match your supplied JSON schema. Learn more - in the - [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). - - Setting to `{ "type": "json_object" }` enables JSON mode, which ensures the - message the model generates is valid JSON. - - **Important:** when using JSON mode, you **must** also instruct the model to - produce JSON yourself via a system or user message. Without this, the model may - generate an unending stream of whitespace until the generation reaches the token - limit, resulting in a long-running and seemingly "stuck" request. Also note that - the message content may be partially cut off if `finish_reason="length"`, which - indicates the generation exceeded `max_tokens` or the conversation exceeded the - max context length. - - seed: This feature is in Beta. If specified, our system will make a best effort to - sample deterministically, such that repeated requests with the same `seed` and - parameters should return the same result. Determinism is not guaranteed, and you - should refer to the `system_fingerprint` response parameter to monitor changes - in the backend. - - service_tier: Specifies the latency tier to use for processing the request. This parameter is - relevant for customers subscribed to the scale tier service: - - - If set to 'auto', and the Project is Scale tier enabled, the system will - utilize scale tier credits until they are exhausted. - - If set to 'auto', and the Project is not Scale tier enabled, the request will - be processed using the default service tier with a lower uptime SLA and no - latency guarentee. - - If set to 'default', the request will be processed using the default service - tier with a lower uptime SLA and no latency guarentee. - - When not set, the default behavior is 'auto'. - - When this parameter is set, the response body will include the `service_tier` - utilized. - - stop: Up to 4 sequences where the API will stop generating further tokens. - - stream_options: Options for streaming response. Only set this when you set `stream: true`. - - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will - make the output more random, while lower values like 0.2 will make it more - focused and deterministic. - - We generally recommend altering this or `top_p` but not both. - - tool_choice: Controls which (if any) tool is called by the model. `none` means the model will - not call any tool and instead generates a message. `auto` means the model can - pick between generating a message or calling one or more tools. `required` means - the model must call one or more tools. Specifying a particular tool via - `{"type": "function", "function": {"name": "my_function"}}` forces the model to - call that tool. - - `none` is the default when no tools are present. `auto` is the default if tools - are present. - - tools: A list of tools the model may call. Currently, only functions are supported as a - tool. Use this to provide a list of functions the model may generate JSON inputs - for. A max of 128 functions are supported. - - top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to - return at each token position, each with an associated log probability. - `logprobs` must be set to `true` if this parameter is used. - - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. - - We generally recommend altering this or `temperature` but not both. - - user: A unique identifier representing your end-user, which can help OpenAI to monitor - and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - ... - - @overload - def create( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - stream: bool, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - response_format: completion_create_params.ResponseFormat | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ChatCompletion | Stream[ChatCompletionChunk]: - """ - Creates a model response for the given chat conversation. - - Args: - messages: A list of messages comprising the conversation so far. - [Example Python code](https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models). - - model: ID of the model to use. See the - [model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility) - table for details on which models work with the Chat API. - - stream: If set, partial message deltas will be sent, like in ChatGPT. Tokens will be - sent as data-only - [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available, with the stream terminated by a `data: [DONE]` - message. - [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). - - frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their - existing frequency in the text so far, decreasing the model's likelihood to - repeat the same line verbatim. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - function_call: Deprecated in favor of `tool_choice`. - - Controls which (if any) function is called by the model. `none` means the model - will not call a function and instead generates a message. `auto` means the model - can pick between generating a message or calling a function. Specifying a - particular function via `{"name": "my_function"}` forces the model to call that - function. - - `none` is the default when no functions are present. `auto` is the default if - functions are present. - - functions: Deprecated in favor of `tools`. - - A list of functions the model may generate JSON inputs for. - - logit_bias: Modify the likelihood of specified tokens appearing in the completion. - - Accepts a JSON object that maps tokens (specified by their token ID in the - tokenizer) to an associated bias value from -100 to 100. Mathematically, the - bias is added to the logits generated by the model prior to sampling. The exact - effect will vary per model, but values between -1 and 1 should decrease or - increase likelihood of selection; values like -100 or 100 should result in a ban - or exclusive selection of the relevant token. - - logprobs: Whether to return log probabilities of the output tokens or not. If true, - returns the log probabilities of each output token returned in the `content` of - `message`. - - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output tokens and - [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). - - max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat - completion. This value can be used to control - [costs](https://openai.com/api/pricing/) for text generated via API. - - This value is now deprecated in favor of `max_completion_tokens`, and is not - compatible with - [o1 series models](https://platform.openai.com/docs/guides/reasoning). - - n: How many chat completion choices to generate for each input message. Note that - you will be charged based on the number of generated tokens across all of the - choices. Keep `n` as `1` to minimize costs. - - parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) - during tool use. - - presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on - whether they appear in the text so far, increasing the model's likelihood to - talk about new topics. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - response_format: An object specifying the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4o mini](https://platform.openai.com/docs/models/gpt-4o-mini), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) and - all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. - - Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured - Outputs which ensures the model will match your supplied JSON schema. Learn more - in the - [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). - - Setting to `{ "type": "json_object" }` enables JSON mode, which ensures the - message the model generates is valid JSON. - - **Important:** when using JSON mode, you **must** also instruct the model to - produce JSON yourself via a system or user message. Without this, the model may - generate an unending stream of whitespace until the generation reaches the token - limit, resulting in a long-running and seemingly "stuck" request. Also note that - the message content may be partially cut off if `finish_reason="length"`, which - indicates the generation exceeded `max_tokens` or the conversation exceeded the - max context length. - - seed: This feature is in Beta. If specified, our system will make a best effort to - sample deterministically, such that repeated requests with the same `seed` and - parameters should return the same result. Determinism is not guaranteed, and you - should refer to the `system_fingerprint` response parameter to monitor changes - in the backend. - - service_tier: Specifies the latency tier to use for processing the request. This parameter is - relevant for customers subscribed to the scale tier service: - - - If set to 'auto', and the Project is Scale tier enabled, the system will - utilize scale tier credits until they are exhausted. - - If set to 'auto', and the Project is not Scale tier enabled, the request will - be processed using the default service tier with a lower uptime SLA and no - latency guarentee. - - If set to 'default', the request will be processed using the default service - tier with a lower uptime SLA and no latency guarentee. - - When not set, the default behavior is 'auto'. - - When this parameter is set, the response body will include the `service_tier` - utilized. - - stop: Up to 4 sequences where the API will stop generating further tokens. - - stream_options: Options for streaming response. Only set this when you set `stream: true`. - - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will - make the output more random, while lower values like 0.2 will make it more - focused and deterministic. - - We generally recommend altering this or `top_p` but not both. - - tool_choice: Controls which (if any) tool is called by the model. `none` means the model will - not call any tool and instead generates a message. `auto` means the model can - pick between generating a message or calling one or more tools. `required` means - the model must call one or more tools. Specifying a particular tool via - `{"type": "function", "function": {"name": "my_function"}}` forces the model to - call that tool. - - `none` is the default when no tools are present. `auto` is the default if tools - are present. - - tools: A list of tools the model may call. Currently, only functions are supported as a - tool. Use this to provide a list of functions the model may generate JSON inputs - for. A max of 128 functions are supported. - - top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to - return at each token position, each with an associated log probability. - `logprobs` must be set to `true` if this parameter is used. - - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. - - We generally recommend altering this or `temperature` but not both. - - user: A unique identifier representing your end-user, which can help OpenAI to monitor - and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - ... - - @required_args(["messages", "model"], ["messages", "model", "stream"]) - def create( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - response_format: completion_create_params.ResponseFormat | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | Literal[True] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ChatCompletion | Stream[ChatCompletionChunk]: - validate_response_format(response_format) - return self._post( - "/chat/completions", - body=maybe_transform( - { - "messages": messages, - "model": model, - "frequency_penalty": frequency_penalty, - "function_call": function_call, - "functions": functions, - "logit_bias": logit_bias, - "logprobs": logprobs, - "max_completion_tokens": max_completion_tokens, - "max_tokens": max_tokens, - "n": n, - "parallel_tool_calls": parallel_tool_calls, - "presence_penalty": presence_penalty, - "response_format": response_format, - "seed": seed, - "service_tier": service_tier, - "stop": stop, - "stream": stream, - "stream_options": stream_options, - "temperature": temperature, - "tool_choice": tool_choice, - "tools": tools, - "top_logprobs": top_logprobs, - "top_p": top_p, - "user": user, - }, - completion_create_params.CompletionCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ChatCompletion, - stream=stream or False, - stream_cls=Stream[ChatCompletionChunk], - ) - - -class AsyncCompletions(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncCompletionsWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers - """ - return AsyncCompletionsWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncCompletionsWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/openai/openai-python#with_streaming_response - """ - return AsyncCompletionsWithStreamingResponse(self) - - @overload - async def create( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - response_format: completion_create_params.ResponseFormat | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ChatCompletion: - """ - Creates a model response for the given chat conversation. - - Args: - messages: A list of messages comprising the conversation so far. - [Example Python code](https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models). - - model: ID of the model to use. See the - [model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility) - table for details on which models work with the Chat API. - - frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their - existing frequency in the text so far, decreasing the model's likelihood to - repeat the same line verbatim. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - function_call: Deprecated in favor of `tool_choice`. - - Controls which (if any) function is called by the model. `none` means the model - will not call a function and instead generates a message. `auto` means the model - can pick between generating a message or calling a function. Specifying a - particular function via `{"name": "my_function"}` forces the model to call that - function. - - `none` is the default when no functions are present. `auto` is the default if - functions are present. - - functions: Deprecated in favor of `tools`. - - A list of functions the model may generate JSON inputs for. - - logit_bias: Modify the likelihood of specified tokens appearing in the completion. - - Accepts a JSON object that maps tokens (specified by their token ID in the - tokenizer) to an associated bias value from -100 to 100. Mathematically, the - bias is added to the logits generated by the model prior to sampling. The exact - effect will vary per model, but values between -1 and 1 should decrease or - increase likelihood of selection; values like -100 or 100 should result in a ban - or exclusive selection of the relevant token. - - logprobs: Whether to return log probabilities of the output tokens or not. If true, - returns the log probabilities of each output token returned in the `content` of - `message`. - - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output tokens and - [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). - - max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat - completion. This value can be used to control - [costs](https://openai.com/api/pricing/) for text generated via API. - - This value is now deprecated in favor of `max_completion_tokens`, and is not - compatible with - [o1 series models](https://platform.openai.com/docs/guides/reasoning). - - n: How many chat completion choices to generate for each input message. Note that - you will be charged based on the number of generated tokens across all of the - choices. Keep `n` as `1` to minimize costs. - - parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) - during tool use. - - presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on - whether they appear in the text so far, increasing the model's likelihood to - talk about new topics. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - response_format: An object specifying the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4o mini](https://platform.openai.com/docs/models/gpt-4o-mini), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) and - all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. - - Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured - Outputs which ensures the model will match your supplied JSON schema. Learn more - in the - [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). - - Setting to `{ "type": "json_object" }` enables JSON mode, which ensures the - message the model generates is valid JSON. - - **Important:** when using JSON mode, you **must** also instruct the model to - produce JSON yourself via a system or user message. Without this, the model may - generate an unending stream of whitespace until the generation reaches the token - limit, resulting in a long-running and seemingly "stuck" request. Also note that - the message content may be partially cut off if `finish_reason="length"`, which - indicates the generation exceeded `max_tokens` or the conversation exceeded the - max context length. - - seed: This feature is in Beta. If specified, our system will make a best effort to - sample deterministically, such that repeated requests with the same `seed` and - parameters should return the same result. Determinism is not guaranteed, and you - should refer to the `system_fingerprint` response parameter to monitor changes - in the backend. - - service_tier: Specifies the latency tier to use for processing the request. This parameter is - relevant for customers subscribed to the scale tier service: - - - If set to 'auto', and the Project is Scale tier enabled, the system will - utilize scale tier credits until they are exhausted. - - If set to 'auto', and the Project is not Scale tier enabled, the request will - be processed using the default service tier with a lower uptime SLA and no - latency guarentee. - - If set to 'default', the request will be processed using the default service - tier with a lower uptime SLA and no latency guarentee. - - When not set, the default behavior is 'auto'. - - When this parameter is set, the response body will include the `service_tier` - utilized. - - stop: Up to 4 sequences where the API will stop generating further tokens. - - stream: If set, partial message deltas will be sent, like in ChatGPT. Tokens will be - sent as data-only - [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available, with the stream terminated by a `data: [DONE]` - message. - [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). - - stream_options: Options for streaming response. Only set this when you set `stream: true`. - - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will - make the output more random, while lower values like 0.2 will make it more - focused and deterministic. - - We generally recommend altering this or `top_p` but not both. - - tool_choice: Controls which (if any) tool is called by the model. `none` means the model will - not call any tool and instead generates a message. `auto` means the model can - pick between generating a message or calling one or more tools. `required` means - the model must call one or more tools. Specifying a particular tool via - `{"type": "function", "function": {"name": "my_function"}}` forces the model to - call that tool. - - `none` is the default when no tools are present. `auto` is the default if tools - are present. - - tools: A list of tools the model may call. Currently, only functions are supported as a - tool. Use this to provide a list of functions the model may generate JSON inputs - for. A max of 128 functions are supported. - - top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to - return at each token position, each with an associated log probability. - `logprobs` must be set to `true` if this parameter is used. - - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. - - We generally recommend altering this or `temperature` but not both. - - user: A unique identifier representing your end-user, which can help OpenAI to monitor - and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - ... - - @overload - async def create( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - stream: Literal[True], - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - response_format: completion_create_params.ResponseFormat | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncStream[ChatCompletionChunk]: - """ - Creates a model response for the given chat conversation. - - Args: - messages: A list of messages comprising the conversation so far. - [Example Python code](https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models). - - model: ID of the model to use. See the - [model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility) - table for details on which models work with the Chat API. - - stream: If set, partial message deltas will be sent, like in ChatGPT. Tokens will be - sent as data-only - [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available, with the stream terminated by a `data: [DONE]` - message. - [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). - - frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their - existing frequency in the text so far, decreasing the model's likelihood to - repeat the same line verbatim. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - function_call: Deprecated in favor of `tool_choice`. - - Controls which (if any) function is called by the model. `none` means the model - will not call a function and instead generates a message. `auto` means the model - can pick between generating a message or calling a function. Specifying a - particular function via `{"name": "my_function"}` forces the model to call that - function. - - `none` is the default when no functions are present. `auto` is the default if - functions are present. - - functions: Deprecated in favor of `tools`. - - A list of functions the model may generate JSON inputs for. - - logit_bias: Modify the likelihood of specified tokens appearing in the completion. - - Accepts a JSON object that maps tokens (specified by their token ID in the - tokenizer) to an associated bias value from -100 to 100. Mathematically, the - bias is added to the logits generated by the model prior to sampling. The exact - effect will vary per model, but values between -1 and 1 should decrease or - increase likelihood of selection; values like -100 or 100 should result in a ban - or exclusive selection of the relevant token. - - logprobs: Whether to return log probabilities of the output tokens or not. If true, - returns the log probabilities of each output token returned in the `content` of - `message`. - - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output tokens and - [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). - - max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat - completion. This value can be used to control - [costs](https://openai.com/api/pricing/) for text generated via API. - - This value is now deprecated in favor of `max_completion_tokens`, and is not - compatible with - [o1 series models](https://platform.openai.com/docs/guides/reasoning). - - n: How many chat completion choices to generate for each input message. Note that - you will be charged based on the number of generated tokens across all of the - choices. Keep `n` as `1` to minimize costs. - - parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) - during tool use. - - presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on - whether they appear in the text so far, increasing the model's likelihood to - talk about new topics. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - response_format: An object specifying the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4o mini](https://platform.openai.com/docs/models/gpt-4o-mini), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) and - all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. - - Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured - Outputs which ensures the model will match your supplied JSON schema. Learn more - in the - [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). - - Setting to `{ "type": "json_object" }` enables JSON mode, which ensures the - message the model generates is valid JSON. - - **Important:** when using JSON mode, you **must** also instruct the model to - produce JSON yourself via a system or user message. Without this, the model may - generate an unending stream of whitespace until the generation reaches the token - limit, resulting in a long-running and seemingly "stuck" request. Also note that - the message content may be partially cut off if `finish_reason="length"`, which - indicates the generation exceeded `max_tokens` or the conversation exceeded the - max context length. - - seed: This feature is in Beta. If specified, our system will make a best effort to - sample deterministically, such that repeated requests with the same `seed` and - parameters should return the same result. Determinism is not guaranteed, and you - should refer to the `system_fingerprint` response parameter to monitor changes - in the backend. - - service_tier: Specifies the latency tier to use for processing the request. This parameter is - relevant for customers subscribed to the scale tier service: - - - If set to 'auto', and the Project is Scale tier enabled, the system will - utilize scale tier credits until they are exhausted. - - If set to 'auto', and the Project is not Scale tier enabled, the request will - be processed using the default service tier with a lower uptime SLA and no - latency guarentee. - - If set to 'default', the request will be processed using the default service - tier with a lower uptime SLA and no latency guarentee. - - When not set, the default behavior is 'auto'. - - When this parameter is set, the response body will include the `service_tier` - utilized. - - stop: Up to 4 sequences where the API will stop generating further tokens. - - stream_options: Options for streaming response. Only set this when you set `stream: true`. - - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will - make the output more random, while lower values like 0.2 will make it more - focused and deterministic. - - We generally recommend altering this or `top_p` but not both. - - tool_choice: Controls which (if any) tool is called by the model. `none` means the model will - not call any tool and instead generates a message. `auto` means the model can - pick between generating a message or calling one or more tools. `required` means - the model must call one or more tools. Specifying a particular tool via - `{"type": "function", "function": {"name": "my_function"}}` forces the model to - call that tool. - - `none` is the default when no tools are present. `auto` is the default if tools - are present. - - tools: A list of tools the model may call. Currently, only functions are supported as a - tool. Use this to provide a list of functions the model may generate JSON inputs - for. A max of 128 functions are supported. - - top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to - return at each token position, each with an associated log probability. - `logprobs` must be set to `true` if this parameter is used. - - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. - - We generally recommend altering this or `temperature` but not both. - - user: A unique identifier representing your end-user, which can help OpenAI to monitor - and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - ... - - @overload - async def create( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - stream: bool, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - response_format: completion_create_params.ResponseFormat | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ChatCompletion | AsyncStream[ChatCompletionChunk]: - """ - Creates a model response for the given chat conversation. - - Args: - messages: A list of messages comprising the conversation so far. - [Example Python code](https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models). - - model: ID of the model to use. See the - [model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility) - table for details on which models work with the Chat API. - - stream: If set, partial message deltas will be sent, like in ChatGPT. Tokens will be - sent as data-only - [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available, with the stream terminated by a `data: [DONE]` - message. - [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). - - frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their - existing frequency in the text so far, decreasing the model's likelihood to - repeat the same line verbatim. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - function_call: Deprecated in favor of `tool_choice`. - - Controls which (if any) function is called by the model. `none` means the model - will not call a function and instead generates a message. `auto` means the model - can pick between generating a message or calling a function. Specifying a - particular function via `{"name": "my_function"}` forces the model to call that - function. - - `none` is the default when no functions are present. `auto` is the default if - functions are present. - - functions: Deprecated in favor of `tools`. - - A list of functions the model may generate JSON inputs for. - - logit_bias: Modify the likelihood of specified tokens appearing in the completion. - - Accepts a JSON object that maps tokens (specified by their token ID in the - tokenizer) to an associated bias value from -100 to 100. Mathematically, the - bias is added to the logits generated by the model prior to sampling. The exact - effect will vary per model, but values between -1 and 1 should decrease or - increase likelihood of selection; values like -100 or 100 should result in a ban - or exclusive selection of the relevant token. - - logprobs: Whether to return log probabilities of the output tokens or not. If true, - returns the log probabilities of each output token returned in the `content` of - `message`. - - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output tokens and - [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). - - max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat - completion. This value can be used to control - [costs](https://openai.com/api/pricing/) for text generated via API. - - This value is now deprecated in favor of `max_completion_tokens`, and is not - compatible with - [o1 series models](https://platform.openai.com/docs/guides/reasoning). - - n: How many chat completion choices to generate for each input message. Note that - you will be charged based on the number of generated tokens across all of the - choices. Keep `n` as `1` to minimize costs. - - parallel_tool_calls: Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) - during tool use. - - presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on - whether they appear in the text so far, increasing the model's likelihood to - talk about new topics. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) - - response_format: An object specifying the format that the model must output. Compatible with - [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4o mini](https://platform.openai.com/docs/models/gpt-4o-mini), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) and - all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. - - Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured - Outputs which ensures the model will match your supplied JSON schema. Learn more - in the - [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). - - Setting to `{ "type": "json_object" }` enables JSON mode, which ensures the - message the model generates is valid JSON. - - **Important:** when using JSON mode, you **must** also instruct the model to - produce JSON yourself via a system or user message. Without this, the model may - generate an unending stream of whitespace until the generation reaches the token - limit, resulting in a long-running and seemingly "stuck" request. Also note that - the message content may be partially cut off if `finish_reason="length"`, which - indicates the generation exceeded `max_tokens` or the conversation exceeded the - max context length. - - seed: This feature is in Beta. If specified, our system will make a best effort to - sample deterministically, such that repeated requests with the same `seed` and - parameters should return the same result. Determinism is not guaranteed, and you - should refer to the `system_fingerprint` response parameter to monitor changes - in the backend. - - service_tier: Specifies the latency tier to use for processing the request. This parameter is - relevant for customers subscribed to the scale tier service: - - - If set to 'auto', and the Project is Scale tier enabled, the system will - utilize scale tier credits until they are exhausted. - - If set to 'auto', and the Project is not Scale tier enabled, the request will - be processed using the default service tier with a lower uptime SLA and no - latency guarentee. - - If set to 'default', the request will be processed using the default service - tier with a lower uptime SLA and no latency guarentee. - - When not set, the default behavior is 'auto'. - - When this parameter is set, the response body will include the `service_tier` - utilized. - - stop: Up to 4 sequences where the API will stop generating further tokens. - - stream_options: Options for streaming response. Only set this when you set `stream: true`. - - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will - make the output more random, while lower values like 0.2 will make it more - focused and deterministic. - - We generally recommend altering this or `top_p` but not both. - - tool_choice: Controls which (if any) tool is called by the model. `none` means the model will - not call any tool and instead generates a message. `auto` means the model can - pick between generating a message or calling one or more tools. `required` means - the model must call one or more tools. Specifying a particular tool via - `{"type": "function", "function": {"name": "my_function"}}` forces the model to - call that tool. - - `none` is the default when no tools are present. `auto` is the default if tools - are present. - - tools: A list of tools the model may call. Currently, only functions are supported as a - tool. Use this to provide a list of functions the model may generate JSON inputs - for. A max of 128 functions are supported. - - top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to - return at each token position, each with an associated log probability. - `logprobs` must be set to `true` if this parameter is used. - - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. - - We generally recommend altering this or `temperature` but not both. - - user: A unique identifier representing your end-user, which can help OpenAI to monitor - and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - ... - - @required_args(["messages", "model"], ["messages", "model", "stream"]) - async def create( - self, - *, - messages: Iterable[ChatCompletionMessageParam], - model: Union[str, ChatModel], - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - function_call: completion_create_params.FunctionCall | NotGiven = NOT_GIVEN, - functions: Iterable[completion_create_params.Function] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[bool] | NotGiven = NOT_GIVEN, - max_completion_tokens: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - parallel_tool_calls: bool | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - response_format: completion_create_params.ResponseFormat | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - service_tier: Optional[Literal["auto", "default"]] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str]] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | Literal[True] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN, - tools: Iterable[ChatCompletionToolParam] | NotGiven = NOT_GIVEN, - top_logprobs: Optional[int] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ChatCompletion | AsyncStream[ChatCompletionChunk]: - validate_response_format(response_format) - return await self._post( - "/chat/completions", - body=await async_maybe_transform( - { - "messages": messages, - "model": model, - "frequency_penalty": frequency_penalty, - "function_call": function_call, - "functions": functions, - "logit_bias": logit_bias, - "logprobs": logprobs, - "max_completion_tokens": max_completion_tokens, - "max_tokens": max_tokens, - "n": n, - "parallel_tool_calls": parallel_tool_calls, - "presence_penalty": presence_penalty, - "response_format": response_format, - "seed": seed, - "service_tier": service_tier, - "stop": stop, - "stream": stream, - "stream_options": stream_options, - "temperature": temperature, - "tool_choice": tool_choice, - "tools": tools, - "top_logprobs": top_logprobs, - "top_p": top_p, - "user": user, - }, - completion_create_params.CompletionCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ChatCompletion, - stream=stream or False, - stream_cls=AsyncStream[ChatCompletionChunk], - ) - - -class CompletionsWithRawResponse: - def __init__(self, completions: Completions) -> None: - self._completions = completions - - self.create = _legacy_response.to_raw_response_wrapper( - completions.create, - ) - - -class AsyncCompletionsWithRawResponse: - def __init__(self, completions: AsyncCompletions) -> None: - self._completions = completions - - self.create = _legacy_response.async_to_raw_response_wrapper( - completions.create, - ) - - -class CompletionsWithStreamingResponse: - def __init__(self, completions: Completions) -> None: - self._completions = completions - - self.create = to_streamed_response_wrapper( - completions.create, - ) - - -class AsyncCompletionsWithStreamingResponse: - def __init__(self, completions: AsyncCompletions) -> None: - self._completions = completions - - self.create = async_to_streamed_response_wrapper( - completions.create, - ) - - -def validate_response_format(response_format: object) -> None: - if inspect.isclass(response_format) and issubclass(response_format, pydantic.BaseModel): - raise TypeError( - "You tried to pass a `BaseModel` class to `chat.completions.create()`; You must use `beta.chat.completions.parse()` instead" - ) diff --git a/src/openai/resources/chat/completions/__init__.py b/src/openai/resources/chat/completions/__init__.py new file mode 100644 index 0000000000..12d3b3aa28 --- /dev/null +++ b/src/openai/resources/chat/completions/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .messages import ( + Messages, + AsyncMessages, + MessagesWithRawResponse, + AsyncMessagesWithRawResponse, + MessagesWithStreamingResponse, + AsyncMessagesWithStreamingResponse, +) +from .completions import ( + Completions, + AsyncCompletions, + CompletionsWithRawResponse, + AsyncCompletionsWithRawResponse, + CompletionsWithStreamingResponse, + AsyncCompletionsWithStreamingResponse, +) + +__all__ = [ + "Messages", + "AsyncMessages", + "MessagesWithRawResponse", + "AsyncMessagesWithRawResponse", + "MessagesWithStreamingResponse", + "AsyncMessagesWithStreamingResponse", + "Completions", + "AsyncCompletions", + "CompletionsWithRawResponse", + "AsyncCompletionsWithRawResponse", + "CompletionsWithStreamingResponse", + "AsyncCompletionsWithStreamingResponse", +] diff --git a/src/openai/resources/chat/completions/completions.py b/src/openai/resources/chat/completions/completions.py new file mode 100644 index 0000000000..835adfde1c --- /dev/null +++ b/src/openai/resources/chat/completions/completions.py @@ -0,0 +1,3315 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import inspect +from typing import Dict, List, Type, Union, Iterable, Optional, cast +from functools import partial +from typing_extensions import Literal, overload + +import httpx +import pydantic + +from .... import _legacy_response +from .messages import ( + Messages, + AsyncMessages, + MessagesWithRawResponse, + AsyncMessagesWithRawResponse, + MessagesWithStreamingResponse, + AsyncMessagesWithStreamingResponse, +) +from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ...._utils import path_template, required_args, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...._streaming import Stream, AsyncStream +from ....pagination import SyncCursorPage, AsyncCursorPage +from ....types.chat import ( + ChatCompletionAudioParam, + completion_list_params, + completion_create_params, + completion_update_params, +) +from ...._base_client import AsyncPaginator, make_request_options +from ....lib._parsing import ( + ResponseFormatT, + validate_input_tools as _validate_input_tools, + parse_chat_completion as _parse_chat_completion, + type_to_response_format_param as _type_to_response_format, +) +from ....lib.streaming.chat import ChatCompletionStreamManager, AsyncChatCompletionStreamManager +from ....types.shared.chat_model import ChatModel +from ....types.chat.chat_completion import ChatCompletion +from ....types.shared_params.metadata import Metadata +from ....types.shared.reasoning_effort import ReasoningEffort +from ....types.chat.chat_completion_chunk import ChatCompletionChunk +from ....types.chat.parsed_chat_completion import ParsedChatCompletion +from ....types.chat.chat_completion_deleted import ChatCompletionDeleted +from ....types.chat.chat_completion_audio_param import ChatCompletionAudioParam +from ....types.chat.chat_completion_message_param import ChatCompletionMessageParam +from ....types.chat.chat_completion_tool_union_param import ChatCompletionToolUnionParam +from ....types.chat.chat_completion_stream_options_param import ChatCompletionStreamOptionsParam +from ....types.chat.chat_completion_prediction_content_param import ChatCompletionPredictionContentParam +from ....types.chat.chat_completion_tool_choice_option_param import ChatCompletionToolChoiceOptionParam + +__all__ = ["Completions", "AsyncCompletions"] + + +class Completions(SyncAPIResource): + """ + Given a list of messages comprising a conversation, the model will return a response. + """ + + @cached_property + def messages(self) -> Messages: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ + return Messages(self._client) + + @cached_property + def with_raw_response(self) -> CompletionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return CompletionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CompletionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return CompletionsWithStreamingResponse(self) + + def parse( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + response_format: type[ResponseFormatT] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ParsedChatCompletion[ResponseFormatT]: + """Wrapper over the `client.chat.completions.create()` method that provides richer integrations with Python specific types + & returns a `ParsedChatCompletion` object, which is a subclass of the standard `ChatCompletion` class. + + You can pass a pydantic model to this method and it will automatically convert the model + into a JSON schema, send it to the API and parse the response content back into the given model. + + This method will also automatically parse `function` tool calls if: + - You use the `openai.pydantic_function_tool()` helper method + - You mark your tool schema with `"strict": True` + + Example usage: + ```py + from pydantic import BaseModel + from openai import OpenAI + + + class Step(BaseModel): + explanation: str + output: str + + + class MathResponse(BaseModel): + steps: List[Step] + final_answer: str + + + client = OpenAI() + completion = client.chat.completions.parse( + model="gpt-4o-2024-08-06", + messages=[ + {"role": "system", "content": "You are a helpful math tutor."}, + {"role": "user", "content": "solve 8x + 31 = 2"}, + ], + response_format=MathResponse, + ) + + message = completion.choices[0].message + if message.parsed: + print(message.parsed.steps) + print("answer: ", message.parsed.final_answer) + ``` + """ + chat_completion_tools = _validate_input_tools(tools) + + extra_headers = { + "X-Stainless-Helper-Method": "chat.completions.parse", + **(extra_headers or {}), + } + + def parser(raw_completion: ChatCompletion) -> ParsedChatCompletion[ResponseFormatT]: + return _parse_chat_completion( + response_format=response_format, + chat_completion=raw_completion, + input_tools=chat_completion_tools, + ) + + return self._post( + "/chat/completions", + body=maybe_transform( + { + "messages": messages, + "model": model, + "audio": audio, + "frequency_penalty": frequency_penalty, + "function_call": function_call, + "functions": functions, + "logit_bias": logit_bias, + "logprobs": logprobs, + "max_completion_tokens": max_completion_tokens, + "max_tokens": max_tokens, + "metadata": metadata, + "modalities": modalities, + "moderation": moderation, + "n": n, + "parallel_tool_calls": parallel_tool_calls, + "prediction": prediction, + "presence_penalty": presence_penalty, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning_effort": reasoning_effort, + "response_format": _type_to_response_format(response_format), + "safety_identifier": safety_identifier, + "seed": seed, + "service_tier": service_tier, + "stop": stop, + "store": store, + "stream": False, + "stream_options": stream_options, + "temperature": temperature, + "tool_choice": tool_choice, + "tools": tools, + "top_logprobs": top_logprobs, + "top_p": top_p, + "user": user, + "verbosity": verbosity, + "web_search_options": web_search_options, + }, + completion_create_params.CompletionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + post_parser=parser, + security={"bearer_auth": True}, + ), + # we turn the `ChatCompletion` instance into a `ParsedChatCompletion` + # in the `parser` function above + cast_to=cast(Type[ParsedChatCompletion[ResponseFormatT]], ChatCompletion), + stream=False, + ) + + @overload + def create( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: completion_create_params.ResponseFormat | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletion: + """ + **Starting a new project?** We recommend trying + [Responses](https://platform.openai.com/docs/api-reference/responses) to take + advantage of the latest OpenAI platform features. Compare + [Chat Completions with Responses](https://platform.openai.com/docs/guides/responses-vs-chat-completions?api-mode=responses). + + --- + + Creates a model response for the given chat conversation. Learn more in the + [text generation](https://platform.openai.com/docs/guides/text-generation), + [vision](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio) guides. + + Parameter support can differ depending on the model used to generate the + response, particularly for newer reasoning models. Parameters that are only + supported for reasoning models are noted below. For the current state of + unsupported parameters in reasoning models, + [refer to the reasoning guide](https://platform.openai.com/docs/guides/reasoning). + + Returns a chat completion object, or a streamed sequence of chat completion + chunk objects if the request is streamed. + + Args: + messages: A list of messages comprising the conversation so far. Depending on the + [model](https://platform.openai.com/docs/models) you use, different message + types (modalities) are supported, like + [text](https://platform.openai.com/docs/guides/text-generation), + [images](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio). + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). + + frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their + existing frequency in the text so far, decreasing the model's likelihood to + repeat the same line verbatim. + + function_call: Deprecated in favor of `tool_choice`. + + Controls which (if any) function is called by the model. + + `none` means the model will not call a function and instead generates a message. + + `auto` means the model can pick between generating a message or calling a + function. + + Specifying a particular function via `{"name": "my_function"}` forces the model + to call that function. + + `none` is the default when no functions are present. `auto` is the default if + functions are present. + + functions: Deprecated in favor of `tools`. + + A list of functions the model may generate JSON inputs for. + + logit_bias: Modify the likelihood of specified tokens appearing in the completion. + + Accepts a JSON object that maps tokens (specified by their token ID in the + tokenizer) to an associated bias value from -100 to 100. Mathematically, the + bias is added to the logits generated by the model prior to sampling. The exact + effect will vary per model, but values between -1 and 1 should decrease or + increase likelihood of selection; values like -100 or 100 should result in a ban + or exclusive selection of the relevant token. + + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. + + max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat + completion. This value can be used to control + [costs](https://openai.com/api/pricing/) for text generated via API. + + This value is now deprecated in favor of `max_completion_tokens`, and is not + compatible with + [o-series models](https://platform.openai.com/docs/guides/reasoning). + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: + + `["text"]` + + The `gpt-4o-audio-preview` model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + + `["text", "audio"]` + + moderation: Configuration for running moderation on the request input and generated output. + + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. + + parallel_tool_calls: Whether to enable + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) + during tool use. + + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. + + presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on + whether they appear in the text so far, increasing the model's likelihood to + talk about new topics. + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + + response_format: An object specifying the format that the model must output. + + Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured + Outputs which ensures the model will match your supplied JSON schema. Learn more + in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + seed: This feature is in Beta. If specified, our system will make a best effort to + sample deterministically, such that repeated requests with the same `seed` and + parameters should return the same result. Determinism is not guaranteed, and you + should refer to the `system_fingerprint` response parameter to monitor changes + in the backend. + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The + returned text will not contain the stop sequence. + + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. + + Supports text and image inputs. Note: image inputs over 8MB will be dropped. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/chat/streaming) + for more information, along with the + [streaming responses](https://platform.openai.com/docs/guides/streaming-responses) + guide for more information on how to handle the streaming events. + + stream_options: Options for streaming response. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. + + `none` is the default when no tools are present. `auto` is the default if tools + are present. + + tools: A list of tools the model may call. You can provide either + [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + or [function tools](https://platform.openai.com/docs/guides/function-calling). + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + `logprobs` must be set to `true` if this parameter is used. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. + + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def create( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + stream: Literal[True], + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: completion_create_params.ResponseFormat | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Stream[ChatCompletionChunk]: + """ + **Starting a new project?** We recommend trying + [Responses](https://platform.openai.com/docs/api-reference/responses) to take + advantage of the latest OpenAI platform features. Compare + [Chat Completions with Responses](https://platform.openai.com/docs/guides/responses-vs-chat-completions?api-mode=responses). + + --- + + Creates a model response for the given chat conversation. Learn more in the + [text generation](https://platform.openai.com/docs/guides/text-generation), + [vision](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio) guides. + + Parameter support can differ depending on the model used to generate the + response, particularly for newer reasoning models. Parameters that are only + supported for reasoning models are noted below. For the current state of + unsupported parameters in reasoning models, + [refer to the reasoning guide](https://platform.openai.com/docs/guides/reasoning). + + Returns a chat completion object, or a streamed sequence of chat completion + chunk objects if the request is streamed. + + Args: + messages: A list of messages comprising the conversation so far. Depending on the + [model](https://platform.openai.com/docs/models) you use, different message + types (modalities) are supported, like + [text](https://platform.openai.com/docs/guides/text-generation), + [images](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio). + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/chat/streaming) + for more information, along with the + [streaming responses](https://platform.openai.com/docs/guides/streaming-responses) + guide for more information on how to handle the streaming events. + + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). + + frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their + existing frequency in the text so far, decreasing the model's likelihood to + repeat the same line verbatim. + + function_call: Deprecated in favor of `tool_choice`. + + Controls which (if any) function is called by the model. + + `none` means the model will not call a function and instead generates a message. + + `auto` means the model can pick between generating a message or calling a + function. + + Specifying a particular function via `{"name": "my_function"}` forces the model + to call that function. + + `none` is the default when no functions are present. `auto` is the default if + functions are present. + + functions: Deprecated in favor of `tools`. + + A list of functions the model may generate JSON inputs for. + + logit_bias: Modify the likelihood of specified tokens appearing in the completion. + + Accepts a JSON object that maps tokens (specified by their token ID in the + tokenizer) to an associated bias value from -100 to 100. Mathematically, the + bias is added to the logits generated by the model prior to sampling. The exact + effect will vary per model, but values between -1 and 1 should decrease or + increase likelihood of selection; values like -100 or 100 should result in a ban + or exclusive selection of the relevant token. + + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. + + max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat + completion. This value can be used to control + [costs](https://openai.com/api/pricing/) for text generated via API. + + This value is now deprecated in favor of `max_completion_tokens`, and is not + compatible with + [o-series models](https://platform.openai.com/docs/guides/reasoning). + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: + + `["text"]` + + The `gpt-4o-audio-preview` model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + + `["text", "audio"]` + + moderation: Configuration for running moderation on the request input and generated output. + + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. + + parallel_tool_calls: Whether to enable + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) + during tool use. + + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. + + presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on + whether they appear in the text so far, increasing the model's likelihood to + talk about new topics. + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + + response_format: An object specifying the format that the model must output. + + Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured + Outputs which ensures the model will match your supplied JSON schema. Learn more + in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + seed: This feature is in Beta. If specified, our system will make a best effort to + sample deterministically, such that repeated requests with the same `seed` and + parameters should return the same result. Determinism is not guaranteed, and you + should refer to the `system_fingerprint` response parameter to monitor changes + in the backend. + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The + returned text will not contain the stop sequence. + + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. + + Supports text and image inputs. Note: image inputs over 8MB will be dropped. + + stream_options: Options for streaming response. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. + + `none` is the default when no tools are present. `auto` is the default if tools + are present. + + tools: A list of tools the model may call. You can provide either + [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + or [function tools](https://platform.openai.com/docs/guides/function-calling). + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + `logprobs` must be set to `true` if this parameter is used. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. + + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def create( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + stream: bool, + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: completion_create_params.ResponseFormat | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletion | Stream[ChatCompletionChunk]: + """ + **Starting a new project?** We recommend trying + [Responses](https://platform.openai.com/docs/api-reference/responses) to take + advantage of the latest OpenAI platform features. Compare + [Chat Completions with Responses](https://platform.openai.com/docs/guides/responses-vs-chat-completions?api-mode=responses). + + --- + + Creates a model response for the given chat conversation. Learn more in the + [text generation](https://platform.openai.com/docs/guides/text-generation), + [vision](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio) guides. + + Parameter support can differ depending on the model used to generate the + response, particularly for newer reasoning models. Parameters that are only + supported for reasoning models are noted below. For the current state of + unsupported parameters in reasoning models, + [refer to the reasoning guide](https://platform.openai.com/docs/guides/reasoning). + + Returns a chat completion object, or a streamed sequence of chat completion + chunk objects if the request is streamed. + + Args: + messages: A list of messages comprising the conversation so far. Depending on the + [model](https://platform.openai.com/docs/models) you use, different message + types (modalities) are supported, like + [text](https://platform.openai.com/docs/guides/text-generation), + [images](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio). + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/chat/streaming) + for more information, along with the + [streaming responses](https://platform.openai.com/docs/guides/streaming-responses) + guide for more information on how to handle the streaming events. + + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). + + frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their + existing frequency in the text so far, decreasing the model's likelihood to + repeat the same line verbatim. + + function_call: Deprecated in favor of `tool_choice`. + + Controls which (if any) function is called by the model. + + `none` means the model will not call a function and instead generates a message. + + `auto` means the model can pick between generating a message or calling a + function. + + Specifying a particular function via `{"name": "my_function"}` forces the model + to call that function. + + `none` is the default when no functions are present. `auto` is the default if + functions are present. + + functions: Deprecated in favor of `tools`. + + A list of functions the model may generate JSON inputs for. + + logit_bias: Modify the likelihood of specified tokens appearing in the completion. + + Accepts a JSON object that maps tokens (specified by their token ID in the + tokenizer) to an associated bias value from -100 to 100. Mathematically, the + bias is added to the logits generated by the model prior to sampling. The exact + effect will vary per model, but values between -1 and 1 should decrease or + increase likelihood of selection; values like -100 or 100 should result in a ban + or exclusive selection of the relevant token. + + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. + + max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat + completion. This value can be used to control + [costs](https://openai.com/api/pricing/) for text generated via API. + + This value is now deprecated in favor of `max_completion_tokens`, and is not + compatible with + [o-series models](https://platform.openai.com/docs/guides/reasoning). + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: + + `["text"]` + + The `gpt-4o-audio-preview` model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + + `["text", "audio"]` + + moderation: Configuration for running moderation on the request input and generated output. + + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. + + parallel_tool_calls: Whether to enable + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) + during tool use. + + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. + + presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on + whether they appear in the text so far, increasing the model's likelihood to + talk about new topics. + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + + response_format: An object specifying the format that the model must output. + + Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured + Outputs which ensures the model will match your supplied JSON schema. Learn more + in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + seed: This feature is in Beta. If specified, our system will make a best effort to + sample deterministically, such that repeated requests with the same `seed` and + parameters should return the same result. Determinism is not guaranteed, and you + should refer to the `system_fingerprint` response parameter to monitor changes + in the backend. + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The + returned text will not contain the stop sequence. + + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. + + Supports text and image inputs. Note: image inputs over 8MB will be dropped. + + stream_options: Options for streaming response. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. + + `none` is the default when no tools are present. `auto` is the default if tools + are present. + + tools: A list of tools the model may call. You can provide either + [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + or [function tools](https://platform.openai.com/docs/guides/function-calling). + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + `logprobs` must be set to `true` if this parameter is used. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. + + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["messages", "model"], ["messages", "model", "stream"]) + def create( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: completion_create_params.ResponseFormat | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletion | Stream[ChatCompletionChunk]: + validate_response_format(response_format) + return self._post( + "/chat/completions", + body=maybe_transform( + { + "messages": messages, + "model": model, + "audio": audio, + "frequency_penalty": frequency_penalty, + "function_call": function_call, + "functions": functions, + "logit_bias": logit_bias, + "logprobs": logprobs, + "max_completion_tokens": max_completion_tokens, + "max_tokens": max_tokens, + "metadata": metadata, + "modalities": modalities, + "moderation": moderation, + "n": n, + "parallel_tool_calls": parallel_tool_calls, + "prediction": prediction, + "presence_penalty": presence_penalty, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning_effort": reasoning_effort, + "response_format": response_format, + "safety_identifier": safety_identifier, + "seed": seed, + "service_tier": service_tier, + "stop": stop, + "store": store, + "stream": stream, + "stream_options": stream_options, + "temperature": temperature, + "tool_choice": tool_choice, + "tools": tools, + "top_logprobs": top_logprobs, + "top_p": top_p, + "user": user, + "verbosity": verbosity, + "web_search_options": web_search_options, + }, + completion_create_params.CompletionCreateParamsStreaming + if stream + else completion_create_params.CompletionCreateParamsNonStreaming, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatCompletion, + stream=stream or False, + stream_cls=Stream[ChatCompletionChunk], + ) + + def retrieve( + self, + completion_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletion: + """Get a stored chat completion. + + Only Chat Completions that have been created with + the `store` parameter set to `true` will be returned. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not completion_id: + raise ValueError(f"Expected a non-empty value for `completion_id` but received {completion_id!r}") + return self._get( + path_template("/chat/completions/{completion_id}", completion_id=completion_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatCompletion, + ) + + def update( + self, + completion_id: str, + *, + metadata: Optional[Metadata], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletion: + """Modify a stored chat completion. + + Only Chat Completions that have been created + with the `store` parameter set to `true` can be modified. Currently, the only + supported modification is to update the `metadata` field. + + Args: + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not completion_id: + raise ValueError(f"Expected a non-empty value for `completion_id` but received {completion_id!r}") + return self._post( + path_template("/chat/completions/{completion_id}", completion_id=completion_id), + body=maybe_transform({"metadata": metadata}, completion_update_params.CompletionUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatCompletion, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: str | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[ChatCompletion]: + """List stored Chat Completions. + + Only Chat Completions that have been stored with + the `store` parameter set to `true` will be returned. + + Args: + after: Identifier for the last chat completion from the previous pagination request. + + limit: Number of Chat Completions to retrieve. + + metadata: + A list of metadata keys to filter the Chat Completions by. Example: + + `metadata[key1]=value1&metadata[key2]=value2` + + model: The model used to generate the Chat Completions. + + order: Sort order for Chat Completions by timestamp. Use `asc` for ascending order or + `desc` for descending order. Defaults to `asc`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/chat/completions", + page=SyncCursorPage[ChatCompletion], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "metadata": metadata, + "model": model, + "order": order, + }, + completion_list_params.CompletionListParams, + ), + security={"bearer_auth": True}, + ), + model=ChatCompletion, + ) + + def delete( + self, + completion_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletionDeleted: + """Delete a stored chat completion. + + Only Chat Completions that have been created + with the `store` parameter set to `true` can be deleted. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not completion_id: + raise ValueError(f"Expected a non-empty value for `completion_id` but received {completion_id!r}") + return self._delete( + path_template("/chat/completions/{completion_id}", completion_id=completion_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatCompletionDeleted, + ) + + def stream( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + response_format: completion_create_params.ResponseFormat | type[ResponseFormatT] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletionStreamManager[ResponseFormatT]: + """Wrapper over the `client.chat.completions.create(stream=True)` method that provides a more granular event API + and automatic accumulation of each delta. + + This also supports all of the parsing utilities that `.parse()` does. + + Unlike `.create(stream=True)`, the `.stream()` method requires usage within a context manager to prevent accidental leakage of the response: + + ```py + with client.chat.completions.stream( + model="gpt-4o-2024-08-06", + messages=[...], + ) as stream: + for event in stream: + if event.type == "content.delta": + print(event.delta, flush=True, end="") + ``` + + When the context manager is entered, a `ChatCompletionStream` instance is returned which, like `.create(stream=True)` is an iterator. The full list of events that are yielded by the iterator are outlined in [these docs](https://github.com/openai/openai-python/blob/main/helpers.md#chat-completions-events). + + When the context manager exits, the response will be closed, however the `stream` instance is still available outside + the context manager. + """ + extra_headers = { + "X-Stainless-Helper-Method": "chat.completions.stream", + **(extra_headers or {}), + } + + api_request: partial[Stream[ChatCompletionChunk]] = partial( + self.create, + messages=messages, + model=model, + audio=audio, + stream=True, + response_format=_type_to_response_format(response_format), + frequency_penalty=frequency_penalty, + function_call=function_call, + functions=functions, + logit_bias=logit_bias, + logprobs=logprobs, + max_completion_tokens=max_completion_tokens, + max_tokens=max_tokens, + metadata=metadata, + modalities=modalities, + moderation=moderation, + n=n, + parallel_tool_calls=parallel_tool_calls, + prediction=prediction, + presence_penalty=presence_penalty, + prompt_cache_key=prompt_cache_key, + prompt_cache_retention=prompt_cache_retention, + reasoning_effort=reasoning_effort, + safety_identifier=safety_identifier, + seed=seed, + service_tier=service_tier, + store=store, + stop=stop, + stream_options=stream_options, + temperature=temperature, + tool_choice=tool_choice, + tools=tools, + top_logprobs=top_logprobs, + top_p=top_p, + user=user, + verbosity=verbosity, + web_search_options=web_search_options, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return ChatCompletionStreamManager( + api_request, + response_format=response_format, + input_tools=tools, + ) + + +class AsyncCompletions(AsyncAPIResource): + """ + Given a list of messages comprising a conversation, the model will return a response. + """ + + @cached_property + def messages(self) -> AsyncMessages: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ + return AsyncMessages(self._client) + + @cached_property + def with_raw_response(self) -> AsyncCompletionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncCompletionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCompletionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncCompletionsWithStreamingResponse(self) + + async def parse( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + response_format: type[ResponseFormatT] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ParsedChatCompletion[ResponseFormatT]: + """Wrapper over the `client.chat.completions.create()` method that provides richer integrations with Python specific types + & returns a `ParsedChatCompletion` object, which is a subclass of the standard `ChatCompletion` class. + + You can pass a pydantic model to this method and it will automatically convert the model + into a JSON schema, send it to the API and parse the response content back into the given model. + + This method will also automatically parse `function` tool calls if: + - You use the `openai.pydantic_function_tool()` helper method + - You mark your tool schema with `"strict": True` + + Example usage: + ```py + from pydantic import BaseModel + from openai import AsyncOpenAI + + + class Step(BaseModel): + explanation: str + output: str + + + class MathResponse(BaseModel): + steps: List[Step] + final_answer: str + + + client = AsyncOpenAI() + completion = await client.chat.completions.parse( + model="gpt-4o-2024-08-06", + messages=[ + {"role": "system", "content": "You are a helpful math tutor."}, + {"role": "user", "content": "solve 8x + 31 = 2"}, + ], + response_format=MathResponse, + ) + + message = completion.choices[0].message + if message.parsed: + print(message.parsed.steps) + print("answer: ", message.parsed.final_answer) + ``` + """ + _validate_input_tools(tools) + + extra_headers = { + "X-Stainless-Helper-Method": "chat.completions.parse", + **(extra_headers or {}), + } + + def parser(raw_completion: ChatCompletion) -> ParsedChatCompletion[ResponseFormatT]: + return _parse_chat_completion( + response_format=response_format, + chat_completion=raw_completion, + input_tools=tools, + ) + + return await self._post( + "/chat/completions", + body=await async_maybe_transform( + { + "messages": messages, + "model": model, + "audio": audio, + "frequency_penalty": frequency_penalty, + "function_call": function_call, + "functions": functions, + "logit_bias": logit_bias, + "logprobs": logprobs, + "max_completion_tokens": max_completion_tokens, + "max_tokens": max_tokens, + "metadata": metadata, + "modalities": modalities, + "moderation": moderation, + "n": n, + "parallel_tool_calls": parallel_tool_calls, + "prediction": prediction, + "presence_penalty": presence_penalty, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning_effort": reasoning_effort, + "response_format": _type_to_response_format(response_format), + "safety_identifier": safety_identifier, + "seed": seed, + "service_tier": service_tier, + "store": store, + "stop": stop, + "stream": False, + "stream_options": stream_options, + "temperature": temperature, + "tool_choice": tool_choice, + "tools": tools, + "top_logprobs": top_logprobs, + "top_p": top_p, + "user": user, + "verbosity": verbosity, + "web_search_options": web_search_options, + }, + completion_create_params.CompletionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + post_parser=parser, + security={"bearer_auth": True}, + ), + # we turn the `ChatCompletion` instance into a `ParsedChatCompletion` + # in the `parser` function above + cast_to=cast(Type[ParsedChatCompletion[ResponseFormatT]], ChatCompletion), + stream=False, + ) + + @overload + async def create( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: completion_create_params.ResponseFormat | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletion: + """ + **Starting a new project?** We recommend trying + [Responses](https://platform.openai.com/docs/api-reference/responses) to take + advantage of the latest OpenAI platform features. Compare + [Chat Completions with Responses](https://platform.openai.com/docs/guides/responses-vs-chat-completions?api-mode=responses). + + --- + + Creates a model response for the given chat conversation. Learn more in the + [text generation](https://platform.openai.com/docs/guides/text-generation), + [vision](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio) guides. + + Parameter support can differ depending on the model used to generate the + response, particularly for newer reasoning models. Parameters that are only + supported for reasoning models are noted below. For the current state of + unsupported parameters in reasoning models, + [refer to the reasoning guide](https://platform.openai.com/docs/guides/reasoning). + + Returns a chat completion object, or a streamed sequence of chat completion + chunk objects if the request is streamed. + + Args: + messages: A list of messages comprising the conversation so far. Depending on the + [model](https://platform.openai.com/docs/models) you use, different message + types (modalities) are supported, like + [text](https://platform.openai.com/docs/guides/text-generation), + [images](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio). + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). + + frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their + existing frequency in the text so far, decreasing the model's likelihood to + repeat the same line verbatim. + + function_call: Deprecated in favor of `tool_choice`. + + Controls which (if any) function is called by the model. + + `none` means the model will not call a function and instead generates a message. + + `auto` means the model can pick between generating a message or calling a + function. + + Specifying a particular function via `{"name": "my_function"}` forces the model + to call that function. + + `none` is the default when no functions are present. `auto` is the default if + functions are present. + + functions: Deprecated in favor of `tools`. + + A list of functions the model may generate JSON inputs for. + + logit_bias: Modify the likelihood of specified tokens appearing in the completion. + + Accepts a JSON object that maps tokens (specified by their token ID in the + tokenizer) to an associated bias value from -100 to 100. Mathematically, the + bias is added to the logits generated by the model prior to sampling. The exact + effect will vary per model, but values between -1 and 1 should decrease or + increase likelihood of selection; values like -100 or 100 should result in a ban + or exclusive selection of the relevant token. + + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. + + max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat + completion. This value can be used to control + [costs](https://openai.com/api/pricing/) for text generated via API. + + This value is now deprecated in favor of `max_completion_tokens`, and is not + compatible with + [o-series models](https://platform.openai.com/docs/guides/reasoning). + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: + + `["text"]` + + The `gpt-4o-audio-preview` model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + + `["text", "audio"]` + + moderation: Configuration for running moderation on the request input and generated output. + + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. + + parallel_tool_calls: Whether to enable + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) + during tool use. + + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. + + presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on + whether they appear in the text so far, increasing the model's likelihood to + talk about new topics. + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + + response_format: An object specifying the format that the model must output. + + Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured + Outputs which ensures the model will match your supplied JSON schema. Learn more + in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + seed: This feature is in Beta. If specified, our system will make a best effort to + sample deterministically, such that repeated requests with the same `seed` and + parameters should return the same result. Determinism is not guaranteed, and you + should refer to the `system_fingerprint` response parameter to monitor changes + in the backend. + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The + returned text will not contain the stop sequence. + + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. + + Supports text and image inputs. Note: image inputs over 8MB will be dropped. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/chat/streaming) + for more information, along with the + [streaming responses](https://platform.openai.com/docs/guides/streaming-responses) + guide for more information on how to handle the streaming events. + + stream_options: Options for streaming response. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. + + `none` is the default when no tools are present. `auto` is the default if tools + are present. + + tools: A list of tools the model may call. You can provide either + [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + or [function tools](https://platform.openai.com/docs/guides/function-calling). + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + `logprobs` must be set to `true` if this parameter is used. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. + + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def create( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + stream: Literal[True], + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: completion_create_params.ResponseFormat | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncStream[ChatCompletionChunk]: + """ + **Starting a new project?** We recommend trying + [Responses](https://platform.openai.com/docs/api-reference/responses) to take + advantage of the latest OpenAI platform features. Compare + [Chat Completions with Responses](https://platform.openai.com/docs/guides/responses-vs-chat-completions?api-mode=responses). + + --- + + Creates a model response for the given chat conversation. Learn more in the + [text generation](https://platform.openai.com/docs/guides/text-generation), + [vision](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio) guides. + + Parameter support can differ depending on the model used to generate the + response, particularly for newer reasoning models. Parameters that are only + supported for reasoning models are noted below. For the current state of + unsupported parameters in reasoning models, + [refer to the reasoning guide](https://platform.openai.com/docs/guides/reasoning). + + Returns a chat completion object, or a streamed sequence of chat completion + chunk objects if the request is streamed. + + Args: + messages: A list of messages comprising the conversation so far. Depending on the + [model](https://platform.openai.com/docs/models) you use, different message + types (modalities) are supported, like + [text](https://platform.openai.com/docs/guides/text-generation), + [images](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio). + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/chat/streaming) + for more information, along with the + [streaming responses](https://platform.openai.com/docs/guides/streaming-responses) + guide for more information on how to handle the streaming events. + + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). + + frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their + existing frequency in the text so far, decreasing the model's likelihood to + repeat the same line verbatim. + + function_call: Deprecated in favor of `tool_choice`. + + Controls which (if any) function is called by the model. + + `none` means the model will not call a function and instead generates a message. + + `auto` means the model can pick between generating a message or calling a + function. + + Specifying a particular function via `{"name": "my_function"}` forces the model + to call that function. + + `none` is the default when no functions are present. `auto` is the default if + functions are present. + + functions: Deprecated in favor of `tools`. + + A list of functions the model may generate JSON inputs for. + + logit_bias: Modify the likelihood of specified tokens appearing in the completion. + + Accepts a JSON object that maps tokens (specified by their token ID in the + tokenizer) to an associated bias value from -100 to 100. Mathematically, the + bias is added to the logits generated by the model prior to sampling. The exact + effect will vary per model, but values between -1 and 1 should decrease or + increase likelihood of selection; values like -100 or 100 should result in a ban + or exclusive selection of the relevant token. + + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. + + max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat + completion. This value can be used to control + [costs](https://openai.com/api/pricing/) for text generated via API. + + This value is now deprecated in favor of `max_completion_tokens`, and is not + compatible with + [o-series models](https://platform.openai.com/docs/guides/reasoning). + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: + + `["text"]` + + The `gpt-4o-audio-preview` model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + + `["text", "audio"]` + + moderation: Configuration for running moderation on the request input and generated output. + + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. + + parallel_tool_calls: Whether to enable + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) + during tool use. + + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. + + presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on + whether they appear in the text so far, increasing the model's likelihood to + talk about new topics. + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + + response_format: An object specifying the format that the model must output. + + Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured + Outputs which ensures the model will match your supplied JSON schema. Learn more + in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + seed: This feature is in Beta. If specified, our system will make a best effort to + sample deterministically, such that repeated requests with the same `seed` and + parameters should return the same result. Determinism is not guaranteed, and you + should refer to the `system_fingerprint` response parameter to monitor changes + in the backend. + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The + returned text will not contain the stop sequence. + + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. + + Supports text and image inputs. Note: image inputs over 8MB will be dropped. + + stream_options: Options for streaming response. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. + + `none` is the default when no tools are present. `auto` is the default if tools + are present. + + tools: A list of tools the model may call. You can provide either + [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + or [function tools](https://platform.openai.com/docs/guides/function-calling). + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + `logprobs` must be set to `true` if this parameter is used. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. + + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def create( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + stream: bool, + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: completion_create_params.ResponseFormat | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletion | AsyncStream[ChatCompletionChunk]: + """ + **Starting a new project?** We recommend trying + [Responses](https://platform.openai.com/docs/api-reference/responses) to take + advantage of the latest OpenAI platform features. Compare + [Chat Completions with Responses](https://platform.openai.com/docs/guides/responses-vs-chat-completions?api-mode=responses). + + --- + + Creates a model response for the given chat conversation. Learn more in the + [text generation](https://platform.openai.com/docs/guides/text-generation), + [vision](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio) guides. + + Parameter support can differ depending on the model used to generate the + response, particularly for newer reasoning models. Parameters that are only + supported for reasoning models are noted below. For the current state of + unsupported parameters in reasoning models, + [refer to the reasoning guide](https://platform.openai.com/docs/guides/reasoning). + + Returns a chat completion object, or a streamed sequence of chat completion + chunk objects if the request is streamed. + + Args: + messages: A list of messages comprising the conversation so far. Depending on the + [model](https://platform.openai.com/docs/models) you use, different message + types (modalities) are supported, like + [text](https://platform.openai.com/docs/guides/text-generation), + [images](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio). + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/chat/streaming) + for more information, along with the + [streaming responses](https://platform.openai.com/docs/guides/streaming-responses) + guide for more information on how to handle the streaming events. + + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). + + frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their + existing frequency in the text so far, decreasing the model's likelihood to + repeat the same line verbatim. + + function_call: Deprecated in favor of `tool_choice`. + + Controls which (if any) function is called by the model. + + `none` means the model will not call a function and instead generates a message. + + `auto` means the model can pick between generating a message or calling a + function. + + Specifying a particular function via `{"name": "my_function"}` forces the model + to call that function. + + `none` is the default when no functions are present. `auto` is the default if + functions are present. + + functions: Deprecated in favor of `tools`. + + A list of functions the model may generate JSON inputs for. + + logit_bias: Modify the likelihood of specified tokens appearing in the completion. + + Accepts a JSON object that maps tokens (specified by their token ID in the + tokenizer) to an associated bias value from -100 to 100. Mathematically, the + bias is added to the logits generated by the model prior to sampling. The exact + effect will vary per model, but values between -1 and 1 should decrease or + increase likelihood of selection; values like -100 or 100 should result in a ban + or exclusive selection of the relevant token. + + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. + + max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tokens: The maximum number of [tokens](/tokenizer) that can be generated in the chat + completion. This value can be used to control + [costs](https://openai.com/api/pricing/) for text generated via API. + + This value is now deprecated in favor of `max_completion_tokens`, and is not + compatible with + [o-series models](https://platform.openai.com/docs/guides/reasoning). + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: + + `["text"]` + + The `gpt-4o-audio-preview` model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + + `["text", "audio"]` + + moderation: Configuration for running moderation on the request input and generated output. + + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. + + parallel_tool_calls: Whether to enable + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) + during tool use. + + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. + + presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on + whether they appear in the text so far, increasing the model's likelihood to + talk about new topics. + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + + response_format: An object specifying the format that the model must output. + + Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured + Outputs which ensures the model will match your supplied JSON schema. Learn more + in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + seed: This feature is in Beta. If specified, our system will make a best effort to + sample deterministically, such that repeated requests with the same `seed` and + parameters should return the same result. Determinism is not guaranteed, and you + should refer to the `system_fingerprint` response parameter to monitor changes + in the backend. + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The + returned text will not contain the stop sequence. + + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. + + Supports text and image inputs. Note: image inputs over 8MB will be dropped. + + stream_options: Options for streaming response. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. + + `none` is the default when no tools are present. `auto` is the default if tools + are present. + + tools: A list of tools the model may call. You can provide either + [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + or [function tools](https://platform.openai.com/docs/guides/function-calling). + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + `logprobs` must be set to `true` if this parameter is used. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. + + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["messages", "model"], ["messages", "model", "stream"]) + async def create( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + response_format: completion_create_params.ResponseFormat | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletion | AsyncStream[ChatCompletionChunk]: + validate_response_format(response_format) + return await self._post( + "/chat/completions", + body=await async_maybe_transform( + { + "messages": messages, + "model": model, + "audio": audio, + "frequency_penalty": frequency_penalty, + "function_call": function_call, + "functions": functions, + "logit_bias": logit_bias, + "logprobs": logprobs, + "max_completion_tokens": max_completion_tokens, + "max_tokens": max_tokens, + "metadata": metadata, + "modalities": modalities, + "moderation": moderation, + "n": n, + "parallel_tool_calls": parallel_tool_calls, + "prediction": prediction, + "presence_penalty": presence_penalty, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning_effort": reasoning_effort, + "response_format": response_format, + "safety_identifier": safety_identifier, + "seed": seed, + "service_tier": service_tier, + "stop": stop, + "store": store, + "stream": stream, + "stream_options": stream_options, + "temperature": temperature, + "tool_choice": tool_choice, + "tools": tools, + "top_logprobs": top_logprobs, + "top_p": top_p, + "user": user, + "verbosity": verbosity, + "web_search_options": web_search_options, + }, + completion_create_params.CompletionCreateParamsStreaming + if stream + else completion_create_params.CompletionCreateParamsNonStreaming, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatCompletion, + stream=stream or False, + stream_cls=AsyncStream[ChatCompletionChunk], + ) + + async def retrieve( + self, + completion_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletion: + """Get a stored chat completion. + + Only Chat Completions that have been created with + the `store` parameter set to `true` will be returned. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not completion_id: + raise ValueError(f"Expected a non-empty value for `completion_id` but received {completion_id!r}") + return await self._get( + path_template("/chat/completions/{completion_id}", completion_id=completion_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatCompletion, + ) + + async def update( + self, + completion_id: str, + *, + metadata: Optional[Metadata], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletion: + """Modify a stored chat completion. + + Only Chat Completions that have been created + with the `store` parameter set to `true` can be modified. Currently, the only + supported modification is to update the `metadata` field. + + Args: + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not completion_id: + raise ValueError(f"Expected a non-empty value for `completion_id` but received {completion_id!r}") + return await self._post( + path_template("/chat/completions/{completion_id}", completion_id=completion_id), + body=await async_maybe_transform({"metadata": metadata}, completion_update_params.CompletionUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatCompletion, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: str | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ChatCompletion, AsyncCursorPage[ChatCompletion]]: + """List stored Chat Completions. + + Only Chat Completions that have been stored with + the `store` parameter set to `true` will be returned. + + Args: + after: Identifier for the last chat completion from the previous pagination request. + + limit: Number of Chat Completions to retrieve. + + metadata: + A list of metadata keys to filter the Chat Completions by. Example: + + `metadata[key1]=value1&metadata[key2]=value2` + + model: The model used to generate the Chat Completions. + + order: Sort order for Chat Completions by timestamp. Use `asc` for ascending order or + `desc` for descending order. Defaults to `asc`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/chat/completions", + page=AsyncCursorPage[ChatCompletion], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "metadata": metadata, + "model": model, + "order": order, + }, + completion_list_params.CompletionListParams, + ), + security={"bearer_auth": True}, + ), + model=ChatCompletion, + ) + + async def delete( + self, + completion_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChatCompletionDeleted: + """Delete a stored chat completion. + + Only Chat Completions that have been created + with the `store` parameter set to `true` can be deleted. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not completion_id: + raise ValueError(f"Expected a non-empty value for `completion_id` but received {completion_id!r}") + return await self._delete( + path_template("/chat/completions/{completion_id}", completion_id=completion_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ChatCompletionDeleted, + ) + + def stream( + self, + *, + messages: Iterable[ChatCompletionMessageParam], + model: Union[str, ChatModel], + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + response_format: completion_create_params.ResponseFormat | type[ResponseFormatT] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + function_call: completion_create_params.FunctionCall | Omit = omit, + functions: Iterable[completion_create_params.Function] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[bool] | Omit = omit, + max_completion_tokens: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + modalities: Optional[List[Literal["text", "audio"]]] | Omit = omit, + moderation: Optional[completion_create_params.Moderation] | Omit = omit, + n: Optional[int] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prediction: Optional[ChatCompletionPredictionContentParam] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning_effort: Optional[ReasoningEffort] | Omit = omit, + safety_identifier: str | Omit = omit, + seed: Optional[int] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + temperature: Optional[float] | Omit = omit, + tool_choice: ChatCompletionToolChoiceOptionParam | Omit = omit, + tools: Iterable[ChatCompletionToolUnionParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + web_search_options: completion_create_params.WebSearchOptions | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncChatCompletionStreamManager[ResponseFormatT]: + """Wrapper over the `client.chat.completions.create(stream=True)` method that provides a more granular event API + and automatic accumulation of each delta. + + This also supports all of the parsing utilities that `.parse()` does. + + Unlike `.create(stream=True)`, the `.stream()` method requires usage within a context manager to prevent accidental leakage of the response: + + ```py + async with client.chat.completions.stream( + model="gpt-4o-2024-08-06", + messages=[...], + ) as stream: + async for event in stream: + if event.type == "content.delta": + print(event.delta, flush=True, end="") + ``` + + When the context manager is entered, an `AsyncChatCompletionStream` instance is returned which, like `.create(stream=True)` is an async iterator. The full list of events that are yielded by the iterator are outlined in [these docs](https://github.com/openai/openai-python/blob/main/helpers.md#chat-completions-events). + + When the context manager exits, the response will be closed, however the `stream` instance is still available outside + the context manager. + """ + _validate_input_tools(tools) + + extra_headers = { + "X-Stainless-Helper-Method": "chat.completions.stream", + **(extra_headers or {}), + } + + api_request = self.create( + messages=messages, + model=model, + audio=audio, + stream=True, + response_format=_type_to_response_format(response_format), + frequency_penalty=frequency_penalty, + function_call=function_call, + functions=functions, + logit_bias=logit_bias, + logprobs=logprobs, + max_completion_tokens=max_completion_tokens, + max_tokens=max_tokens, + metadata=metadata, + modalities=modalities, + moderation=moderation, + n=n, + parallel_tool_calls=parallel_tool_calls, + prediction=prediction, + presence_penalty=presence_penalty, + prompt_cache_key=prompt_cache_key, + prompt_cache_retention=prompt_cache_retention, + reasoning_effort=reasoning_effort, + safety_identifier=safety_identifier, + seed=seed, + service_tier=service_tier, + stop=stop, + store=store, + stream_options=stream_options, + temperature=temperature, + tool_choice=tool_choice, + tools=tools, + top_logprobs=top_logprobs, + top_p=top_p, + user=user, + verbosity=verbosity, + web_search_options=web_search_options, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return AsyncChatCompletionStreamManager( + api_request, + response_format=response_format, + input_tools=tools, + ) + + +class CompletionsWithRawResponse: + def __init__(self, completions: Completions) -> None: + self._completions = completions + + self.parse = _legacy_response.to_raw_response_wrapper( + completions.parse, + ) + self.create = _legacy_response.to_raw_response_wrapper( + completions.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + completions.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + completions.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + completions.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + completions.delete, + ) + + @cached_property + def messages(self) -> MessagesWithRawResponse: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ + return MessagesWithRawResponse(self._completions.messages) + + +class AsyncCompletionsWithRawResponse: + def __init__(self, completions: AsyncCompletions) -> None: + self._completions = completions + + self.parse = _legacy_response.async_to_raw_response_wrapper( + completions.parse, + ) + self.create = _legacy_response.async_to_raw_response_wrapper( + completions.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + completions.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + completions.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + completions.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + completions.delete, + ) + + @cached_property + def messages(self) -> AsyncMessagesWithRawResponse: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ + return AsyncMessagesWithRawResponse(self._completions.messages) + + +class CompletionsWithStreamingResponse: + def __init__(self, completions: Completions) -> None: + self._completions = completions + + self.parse = to_streamed_response_wrapper( + completions.parse, + ) + self.create = to_streamed_response_wrapper( + completions.create, + ) + self.retrieve = to_streamed_response_wrapper( + completions.retrieve, + ) + self.update = to_streamed_response_wrapper( + completions.update, + ) + self.list = to_streamed_response_wrapper( + completions.list, + ) + self.delete = to_streamed_response_wrapper( + completions.delete, + ) + + @cached_property + def messages(self) -> MessagesWithStreamingResponse: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ + return MessagesWithStreamingResponse(self._completions.messages) + + +class AsyncCompletionsWithStreamingResponse: + def __init__(self, completions: AsyncCompletions) -> None: + self._completions = completions + + self.parse = async_to_streamed_response_wrapper( + completions.parse, + ) + self.create = async_to_streamed_response_wrapper( + completions.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + completions.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + completions.update, + ) + self.list = async_to_streamed_response_wrapper( + completions.list, + ) + self.delete = async_to_streamed_response_wrapper( + completions.delete, + ) + + @cached_property + def messages(self) -> AsyncMessagesWithStreamingResponse: + """ + Given a list of messages comprising a conversation, the model will return a response. + """ + return AsyncMessagesWithStreamingResponse(self._completions.messages) + + +def validate_response_format(response_format: object) -> None: + if inspect.isclass(response_format) and issubclass(response_format, pydantic.BaseModel): + raise TypeError( + "You tried to pass a `BaseModel` class to `chat.completions.create()`; You must use `chat.completions.parse()` instead" + ) diff --git a/src/openai/resources/chat/completions/messages.py b/src/openai/resources/chat/completions/messages.py new file mode 100644 index 0000000000..05dd23735a --- /dev/null +++ b/src/openai/resources/chat/completions/messages.py @@ -0,0 +1,222 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncCursorPage, AsyncCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.chat.completions import message_list_params +from ....types.chat.chat_completion_store_message import ChatCompletionStoreMessage + +__all__ = ["Messages", "AsyncMessages"] + + +class Messages(SyncAPIResource): + """ + Given a list of messages comprising a conversation, the model will return a response. + """ + + @cached_property + def with_raw_response(self) -> MessagesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return MessagesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> MessagesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return MessagesWithStreamingResponse(self) + + def list( + self, + completion_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[ChatCompletionStoreMessage]: + """Get the messages in a stored chat completion. + + Only Chat Completions that have + been created with the `store` parameter set to `true` will be returned. + + Args: + after: Identifier for the last message from the previous pagination request. + + limit: Number of messages to retrieve. + + order: Sort order for messages by timestamp. Use `asc` for ascending order or `desc` + for descending order. Defaults to `asc`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not completion_id: + raise ValueError(f"Expected a non-empty value for `completion_id` but received {completion_id!r}") + return self._get_api_list( + path_template("/chat/completions/{completion_id}/messages", completion_id=completion_id), + page=SyncCursorPage[ChatCompletionStoreMessage], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + message_list_params.MessageListParams, + ), + security={"bearer_auth": True}, + ), + model=ChatCompletionStoreMessage, + ) + + +class AsyncMessages(AsyncAPIResource): + """ + Given a list of messages comprising a conversation, the model will return a response. + """ + + @cached_property + def with_raw_response(self) -> AsyncMessagesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncMessagesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncMessagesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncMessagesWithStreamingResponse(self) + + def list( + self, + completion_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ChatCompletionStoreMessage, AsyncCursorPage[ChatCompletionStoreMessage]]: + """Get the messages in a stored chat completion. + + Only Chat Completions that have + been created with the `store` parameter set to `true` will be returned. + + Args: + after: Identifier for the last message from the previous pagination request. + + limit: Number of messages to retrieve. + + order: Sort order for messages by timestamp. Use `asc` for ascending order or `desc` + for descending order. Defaults to `asc`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not completion_id: + raise ValueError(f"Expected a non-empty value for `completion_id` but received {completion_id!r}") + return self._get_api_list( + path_template("/chat/completions/{completion_id}/messages", completion_id=completion_id), + page=AsyncCursorPage[ChatCompletionStoreMessage], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + message_list_params.MessageListParams, + ), + security={"bearer_auth": True}, + ), + model=ChatCompletionStoreMessage, + ) + + +class MessagesWithRawResponse: + def __init__(self, messages: Messages) -> None: + self._messages = messages + + self.list = _legacy_response.to_raw_response_wrapper( + messages.list, + ) + + +class AsyncMessagesWithRawResponse: + def __init__(self, messages: AsyncMessages) -> None: + self._messages = messages + + self.list = _legacy_response.async_to_raw_response_wrapper( + messages.list, + ) + + +class MessagesWithStreamingResponse: + def __init__(self, messages: Messages) -> None: + self._messages = messages + + self.list = to_streamed_response_wrapper( + messages.list, + ) + + +class AsyncMessagesWithStreamingResponse: + def __init__(self, messages: AsyncMessages) -> None: + self._messages = messages + + self.list = async_to_streamed_response_wrapper( + messages.list, + ) diff --git a/src/openai/resources/completions.py b/src/openai/resources/completions.py index 091fb5657a..d2eb646a66 100644 --- a/src/openai/resources/completions.py +++ b/src/openai/resources/completions.py @@ -2,19 +2,15 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable, Optional, overload -from typing_extensions import Literal +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Literal, overload import httpx from .. import _legacy_response from ..types import completion_create_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - required_args, - maybe_transform, - async_maybe_transform, -) +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import required_args, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -29,10 +25,14 @@ class Completions(SyncAPIResource): + """ + Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + """ + @cached_property def with_raw_response(self) -> CompletionsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -53,39 +53,42 @@ def create( self, *, model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]], - prompt: Union[str, List[str], Iterable[int], Iterable[Iterable[int]], None], - best_of: Optional[int] | NotGiven = NOT_GIVEN, - echo: Optional[bool] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str], None] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - suffix: Optional[str] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None], + best_of: Optional[int] | Omit = omit, + echo: Optional[bool] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + n: Optional[int] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + seed: Optional[int] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + suffix: Optional[str] | Omit = omit, + temperature: Optional[float] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Completion: """ Creates a completion for the provided prompt and parameters. + Returns a completion object, or a sequence of completion objects if the request + is streamed. + Args: model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. prompt: The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays. @@ -110,7 +113,7 @@ def create( existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) logit_bias: Modify the likelihood of specified tokens appearing in the completion. @@ -150,7 +153,7 @@ def create( whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) seed: If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return @@ -159,7 +162,9 @@ def create( Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend. - stop: Up to 4 sequences where the API will stop generating further tokens. The + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. stream: Whether to stream back partial progress. If set, tokens will be sent as @@ -189,7 +194,7 @@ def create( user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -206,39 +211,42 @@ def create( self, *, model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]], - prompt: Union[str, List[str], Iterable[int], Iterable[Iterable[int]], None], + prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None], stream: Literal[True], - best_of: Optional[int] | NotGiven = NOT_GIVEN, - echo: Optional[bool] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str], None] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - suffix: Optional[str] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + best_of: Optional[int] | Omit = omit, + echo: Optional[bool] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + n: Optional[int] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + seed: Optional[int] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + suffix: Optional[str] | Omit = omit, + temperature: Optional[float] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Stream[Completion]: """ Creates a completion for the provided prompt and parameters. + Returns a completion object, or a sequence of completion objects if the request + is streamed. + Args: model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. prompt: The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays. @@ -270,7 +278,7 @@ def create( existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) logit_bias: Modify the likelihood of specified tokens appearing in the completion. @@ -310,7 +318,7 @@ def create( whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) seed: If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return @@ -319,7 +327,9 @@ def create( Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend. - stop: Up to 4 sequences where the API will stop generating further tokens. The + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. stream_options: Options for streaming response. Only set this when you set `stream: true`. @@ -342,7 +352,7 @@ def create( user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -359,39 +369,42 @@ def create( self, *, model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]], - prompt: Union[str, List[str], Iterable[int], Iterable[Iterable[int]], None], + prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None], stream: bool, - best_of: Optional[int] | NotGiven = NOT_GIVEN, - echo: Optional[bool] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str], None] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - suffix: Optional[str] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + best_of: Optional[int] | Omit = omit, + echo: Optional[bool] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + n: Optional[int] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + seed: Optional[int] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + suffix: Optional[str] | Omit = omit, + temperature: Optional[float] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Completion | Stream[Completion]: """ Creates a completion for the provided prompt and parameters. + Returns a completion object, or a sequence of completion objects if the request + is streamed. + Args: model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. prompt: The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays. @@ -423,7 +436,7 @@ def create( existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) logit_bias: Modify the likelihood of specified tokens appearing in the completion. @@ -463,7 +476,7 @@ def create( whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) seed: If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return @@ -472,7 +485,9 @@ def create( Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend. - stop: Up to 4 sequences where the API will stop generating further tokens. The + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. stream_options: Options for streaming response. Only set this when you set `stream: true`. @@ -495,7 +510,7 @@ def create( user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -512,29 +527,29 @@ def create( self, *, model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]], - prompt: Union[str, List[str], Iterable[int], Iterable[Iterable[int]], None], - best_of: Optional[int] | NotGiven = NOT_GIVEN, - echo: Optional[bool] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str], None] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | Literal[True] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - suffix: Optional[str] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None], + best_of: Optional[int] | Omit = omit, + echo: Optional[bool] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + n: Optional[int] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + seed: Optional[int] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + suffix: Optional[str] | Omit = omit, + temperature: Optional[float] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Completion | Stream[Completion]: return self._post( "/completions", @@ -559,10 +574,16 @@ def create( "top_p": top_p, "user": user, }, - completion_create_params.CompletionCreateParams, + completion_create_params.CompletionCreateParamsStreaming + if stream + else completion_create_params.CompletionCreateParamsNonStreaming, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Completion, stream=stream or False, @@ -571,10 +592,14 @@ def create( class AsyncCompletions(AsyncAPIResource): + """ + Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + """ + @cached_property def with_raw_response(self) -> AsyncCompletionsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -595,39 +620,42 @@ async def create( self, *, model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]], - prompt: Union[str, List[str], Iterable[int], Iterable[Iterable[int]], None], - best_of: Optional[int] | NotGiven = NOT_GIVEN, - echo: Optional[bool] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str], None] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - suffix: Optional[str] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None], + best_of: Optional[int] | Omit = omit, + echo: Optional[bool] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + n: Optional[int] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + seed: Optional[int] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + suffix: Optional[str] | Omit = omit, + temperature: Optional[float] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Completion: """ Creates a completion for the provided prompt and parameters. + Returns a completion object, or a sequence of completion objects if the request + is streamed. + Args: model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. prompt: The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays. @@ -652,7 +680,7 @@ async def create( existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) logit_bias: Modify the likelihood of specified tokens appearing in the completion. @@ -692,7 +720,7 @@ async def create( whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) seed: If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return @@ -701,7 +729,9 @@ async def create( Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend. - stop: Up to 4 sequences where the API will stop generating further tokens. The + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. stream: Whether to stream back partial progress. If set, tokens will be sent as @@ -731,7 +761,7 @@ async def create( user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -748,39 +778,42 @@ async def create( self, *, model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]], - prompt: Union[str, List[str], Iterable[int], Iterable[Iterable[int]], None], + prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None], stream: Literal[True], - best_of: Optional[int] | NotGiven = NOT_GIVEN, - echo: Optional[bool] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str], None] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - suffix: Optional[str] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + best_of: Optional[int] | Omit = omit, + echo: Optional[bool] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + n: Optional[int] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + seed: Optional[int] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + suffix: Optional[str] | Omit = omit, + temperature: Optional[float] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncStream[Completion]: """ Creates a completion for the provided prompt and parameters. + Returns a completion object, or a sequence of completion objects if the request + is streamed. + Args: model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. prompt: The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays. @@ -812,7 +845,7 @@ async def create( existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) logit_bias: Modify the likelihood of specified tokens appearing in the completion. @@ -852,7 +885,7 @@ async def create( whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) seed: If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return @@ -861,7 +894,9 @@ async def create( Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend. - stop: Up to 4 sequences where the API will stop generating further tokens. The + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. stream_options: Options for streaming response. Only set this when you set `stream: true`. @@ -884,7 +919,7 @@ async def create( user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -901,39 +936,42 @@ async def create( self, *, model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]], - prompt: Union[str, List[str], Iterable[int], Iterable[Iterable[int]], None], + prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None], stream: bool, - best_of: Optional[int] | NotGiven = NOT_GIVEN, - echo: Optional[bool] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str], None] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - suffix: Optional[str] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + best_of: Optional[int] | Omit = omit, + echo: Optional[bool] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + n: Optional[int] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + seed: Optional[int] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + suffix: Optional[str] | Omit = omit, + temperature: Optional[float] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Completion | AsyncStream[Completion]: """ Creates a completion for the provided prompt and parameters. + Returns a completion object, or a sequence of completion objects if the request + is streamed. + Args: model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. prompt: The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays. @@ -965,7 +1003,7 @@ async def create( existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) logit_bias: Modify the likelihood of specified tokens appearing in the completion. @@ -1005,7 +1043,7 @@ async def create( whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) seed: If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return @@ -1014,7 +1052,9 @@ async def create( Determinism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend. - stop: Up to 4 sequences where the API will stop generating further tokens. The + stop: Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. stream_options: Options for streaming response. Only set this when you set `stream: true`. @@ -1037,7 +1077,7 @@ async def create( user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -1054,29 +1094,29 @@ async def create( self, *, model: Union[str, Literal["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"]], - prompt: Union[str, List[str], Iterable[int], Iterable[Iterable[int]], None], - best_of: Optional[int] | NotGiven = NOT_GIVEN, - echo: Optional[bool] | NotGiven = NOT_GIVEN, - frequency_penalty: Optional[float] | NotGiven = NOT_GIVEN, - logit_bias: Optional[Dict[str, int]] | NotGiven = NOT_GIVEN, - logprobs: Optional[int] | NotGiven = NOT_GIVEN, - max_tokens: Optional[int] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - stop: Union[Optional[str], List[str], None] | NotGiven = NOT_GIVEN, - stream: Optional[Literal[False]] | Literal[True] | NotGiven = NOT_GIVEN, - stream_options: Optional[ChatCompletionStreamOptionsParam] | NotGiven = NOT_GIVEN, - suffix: Optional[str] | NotGiven = NOT_GIVEN, - temperature: Optional[float] | NotGiven = NOT_GIVEN, - top_p: Optional[float] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + prompt: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None], + best_of: Optional[int] | Omit = omit, + echo: Optional[bool] | Omit = omit, + frequency_penalty: Optional[float] | Omit = omit, + logit_bias: Optional[Dict[str, int]] | Omit = omit, + logprobs: Optional[int] | Omit = omit, + max_tokens: Optional[int] | Omit = omit, + n: Optional[int] | Omit = omit, + presence_penalty: Optional[float] | Omit = omit, + seed: Optional[int] | Omit = omit, + stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + stream_options: Optional[ChatCompletionStreamOptionsParam] | Omit = omit, + suffix: Optional[str] | Omit = omit, + temperature: Optional[float] | Omit = omit, + top_p: Optional[float] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Completion | AsyncStream[Completion]: return await self._post( "/completions", @@ -1101,10 +1141,16 @@ async def create( "top_p": top_p, "user": user, }, - completion_create_params.CompletionCreateParams, + completion_create_params.CompletionCreateParamsStreaming + if stream + else completion_create_params.CompletionCreateParamsNonStreaming, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Completion, stream=stream or False, diff --git a/src/openai/resources/containers/__init__.py b/src/openai/resources/containers/__init__.py new file mode 100644 index 0000000000..dc1936780b --- /dev/null +++ b/src/openai/resources/containers/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .files import ( + Files, + AsyncFiles, + FilesWithRawResponse, + AsyncFilesWithRawResponse, + FilesWithStreamingResponse, + AsyncFilesWithStreamingResponse, +) +from .containers import ( + Containers, + AsyncContainers, + ContainersWithRawResponse, + AsyncContainersWithRawResponse, + ContainersWithStreamingResponse, + AsyncContainersWithStreamingResponse, +) + +__all__ = [ + "Files", + "AsyncFiles", + "FilesWithRawResponse", + "AsyncFilesWithRawResponse", + "FilesWithStreamingResponse", + "AsyncFilesWithStreamingResponse", + "Containers", + "AsyncContainers", + "ContainersWithRawResponse", + "AsyncContainersWithRawResponse", + "ContainersWithStreamingResponse", + "AsyncContainersWithStreamingResponse", +] diff --git a/src/openai/resources/containers/containers.py b/src/openai/resources/containers/containers.py new file mode 100644 index 0000000000..7588ad7240 --- /dev/null +++ b/src/openai/resources/containers/containers.py @@ -0,0 +1,569 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal + +import httpx + +from ... import _legacy_response +from ...types import container_list_params, container_create_params +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .files.files import ( + Files, + AsyncFiles, + FilesWithRawResponse, + AsyncFilesWithRawResponse, + FilesWithStreamingResponse, + AsyncFilesWithStreamingResponse, +) +from ...pagination import SyncCursorPage, AsyncCursorPage +from ..._base_client import AsyncPaginator, make_request_options +from ...types.container_list_response import ContainerListResponse +from ...types.container_create_response import ContainerCreateResponse +from ...types.container_retrieve_response import ContainerRetrieveResponse + +__all__ = ["Containers", "AsyncContainers"] + + +class Containers(SyncAPIResource): + @cached_property + def files(self) -> Files: + return Files(self._client) + + @cached_property + def with_raw_response(self) -> ContainersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ContainersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ContainersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ContainersWithStreamingResponse(self) + + def create( + self, + *, + name: str, + expires_after: container_create_params.ExpiresAfter | Omit = omit, + file_ids: SequenceNotStr[str] | Omit = omit, + memory_limit: Literal["1g", "4g", "16g", "64g"] | Omit = omit, + network_policy: container_create_params.NetworkPolicy | Omit = omit, + skills: Iterable[container_create_params.Skill] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ContainerCreateResponse: + """ + Create Container + + Args: + name: Name of the container to create. + + expires_after: Container expiration time in seconds relative to the 'anchor' time. + + file_ids: IDs of files to copy to the container. + + memory_limit: Optional memory limit for the container. Defaults to "1g". + + network_policy: Network access policy for the container. + + skills: An optional list of skills referenced by id or inline data. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/containers", + body=maybe_transform( + { + "name": name, + "expires_after": expires_after, + "file_ids": file_ids, + "memory_limit": memory_limit, + "network_policy": network_policy, + "skills": skills, + }, + container_create_params.ContainerCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ContainerCreateResponse, + ) + + def retrieve( + self, + container_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ContainerRetrieveResponse: + """ + Retrieve Container + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + return self._get( + path_template("/containers/{container_id}", container_id=container_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ContainerRetrieveResponse, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + name: str | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[ContainerListResponse]: + """List Containers + + Args: + after: A cursor for use in pagination. + + `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + name: Filter results by container name. + + order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending + order and `desc` for descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/containers", + page=SyncCursorPage[ContainerListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "name": name, + "order": order, + }, + container_list_params.ContainerListParams, + ), + security={"bearer_auth": True}, + ), + model=ContainerListResponse, + ) + + def delete( + self, + container_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Container + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + path_template("/containers/{container_id}", container_id=container_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + +class AsyncContainers(AsyncAPIResource): + @cached_property + def files(self) -> AsyncFiles: + return AsyncFiles(self._client) + + @cached_property + def with_raw_response(self) -> AsyncContainersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncContainersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncContainersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncContainersWithStreamingResponse(self) + + async def create( + self, + *, + name: str, + expires_after: container_create_params.ExpiresAfter | Omit = omit, + file_ids: SequenceNotStr[str] | Omit = omit, + memory_limit: Literal["1g", "4g", "16g", "64g"] | Omit = omit, + network_policy: container_create_params.NetworkPolicy | Omit = omit, + skills: Iterable[container_create_params.Skill] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ContainerCreateResponse: + """ + Create Container + + Args: + name: Name of the container to create. + + expires_after: Container expiration time in seconds relative to the 'anchor' time. + + file_ids: IDs of files to copy to the container. + + memory_limit: Optional memory limit for the container. Defaults to "1g". + + network_policy: Network access policy for the container. + + skills: An optional list of skills referenced by id or inline data. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/containers", + body=await async_maybe_transform( + { + "name": name, + "expires_after": expires_after, + "file_ids": file_ids, + "memory_limit": memory_limit, + "network_policy": network_policy, + "skills": skills, + }, + container_create_params.ContainerCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ContainerCreateResponse, + ) + + async def retrieve( + self, + container_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ContainerRetrieveResponse: + """ + Retrieve Container + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + return await self._get( + path_template("/containers/{container_id}", container_id=container_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ContainerRetrieveResponse, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + name: str | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ContainerListResponse, AsyncCursorPage[ContainerListResponse]]: + """List Containers + + Args: + after: A cursor for use in pagination. + + `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + name: Filter results by container name. + + order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending + order and `desc` for descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/containers", + page=AsyncCursorPage[ContainerListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "name": name, + "order": order, + }, + container_list_params.ContainerListParams, + ), + security={"bearer_auth": True}, + ), + model=ContainerListResponse, + ) + + async def delete( + self, + container_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Container + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + path_template("/containers/{container_id}", container_id=container_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + +class ContainersWithRawResponse: + def __init__(self, containers: Containers) -> None: + self._containers = containers + + self.create = _legacy_response.to_raw_response_wrapper( + containers.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + containers.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + containers.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + containers.delete, + ) + + @cached_property + def files(self) -> FilesWithRawResponse: + return FilesWithRawResponse(self._containers.files) + + +class AsyncContainersWithRawResponse: + def __init__(self, containers: AsyncContainers) -> None: + self._containers = containers + + self.create = _legacy_response.async_to_raw_response_wrapper( + containers.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + containers.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + containers.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + containers.delete, + ) + + @cached_property + def files(self) -> AsyncFilesWithRawResponse: + return AsyncFilesWithRawResponse(self._containers.files) + + +class ContainersWithStreamingResponse: + def __init__(self, containers: Containers) -> None: + self._containers = containers + + self.create = to_streamed_response_wrapper( + containers.create, + ) + self.retrieve = to_streamed_response_wrapper( + containers.retrieve, + ) + self.list = to_streamed_response_wrapper( + containers.list, + ) + self.delete = to_streamed_response_wrapper( + containers.delete, + ) + + @cached_property + def files(self) -> FilesWithStreamingResponse: + return FilesWithStreamingResponse(self._containers.files) + + +class AsyncContainersWithStreamingResponse: + def __init__(self, containers: AsyncContainers) -> None: + self._containers = containers + + self.create = async_to_streamed_response_wrapper( + containers.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + containers.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + containers.list, + ) + self.delete = async_to_streamed_response_wrapper( + containers.delete, + ) + + @cached_property + def files(self) -> AsyncFilesWithStreamingResponse: + return AsyncFilesWithStreamingResponse(self._containers.files) diff --git a/src/openai/resources/containers/files/__init__.py b/src/openai/resources/containers/files/__init__.py new file mode 100644 index 0000000000..f71f7dbf55 --- /dev/null +++ b/src/openai/resources/containers/files/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .files import ( + Files, + AsyncFiles, + FilesWithRawResponse, + AsyncFilesWithRawResponse, + FilesWithStreamingResponse, + AsyncFilesWithStreamingResponse, +) +from .content import ( + Content, + AsyncContent, + ContentWithRawResponse, + AsyncContentWithRawResponse, + ContentWithStreamingResponse, + AsyncContentWithStreamingResponse, +) + +__all__ = [ + "Content", + "AsyncContent", + "ContentWithRawResponse", + "AsyncContentWithRawResponse", + "ContentWithStreamingResponse", + "AsyncContentWithStreamingResponse", + "Files", + "AsyncFiles", + "FilesWithRawResponse", + "AsyncFilesWithRawResponse", + "FilesWithStreamingResponse", + "AsyncFilesWithStreamingResponse", +] diff --git a/src/openai/resources/containers/files/content.py b/src/openai/resources/containers/files/content.py new file mode 100644 index 0000000000..7df5e4cf2d --- /dev/null +++ b/src/openai/resources/containers/files/content.py @@ -0,0 +1,186 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .... import _legacy_response +from ...._types import Body, Query, Headers, NotGiven, not_given +from ...._utils import path_template +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, + to_custom_streamed_response_wrapper, + async_to_custom_streamed_response_wrapper, +) +from ...._base_client import make_request_options + +__all__ = ["Content", "AsyncContent"] + + +class Content(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ContentWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ContentWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ContentWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ContentWithStreamingResponse(self) + + def retrieve( + self, + file_id: str, + *, + container_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + """ + Retrieve Container File Content + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"Accept": "application/binary", **(extra_headers or {})} + return self._get( + path_template( + "/containers/{container_id}/files/{file_id}/content", container_id=container_id, file_id=file_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + +class AsyncContent(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncContentWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncContentWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncContentWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncContentWithStreamingResponse(self) + + async def retrieve( + self, + file_id: str, + *, + container_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + """ + Retrieve Container File Content + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"Accept": "application/binary", **(extra_headers or {})} + return await self._get( + path_template( + "/containers/{container_id}/files/{file_id}/content", container_id=container_id, file_id=file_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + +class ContentWithRawResponse: + def __init__(self, content: Content) -> None: + self._content = content + + self.retrieve = _legacy_response.to_raw_response_wrapper( + content.retrieve, + ) + + +class AsyncContentWithRawResponse: + def __init__(self, content: AsyncContent) -> None: + self._content = content + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + content.retrieve, + ) + + +class ContentWithStreamingResponse: + def __init__(self, content: Content) -> None: + self._content = content + + self.retrieve = to_custom_streamed_response_wrapper( + content.retrieve, + StreamedBinaryAPIResponse, + ) + + +class AsyncContentWithStreamingResponse: + def __init__(self, content: AsyncContent) -> None: + self._content = content + + self.retrieve = async_to_custom_streamed_response_wrapper( + content.retrieve, + AsyncStreamedBinaryAPIResponse, + ) diff --git a/src/openai/resources/containers/files/files.py b/src/openai/resources/containers/files/files.py new file mode 100644 index 0000000000..736b1fb75b --- /dev/null +++ b/src/openai/resources/containers/files/files.py @@ -0,0 +1,576 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Mapping, cast +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from .content import ( + Content, + AsyncContent, + ContentWithRawResponse, + AsyncContentWithRawResponse, + ContentWithStreamingResponse, + AsyncContentWithStreamingResponse, +) +from ...._files import deepcopy_with_paths +from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, FileTypes, omit, not_given +from ...._utils import extract_files, path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncCursorPage, AsyncCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.containers import file_list_params, file_create_params +from ....types.containers.file_list_response import FileListResponse +from ....types.containers.file_create_response import FileCreateResponse +from ....types.containers.file_retrieve_response import FileRetrieveResponse + +__all__ = ["Files", "AsyncFiles"] + + +class Files(SyncAPIResource): + @cached_property + def content(self) -> Content: + return Content(self._client) + + @cached_property + def with_raw_response(self) -> FilesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return FilesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> FilesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return FilesWithStreamingResponse(self) + + def create( + self, + container_id: str, + *, + file: FileTypes | Omit = omit, + file_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FileCreateResponse: + """ + Create a Container File + + You can send either a multipart/form-data request with the raw file content, or + a JSON request with a file ID. + + Args: + file: The File object (not file name) to be uploaded. + + file_id: Name of the file to create. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + body = deepcopy_with_paths( + { + "file": file, + "file_id": file_id, + }, + [["file"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) + if files: + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( + path_template("/containers/{container_id}/files", container_id=container_id), + body=maybe_transform(body, file_create_params.FileCreateParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=FileCreateResponse, + ) + + def retrieve( + self, + file_id: str, + *, + container_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FileRetrieveResponse: + """ + Retrieve Container File + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + return self._get( + path_template("/containers/{container_id}/files/{file_id}", container_id=container_id, file_id=file_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=FileRetrieveResponse, + ) + + def list( + self, + container_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[FileListResponse]: + """List Container files + + Args: + after: A cursor for use in pagination. + + `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending + order and `desc` for descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + return self._get_api_list( + path_template("/containers/{container_id}/files", container_id=container_id), + page=SyncCursorPage[FileListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + file_list_params.FileListParams, + ), + security={"bearer_auth": True}, + ), + model=FileListResponse, + ) + + def delete( + self, + file_id: str, + *, + container_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Container File + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + path_template("/containers/{container_id}/files/{file_id}", container_id=container_id, file_id=file_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + +class AsyncFiles(AsyncAPIResource): + @cached_property + def content(self) -> AsyncContent: + return AsyncContent(self._client) + + @cached_property + def with_raw_response(self) -> AsyncFilesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncFilesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncFilesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncFilesWithStreamingResponse(self) + + async def create( + self, + container_id: str, + *, + file: FileTypes | Omit = omit, + file_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FileCreateResponse: + """ + Create a Container File + + You can send either a multipart/form-data request with the raw file content, or + a JSON request with a file ID. + + Args: + file: The File object (not file name) to be uploaded. + + file_id: Name of the file to create. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + body = deepcopy_with_paths( + { + "file": file, + "file_id": file_id, + }, + [["file"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) + if files: + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + path_template("/containers/{container_id}/files", container_id=container_id), + body=await async_maybe_transform(body, file_create_params.FileCreateParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=FileCreateResponse, + ) + + async def retrieve( + self, + file_id: str, + *, + container_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FileRetrieveResponse: + """ + Retrieve Container File + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + return await self._get( + path_template("/containers/{container_id}/files/{file_id}", container_id=container_id, file_id=file_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=FileRetrieveResponse, + ) + + def list( + self, + container_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[FileListResponse, AsyncCursorPage[FileListResponse]]: + """List Container files + + Args: + after: A cursor for use in pagination. + + `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending + order and `desc` for descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + return self._get_api_list( + path_template("/containers/{container_id}/files", container_id=container_id), + page=AsyncCursorPage[FileListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + file_list_params.FileListParams, + ), + security={"bearer_auth": True}, + ), + model=FileListResponse, + ) + + async def delete( + self, + file_id: str, + *, + container_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Container File + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not container_id: + raise ValueError(f"Expected a non-empty value for `container_id` but received {container_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + path_template("/containers/{container_id}/files/{file_id}", container_id=container_id, file_id=file_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + +class FilesWithRawResponse: + def __init__(self, files: Files) -> None: + self._files = files + + self.create = _legacy_response.to_raw_response_wrapper( + files.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + files.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + files.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + files.delete, + ) + + @cached_property + def content(self) -> ContentWithRawResponse: + return ContentWithRawResponse(self._files.content) + + +class AsyncFilesWithRawResponse: + def __init__(self, files: AsyncFiles) -> None: + self._files = files + + self.create = _legacy_response.async_to_raw_response_wrapper( + files.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + files.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + files.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + files.delete, + ) + + @cached_property + def content(self) -> AsyncContentWithRawResponse: + return AsyncContentWithRawResponse(self._files.content) + + +class FilesWithStreamingResponse: + def __init__(self, files: Files) -> None: + self._files = files + + self.create = to_streamed_response_wrapper( + files.create, + ) + self.retrieve = to_streamed_response_wrapper( + files.retrieve, + ) + self.list = to_streamed_response_wrapper( + files.list, + ) + self.delete = to_streamed_response_wrapper( + files.delete, + ) + + @cached_property + def content(self) -> ContentWithStreamingResponse: + return ContentWithStreamingResponse(self._files.content) + + +class AsyncFilesWithStreamingResponse: + def __init__(self, files: AsyncFiles) -> None: + self._files = files + + self.create = async_to_streamed_response_wrapper( + files.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + files.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + files.list, + ) + self.delete = async_to_streamed_response_wrapper( + files.delete, + ) + + @cached_property + def content(self) -> AsyncContentWithStreamingResponse: + return AsyncContentWithStreamingResponse(self._files.content) diff --git a/src/openai/resources/conversations/__init__.py b/src/openai/resources/conversations/__init__.py new file mode 100644 index 0000000000..c6c4fd6ee4 --- /dev/null +++ b/src/openai/resources/conversations/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .items import ( + Items, + AsyncItems, + ItemsWithRawResponse, + AsyncItemsWithRawResponse, + ItemsWithStreamingResponse, + AsyncItemsWithStreamingResponse, +) +from .conversations import ( + Conversations, + AsyncConversations, + ConversationsWithRawResponse, + AsyncConversationsWithRawResponse, + ConversationsWithStreamingResponse, + AsyncConversationsWithStreamingResponse, +) + +__all__ = [ + "Items", + "AsyncItems", + "ItemsWithRawResponse", + "AsyncItemsWithRawResponse", + "ItemsWithStreamingResponse", + "AsyncItemsWithStreamingResponse", + "Conversations", + "AsyncConversations", + "ConversationsWithRawResponse", + "AsyncConversationsWithRawResponse", + "ConversationsWithStreamingResponse", + "AsyncConversationsWithStreamingResponse", +] diff --git a/src/openai/resources/conversations/api.md b/src/openai/resources/conversations/api.md new file mode 100644 index 0000000000..9e9181a367 --- /dev/null +++ b/src/openai/resources/conversations/api.md @@ -0,0 +1,42 @@ +# Conversations + +Types: + +```python +from openai.types.conversations import ( + ComputerScreenshotContent, + Conversation, + ConversationDeleted, + ConversationDeletedResource, + Message, + SummaryTextContent, + TextContent, + InputTextContent, + OutputTextContent, + RefusalContent, + InputImageContent, + InputFileContent, +) +``` + +Methods: + +- client.conversations.create(\*\*params) -> Conversation +- client.conversations.retrieve(conversation_id) -> Conversation +- client.conversations.update(conversation_id, \*\*params) -> Conversation +- client.conversations.delete(conversation_id) -> ConversationDeletedResource + +## Items + +Types: + +```python +from openai.types.conversations import ConversationItem, ConversationItemList +``` + +Methods: + +- client.conversations.items.create(conversation_id, \*\*params) -> ConversationItemList +- client.conversations.items.retrieve(item_id, \*, conversation_id, \*\*params) -> ConversationItem +- client.conversations.items.list(conversation_id, \*\*params) -> SyncConversationCursorPage[ConversationItem] +- client.conversations.items.delete(item_id, \*, conversation_id) -> Conversation diff --git a/src/openai/resources/conversations/conversations.py b/src/openai/resources/conversations/conversations.py new file mode 100644 index 0000000000..bdc8e1d637 --- /dev/null +++ b/src/openai/resources/conversations/conversations.py @@ -0,0 +1,528 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional + +import httpx + +from ... import _legacy_response +from .items import ( + Items, + AsyncItems, + ItemsWithRawResponse, + AsyncItemsWithRawResponse, + ItemsWithStreamingResponse, + AsyncItemsWithStreamingResponse, +) +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options +from ...types.conversations import conversation_create_params, conversation_update_params +from ...types.shared_params.metadata import Metadata +from ...types.conversations.conversation import Conversation +from ...types.responses.response_input_item_param import ResponseInputItemParam +from ...types.conversations.conversation_deleted_resource import ConversationDeletedResource + +__all__ = ["Conversations", "AsyncConversations"] + + +class Conversations(SyncAPIResource): + """Manage conversations and conversation items.""" + + @cached_property + def items(self) -> Items: + """Manage conversations and conversation items.""" + return Items(self._client) + + @cached_property + def with_raw_response(self) -> ConversationsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ConversationsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ConversationsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ConversationsWithStreamingResponse(self) + + def create( + self, + *, + items: Optional[Iterable[ResponseInputItemParam]] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Conversation: + """ + Create a conversation. + + Args: + items: Initial items to include in the conversation context. You may add up to 20 items + at a time. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/conversations", + body=maybe_transform( + { + "items": items, + "metadata": metadata, + }, + conversation_create_params.ConversationCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Conversation, + ) + + def retrieve( + self, + conversation_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Conversation: + """ + Get a conversation + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + return self._get( + path_template("/conversations/{conversation_id}", conversation_id=conversation_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Conversation, + ) + + def update( + self, + conversation_id: str, + *, + metadata: Optional[Metadata], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Conversation: + """ + Update a conversation + + Args: + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + return self._post( + path_template("/conversations/{conversation_id}", conversation_id=conversation_id), + body=maybe_transform({"metadata": metadata}, conversation_update_params.ConversationUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Conversation, + ) + + def delete( + self, + conversation_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ConversationDeletedResource: + """Delete a conversation. + + Items in the conversation will not be deleted. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + return self._delete( + path_template("/conversations/{conversation_id}", conversation_id=conversation_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ConversationDeletedResource, + ) + + +class AsyncConversations(AsyncAPIResource): + """Manage conversations and conversation items.""" + + @cached_property + def items(self) -> AsyncItems: + """Manage conversations and conversation items.""" + return AsyncItems(self._client) + + @cached_property + def with_raw_response(self) -> AsyncConversationsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncConversationsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncConversationsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncConversationsWithStreamingResponse(self) + + async def create( + self, + *, + items: Optional[Iterable[ResponseInputItemParam]] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Conversation: + """ + Create a conversation. + + Args: + items: Initial items to include in the conversation context. You may add up to 20 items + at a time. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/conversations", + body=await async_maybe_transform( + { + "items": items, + "metadata": metadata, + }, + conversation_create_params.ConversationCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Conversation, + ) + + async def retrieve( + self, + conversation_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Conversation: + """ + Get a conversation + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + return await self._get( + path_template("/conversations/{conversation_id}", conversation_id=conversation_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Conversation, + ) + + async def update( + self, + conversation_id: str, + *, + metadata: Optional[Metadata], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Conversation: + """ + Update a conversation + + Args: + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + return await self._post( + path_template("/conversations/{conversation_id}", conversation_id=conversation_id), + body=await async_maybe_transform( + {"metadata": metadata}, conversation_update_params.ConversationUpdateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Conversation, + ) + + async def delete( + self, + conversation_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ConversationDeletedResource: + """Delete a conversation. + + Items in the conversation will not be deleted. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + return await self._delete( + path_template("/conversations/{conversation_id}", conversation_id=conversation_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ConversationDeletedResource, + ) + + +class ConversationsWithRawResponse: + def __init__(self, conversations: Conversations) -> None: + self._conversations = conversations + + self.create = _legacy_response.to_raw_response_wrapper( + conversations.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + conversations.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + conversations.update, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + conversations.delete, + ) + + @cached_property + def items(self) -> ItemsWithRawResponse: + """Manage conversations and conversation items.""" + return ItemsWithRawResponse(self._conversations.items) + + +class AsyncConversationsWithRawResponse: + def __init__(self, conversations: AsyncConversations) -> None: + self._conversations = conversations + + self.create = _legacy_response.async_to_raw_response_wrapper( + conversations.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + conversations.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + conversations.update, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + conversations.delete, + ) + + @cached_property + def items(self) -> AsyncItemsWithRawResponse: + """Manage conversations and conversation items.""" + return AsyncItemsWithRawResponse(self._conversations.items) + + +class ConversationsWithStreamingResponse: + def __init__(self, conversations: Conversations) -> None: + self._conversations = conversations + + self.create = to_streamed_response_wrapper( + conversations.create, + ) + self.retrieve = to_streamed_response_wrapper( + conversations.retrieve, + ) + self.update = to_streamed_response_wrapper( + conversations.update, + ) + self.delete = to_streamed_response_wrapper( + conversations.delete, + ) + + @cached_property + def items(self) -> ItemsWithStreamingResponse: + """Manage conversations and conversation items.""" + return ItemsWithStreamingResponse(self._conversations.items) + + +class AsyncConversationsWithStreamingResponse: + def __init__(self, conversations: AsyncConversations) -> None: + self._conversations = conversations + + self.create = async_to_streamed_response_wrapper( + conversations.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + conversations.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + conversations.update, + ) + self.delete = async_to_streamed_response_wrapper( + conversations.delete, + ) + + @cached_property + def items(self) -> AsyncItemsWithStreamingResponse: + """Manage conversations and conversation items.""" + return AsyncItemsWithStreamingResponse(self._conversations.items) diff --git a/src/openai/resources/conversations/items.py b/src/openai/resources/conversations/items.py new file mode 100644 index 0000000000..01cdfe0804 --- /dev/null +++ b/src/openai/resources/conversations/items.py @@ -0,0 +1,583 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Any, List, Iterable, cast +from typing_extensions import Literal + +import httpx + +from ... import _legacy_response +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ..._base_client import AsyncPaginator, make_request_options +from ...types.conversations import item_list_params, item_create_params, item_retrieve_params +from ...types.conversations.conversation import Conversation +from ...types.responses.response_includable import ResponseIncludable +from ...types.conversations.conversation_item import ConversationItem +from ...types.responses.response_input_item_param import ResponseInputItemParam +from ...types.conversations.conversation_item_list import ConversationItemList + +__all__ = ["Items", "AsyncItems"] + + +class Items(SyncAPIResource): + """Manage conversations and conversation items.""" + + @cached_property + def with_raw_response(self) -> ItemsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ItemsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ItemsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ItemsWithStreamingResponse(self) + + def create( + self, + conversation_id: str, + *, + items: Iterable[ResponseInputItemParam], + include: List[ResponseIncludable] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ConversationItemList: + """ + Create items in a conversation with the given ID. + + Args: + items: The items to add to the conversation. You may add up to 20 items at a time. + + include: Additional fields to include in the response. See the `include` parameter for + [listing Conversation items above](https://platform.openai.com/docs/api-reference/conversations/list-items#conversations_list_items-include) + for more information. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + return self._post( + path_template("/conversations/{conversation_id}/items", conversation_id=conversation_id), + body=maybe_transform({"items": items}, item_create_params.ItemCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"include": include}, item_create_params.ItemCreateParams), + security={"bearer_auth": True}, + ), + cast_to=ConversationItemList, + ) + + def retrieve( + self, + item_id: str, + *, + conversation_id: str, + include: List[ResponseIncludable] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ConversationItem: + """ + Get a single item from a conversation with the given IDs. + + Args: + include: Additional fields to include in the response. See the `include` parameter for + [listing Conversation items above](https://platform.openai.com/docs/api-reference/conversations/list-items#conversations_list_items-include) + for more information. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + if not item_id: + raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}") + return cast( + ConversationItem, + self._get( + path_template( + "/conversations/{conversation_id}/items/{item_id}", conversation_id=conversation_id, item_id=item_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"include": include}, item_retrieve_params.ItemRetrieveParams), + security={"bearer_auth": True}, + ), + cast_to=cast(Any, ConversationItem), # Union types cannot be passed in as arguments in the type system + ), + ) + + def list( + self, + conversation_id: str, + *, + after: str | Omit = omit, + include: List[ResponseIncludable] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[ConversationItem]: + """ + List all items for a conversation with the given ID. + + Args: + after: An item ID to list items after, used in pagination. + + include: Specify additional output data to include in the model response. Currently + supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + order: The order to return the input items in. Default is `desc`. + + - `asc`: Return the input items in ascending order. + - `desc`: Return the input items in descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + return self._get_api_list( + path_template("/conversations/{conversation_id}/items", conversation_id=conversation_id), + page=SyncConversationCursorPage[ConversationItem], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "include": include, + "limit": limit, + "order": order, + }, + item_list_params.ItemListParams, + ), + security={"bearer_auth": True}, + ), + model=cast(Any, ConversationItem), # Union types cannot be passed in as arguments in the type system + ) + + def delete( + self, + item_id: str, + *, + conversation_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Conversation: + """ + Delete an item from a conversation with the given IDs. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + if not item_id: + raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}") + return self._delete( + path_template( + "/conversations/{conversation_id}/items/{item_id}", conversation_id=conversation_id, item_id=item_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Conversation, + ) + + +class AsyncItems(AsyncAPIResource): + """Manage conversations and conversation items.""" + + @cached_property + def with_raw_response(self) -> AsyncItemsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncItemsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncItemsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncItemsWithStreamingResponse(self) + + async def create( + self, + conversation_id: str, + *, + items: Iterable[ResponseInputItemParam], + include: List[ResponseIncludable] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ConversationItemList: + """ + Create items in a conversation with the given ID. + + Args: + items: The items to add to the conversation. You may add up to 20 items at a time. + + include: Additional fields to include in the response. See the `include` parameter for + [listing Conversation items above](https://platform.openai.com/docs/api-reference/conversations/list-items#conversations_list_items-include) + for more information. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + return await self._post( + path_template("/conversations/{conversation_id}/items", conversation_id=conversation_id), + body=await async_maybe_transform({"items": items}, item_create_params.ItemCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"include": include}, item_create_params.ItemCreateParams), + security={"bearer_auth": True}, + ), + cast_to=ConversationItemList, + ) + + async def retrieve( + self, + item_id: str, + *, + conversation_id: str, + include: List[ResponseIncludable] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ConversationItem: + """ + Get a single item from a conversation with the given IDs. + + Args: + include: Additional fields to include in the response. See the `include` parameter for + [listing Conversation items above](https://platform.openai.com/docs/api-reference/conversations/list-items#conversations_list_items-include) + for more information. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + if not item_id: + raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}") + return cast( + ConversationItem, + await self._get( + path_template( + "/conversations/{conversation_id}/items/{item_id}", conversation_id=conversation_id, item_id=item_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"include": include}, item_retrieve_params.ItemRetrieveParams), + security={"bearer_auth": True}, + ), + cast_to=cast(Any, ConversationItem), # Union types cannot be passed in as arguments in the type system + ), + ) + + def list( + self, + conversation_id: str, + *, + after: str | Omit = omit, + include: List[ResponseIncludable] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ConversationItem, AsyncConversationCursorPage[ConversationItem]]: + """ + List all items for a conversation with the given ID. + + Args: + after: An item ID to list items after, used in pagination. + + include: Specify additional output data to include in the model response. Currently + supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + order: The order to return the input items in. Default is `desc`. + + - `asc`: Return the input items in ascending order. + - `desc`: Return the input items in descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + return self._get_api_list( + path_template("/conversations/{conversation_id}/items", conversation_id=conversation_id), + page=AsyncConversationCursorPage[ConversationItem], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "include": include, + "limit": limit, + "order": order, + }, + item_list_params.ItemListParams, + ), + security={"bearer_auth": True}, + ), + model=cast(Any, ConversationItem), # Union types cannot be passed in as arguments in the type system + ) + + async def delete( + self, + item_id: str, + *, + conversation_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Conversation: + """ + Delete an item from a conversation with the given IDs. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not conversation_id: + raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}") + if not item_id: + raise ValueError(f"Expected a non-empty value for `item_id` but received {item_id!r}") + return await self._delete( + path_template( + "/conversations/{conversation_id}/items/{item_id}", conversation_id=conversation_id, item_id=item_id + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Conversation, + ) + + +class ItemsWithRawResponse: + def __init__(self, items: Items) -> None: + self._items = items + + self.create = _legacy_response.to_raw_response_wrapper( + items.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + items.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + items.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + items.delete, + ) + + +class AsyncItemsWithRawResponse: + def __init__(self, items: AsyncItems) -> None: + self._items = items + + self.create = _legacy_response.async_to_raw_response_wrapper( + items.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + items.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + items.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + items.delete, + ) + + +class ItemsWithStreamingResponse: + def __init__(self, items: Items) -> None: + self._items = items + + self.create = to_streamed_response_wrapper( + items.create, + ) + self.retrieve = to_streamed_response_wrapper( + items.retrieve, + ) + self.list = to_streamed_response_wrapper( + items.list, + ) + self.delete = to_streamed_response_wrapper( + items.delete, + ) + + +class AsyncItemsWithStreamingResponse: + def __init__(self, items: AsyncItems) -> None: + self._items = items + + self.create = async_to_streamed_response_wrapper( + items.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + items.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + items.list, + ) + self.delete = async_to_streamed_response_wrapper( + items.delete, + ) diff --git a/src/openai/resources/embeddings.py b/src/openai/resources/embeddings.py index 71c2a18a24..a51936d809 100644 --- a/src/openai/resources/embeddings.py +++ b/src/openai/resources/embeddings.py @@ -2,33 +2,37 @@ from __future__ import annotations +import array import base64 -from typing import List, Union, Iterable, cast +from typing import Union, Iterable, cast from typing_extensions import Literal import httpx from .. import _legacy_response from ..types import embedding_create_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from .._utils import is_given, maybe_transform from .._compat import cached_property from .._extras import numpy as np, has_numpy from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from .._base_client import ( - make_request_options, -) +from .._base_client import make_request_options +from ..types.embedding_model import EmbeddingModel from ..types.create_embedding_response import CreateEmbeddingResponse __all__ = ["Embeddings", "AsyncEmbeddings"] class Embeddings(SyncAPIResource): + """ + Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + """ + @cached_property def with_raw_response(self) -> EmbeddingsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -47,17 +51,17 @@ def with_streaming_response(self) -> EmbeddingsWithStreamingResponse: def create( self, *, - input: Union[str, List[str], Iterable[int], Iterable[Iterable[int]]], - model: Union[str, Literal["text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large"]], - dimensions: int | NotGiven = NOT_GIVEN, - encoding_format: Literal["float", "base64"] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + input: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]]], + model: Union[str, EmbeddingModel], + dimensions: int | Omit = omit, + encoding_format: Literal["float", "base64"] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CreateEmbeddingResponse: """ Creates an embedding vector representing the input text. @@ -66,16 +70,18 @@ def create( input: Input text to embed, encoded as a string or array of tokens. To embed multiple inputs in a single request, pass an array of strings or array of token arrays. The input must not exceed the max input tokens for the model (8192 tokens for - `text-embedding-ada-002`), cannot be an empty string, and any array must be 2048 + all embedding models), cannot be an empty string, and any array must be 2048 dimensions or less. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) - for counting tokens. + for counting tokens. In addition to the per-input token limit, all embedding + models enforce a maximum of 300,000 tokens summed across all inputs in a single + request. model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. dimensions: The number of dimensions the resulting output embeddings should have. Only supported in `text-embedding-3` and later models. @@ -85,7 +91,7 @@ def create( user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -102,7 +108,7 @@ def create( "dimensions": dimensions, "encoding_format": encoding_format, } - if not is_given(encoding_format) and has_numpy(): + if not is_given(encoding_format): params["encoding_format"] = "base64" def parser(obj: CreateEmbeddingResponse) -> CreateEmbeddingResponse: @@ -110,15 +116,20 @@ def parser(obj: CreateEmbeddingResponse) -> CreateEmbeddingResponse: # don't modify the response object if a user explicitly asked for a format return obj + if not obj.data: + raise ValueError("No embedding data received") + for embedding in obj.data: data = cast(object, embedding.embedding) if not isinstance(data, str): - # numpy is not installed / base64 optimisation isn't enabled for this model yet continue - - embedding.embedding = np.frombuffer( # type: ignore[no-untyped-call] - base64.b64decode(data), dtype="float32" - ).tolist() + if not has_numpy(): + # use array for base64 optimisation + embedding.embedding = array.array("f", base64.b64decode(data)).tolist() + else: + embedding.embedding = np.frombuffer( # type: ignore[no-untyped-call] + base64.b64decode(data), dtype="float32" + ).tolist() return obj @@ -131,16 +142,21 @@ def parser(obj: CreateEmbeddingResponse) -> CreateEmbeddingResponse: extra_body=extra_body, timeout=timeout, post_parser=parser, + security={"bearer_auth": True}, ), cast_to=CreateEmbeddingResponse, ) class AsyncEmbeddings(AsyncAPIResource): + """ + Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + """ + @cached_property def with_raw_response(self) -> AsyncEmbeddingsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -159,17 +175,17 @@ def with_streaming_response(self) -> AsyncEmbeddingsWithStreamingResponse: async def create( self, *, - input: Union[str, List[str], Iterable[int], Iterable[Iterable[int]]], - model: Union[str, Literal["text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large"]], - dimensions: int | NotGiven = NOT_GIVEN, - encoding_format: Literal["float", "base64"] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + input: Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]]], + model: Union[str, EmbeddingModel], + dimensions: int | Omit = omit, + encoding_format: Literal["float", "base64"] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CreateEmbeddingResponse: """ Creates an embedding vector representing the input text. @@ -178,16 +194,18 @@ async def create( input: Input text to embed, encoded as a string or array of tokens. To embed multiple inputs in a single request, pass an array of strings or array of token arrays. The input must not exceed the max input tokens for the model (8192 tokens for - `text-embedding-ada-002`), cannot be an empty string, and any array must be 2048 + all embedding models), cannot be an empty string, and any array must be 2048 dimensions or less. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) - for counting tokens. + for counting tokens. In addition to the per-input token limit, all embedding + models enforce a maximum of 300,000 tokens summed across all inputs in a single + request. model: ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. dimensions: The number of dimensions the resulting output embeddings should have. Only supported in `text-embedding-3` and later models. @@ -197,7 +215,7 @@ async def create( user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -214,7 +232,7 @@ async def create( "dimensions": dimensions, "encoding_format": encoding_format, } - if not is_given(encoding_format) and has_numpy(): + if not is_given(encoding_format): params["encoding_format"] = "base64" def parser(obj: CreateEmbeddingResponse) -> CreateEmbeddingResponse: @@ -222,15 +240,20 @@ def parser(obj: CreateEmbeddingResponse) -> CreateEmbeddingResponse: # don't modify the response object if a user explicitly asked for a format return obj + if not obj.data: + raise ValueError("No embedding data received") + for embedding in obj.data: data = cast(object, embedding.embedding) if not isinstance(data, str): - # numpy is not installed / base64 optimisation isn't enabled for this model yet continue - - embedding.embedding = np.frombuffer( # type: ignore[no-untyped-call] - base64.b64decode(data), dtype="float32" - ).tolist() + if not has_numpy(): + # use array for base64 optimisation + embedding.embedding = array.array("f", base64.b64decode(data)).tolist() + else: + embedding.embedding = np.frombuffer( # type: ignore[no-untyped-call] + base64.b64decode(data), dtype="float32" + ).tolist() return obj @@ -243,6 +266,7 @@ def parser(obj: CreateEmbeddingResponse) -> CreateEmbeddingResponse: extra_body=extra_body, timeout=timeout, post_parser=parser, + security={"bearer_auth": True}, ), cast_to=CreateEmbeddingResponse, ) diff --git a/src/openai/resources/evals/__init__.py b/src/openai/resources/evals/__init__.py new file mode 100644 index 0000000000..84f707511d --- /dev/null +++ b/src/openai/resources/evals/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .runs import ( + Runs, + AsyncRuns, + RunsWithRawResponse, + AsyncRunsWithRawResponse, + RunsWithStreamingResponse, + AsyncRunsWithStreamingResponse, +) +from .evals import ( + Evals, + AsyncEvals, + EvalsWithRawResponse, + AsyncEvalsWithRawResponse, + EvalsWithStreamingResponse, + AsyncEvalsWithStreamingResponse, +) + +__all__ = [ + "Runs", + "AsyncRuns", + "RunsWithRawResponse", + "AsyncRunsWithRawResponse", + "RunsWithStreamingResponse", + "AsyncRunsWithStreamingResponse", + "Evals", + "AsyncEvals", + "EvalsWithRawResponse", + "AsyncEvalsWithRawResponse", + "EvalsWithStreamingResponse", + "AsyncEvalsWithStreamingResponse", +] diff --git a/src/openai/resources/evals/evals.py b/src/openai/resources/evals/evals.py new file mode 100644 index 0000000000..a23c9cdef2 --- /dev/null +++ b/src/openai/resources/evals/evals.py @@ -0,0 +1,706 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal + +import httpx + +from ... import _legacy_response +from ...types import eval_list_params, eval_create_params, eval_update_params +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from .runs.runs import ( + Runs, + AsyncRuns, + RunsWithRawResponse, + AsyncRunsWithRawResponse, + RunsWithStreamingResponse, + AsyncRunsWithStreamingResponse, +) +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncCursorPage, AsyncCursorPage +from ..._base_client import AsyncPaginator, make_request_options +from ...types.eval_list_response import EvalListResponse +from ...types.eval_create_response import EvalCreateResponse +from ...types.eval_delete_response import EvalDeleteResponse +from ...types.eval_update_response import EvalUpdateResponse +from ...types.eval_retrieve_response import EvalRetrieveResponse +from ...types.shared_params.metadata import Metadata + +__all__ = ["Evals", "AsyncEvals"] + + +class Evals(SyncAPIResource): + """Manage and run evals in the OpenAI platform.""" + + @cached_property + def runs(self) -> Runs: + """Manage and run evals in the OpenAI platform.""" + return Runs(self._client) + + @cached_property + def with_raw_response(self) -> EvalsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return EvalsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EvalsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return EvalsWithStreamingResponse(self) + + def create( + self, + *, + data_source_config: eval_create_params.DataSourceConfig, + testing_criteria: Iterable[eval_create_params.TestingCriterion], + metadata: Optional[Metadata] | Omit = omit, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EvalCreateResponse: + """ + Create the structure of an evaluation that can be used to test a model's + performance. An evaluation is a set of testing criteria and the config for a + data source, which dictates the schema of the data used in the evaluation. After + creating an evaluation, you can run it on different models and model parameters. + We support several types of graders and datasources. For more information, see + the [Evals guide](https://platform.openai.com/docs/guides/evals). + + Args: + data_source_config: The configuration for the data source used for the evaluation runs. Dictates the + schema of the data used in the evaluation. + + testing_criteria: A list of graders for all eval runs in this group. Graders can reference + variables in the data source using double curly braces notation, like + `{{item.variable_name}}`. To reference the model's output, use the `sample` + namespace (ie, `{{sample.output_text}}`). + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + name: The name of the evaluation. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/evals", + body=maybe_transform( + { + "data_source_config": data_source_config, + "testing_criteria": testing_criteria, + "metadata": metadata, + "name": name, + }, + eval_create_params.EvalCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=EvalCreateResponse, + ) + + def retrieve( + self, + eval_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EvalRetrieveResponse: + """ + Get an evaluation by ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + return self._get( + path_template("/evals/{eval_id}", eval_id=eval_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=EvalRetrieveResponse, + ) + + def update( + self, + eval_id: str, + *, + metadata: Optional[Metadata] | Omit = omit, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EvalUpdateResponse: + """ + Update certain properties of an evaluation. + + Args: + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + name: Rename the evaluation. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + return self._post( + path_template("/evals/{eval_id}", eval_id=eval_id), + body=maybe_transform( + { + "metadata": metadata, + "name": name, + }, + eval_update_params.EvalUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=EvalUpdateResponse, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + order_by: Literal["created_at", "updated_at"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[EvalListResponse]: + """ + List evaluations for a project. + + Args: + after: Identifier for the last eval from the previous pagination request. + + limit: Number of evals to retrieve. + + order: Sort order for evals by timestamp. Use `asc` for ascending order or `desc` for + descending order. + + order_by: Evals can be ordered by creation time or last updated time. Use `created_at` for + creation time or `updated_at` for last updated time. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/evals", + page=SyncCursorPage[EvalListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "order_by": order_by, + }, + eval_list_params.EvalListParams, + ), + security={"bearer_auth": True}, + ), + model=EvalListResponse, + ) + + def delete( + self, + eval_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EvalDeleteResponse: + """ + Delete an evaluation. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + return self._delete( + path_template("/evals/{eval_id}", eval_id=eval_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=EvalDeleteResponse, + ) + + +class AsyncEvals(AsyncAPIResource): + """Manage and run evals in the OpenAI platform.""" + + @cached_property + def runs(self) -> AsyncRuns: + """Manage and run evals in the OpenAI platform.""" + return AsyncRuns(self._client) + + @cached_property + def with_raw_response(self) -> AsyncEvalsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncEvalsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEvalsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncEvalsWithStreamingResponse(self) + + async def create( + self, + *, + data_source_config: eval_create_params.DataSourceConfig, + testing_criteria: Iterable[eval_create_params.TestingCriterion], + metadata: Optional[Metadata] | Omit = omit, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EvalCreateResponse: + """ + Create the structure of an evaluation that can be used to test a model's + performance. An evaluation is a set of testing criteria and the config for a + data source, which dictates the schema of the data used in the evaluation. After + creating an evaluation, you can run it on different models and model parameters. + We support several types of graders and datasources. For more information, see + the [Evals guide](https://platform.openai.com/docs/guides/evals). + + Args: + data_source_config: The configuration for the data source used for the evaluation runs. Dictates the + schema of the data used in the evaluation. + + testing_criteria: A list of graders for all eval runs in this group. Graders can reference + variables in the data source using double curly braces notation, like + `{{item.variable_name}}`. To reference the model's output, use the `sample` + namespace (ie, `{{sample.output_text}}`). + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + name: The name of the evaluation. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/evals", + body=await async_maybe_transform( + { + "data_source_config": data_source_config, + "testing_criteria": testing_criteria, + "metadata": metadata, + "name": name, + }, + eval_create_params.EvalCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=EvalCreateResponse, + ) + + async def retrieve( + self, + eval_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EvalRetrieveResponse: + """ + Get an evaluation by ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + return await self._get( + path_template("/evals/{eval_id}", eval_id=eval_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=EvalRetrieveResponse, + ) + + async def update( + self, + eval_id: str, + *, + metadata: Optional[Metadata] | Omit = omit, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EvalUpdateResponse: + """ + Update certain properties of an evaluation. + + Args: + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + name: Rename the evaluation. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + return await self._post( + path_template("/evals/{eval_id}", eval_id=eval_id), + body=await async_maybe_transform( + { + "metadata": metadata, + "name": name, + }, + eval_update_params.EvalUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=EvalUpdateResponse, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + order_by: Literal["created_at", "updated_at"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[EvalListResponse, AsyncCursorPage[EvalListResponse]]: + """ + List evaluations for a project. + + Args: + after: Identifier for the last eval from the previous pagination request. + + limit: Number of evals to retrieve. + + order: Sort order for evals by timestamp. Use `asc` for ascending order or `desc` for + descending order. + + order_by: Evals can be ordered by creation time or last updated time. Use `created_at` for + creation time or `updated_at` for last updated time. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/evals", + page=AsyncCursorPage[EvalListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "order_by": order_by, + }, + eval_list_params.EvalListParams, + ), + security={"bearer_auth": True}, + ), + model=EvalListResponse, + ) + + async def delete( + self, + eval_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EvalDeleteResponse: + """ + Delete an evaluation. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + return await self._delete( + path_template("/evals/{eval_id}", eval_id=eval_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=EvalDeleteResponse, + ) + + +class EvalsWithRawResponse: + def __init__(self, evals: Evals) -> None: + self._evals = evals + + self.create = _legacy_response.to_raw_response_wrapper( + evals.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + evals.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + evals.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + evals.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + evals.delete, + ) + + @cached_property + def runs(self) -> RunsWithRawResponse: + """Manage and run evals in the OpenAI platform.""" + return RunsWithRawResponse(self._evals.runs) + + +class AsyncEvalsWithRawResponse: + def __init__(self, evals: AsyncEvals) -> None: + self._evals = evals + + self.create = _legacy_response.async_to_raw_response_wrapper( + evals.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + evals.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + evals.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + evals.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + evals.delete, + ) + + @cached_property + def runs(self) -> AsyncRunsWithRawResponse: + """Manage and run evals in the OpenAI platform.""" + return AsyncRunsWithRawResponse(self._evals.runs) + + +class EvalsWithStreamingResponse: + def __init__(self, evals: Evals) -> None: + self._evals = evals + + self.create = to_streamed_response_wrapper( + evals.create, + ) + self.retrieve = to_streamed_response_wrapper( + evals.retrieve, + ) + self.update = to_streamed_response_wrapper( + evals.update, + ) + self.list = to_streamed_response_wrapper( + evals.list, + ) + self.delete = to_streamed_response_wrapper( + evals.delete, + ) + + @cached_property + def runs(self) -> RunsWithStreamingResponse: + """Manage and run evals in the OpenAI platform.""" + return RunsWithStreamingResponse(self._evals.runs) + + +class AsyncEvalsWithStreamingResponse: + def __init__(self, evals: AsyncEvals) -> None: + self._evals = evals + + self.create = async_to_streamed_response_wrapper( + evals.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + evals.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + evals.update, + ) + self.list = async_to_streamed_response_wrapper( + evals.list, + ) + self.delete = async_to_streamed_response_wrapper( + evals.delete, + ) + + @cached_property + def runs(self) -> AsyncRunsWithStreamingResponse: + """Manage and run evals in the OpenAI platform.""" + return AsyncRunsWithStreamingResponse(self._evals.runs) diff --git a/src/openai/resources/evals/runs/__init__.py b/src/openai/resources/evals/runs/__init__.py new file mode 100644 index 0000000000..d189f16fb7 --- /dev/null +++ b/src/openai/resources/evals/runs/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .runs import ( + Runs, + AsyncRuns, + RunsWithRawResponse, + AsyncRunsWithRawResponse, + RunsWithStreamingResponse, + AsyncRunsWithStreamingResponse, +) +from .output_items import ( + OutputItems, + AsyncOutputItems, + OutputItemsWithRawResponse, + AsyncOutputItemsWithRawResponse, + OutputItemsWithStreamingResponse, + AsyncOutputItemsWithStreamingResponse, +) + +__all__ = [ + "OutputItems", + "AsyncOutputItems", + "OutputItemsWithRawResponse", + "AsyncOutputItemsWithRawResponse", + "OutputItemsWithStreamingResponse", + "AsyncOutputItemsWithStreamingResponse", + "Runs", + "AsyncRuns", + "RunsWithRawResponse", + "AsyncRunsWithRawResponse", + "RunsWithStreamingResponse", + "AsyncRunsWithStreamingResponse", +] diff --git a/src/openai/resources/evals/runs/output_items.py b/src/openai/resources/evals/runs/output_items.py new file mode 100644 index 0000000000..2f884dd876 --- /dev/null +++ b/src/openai/resources/evals/runs/output_items.py @@ -0,0 +1,339 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncCursorPage, AsyncCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.evals.runs import output_item_list_params +from ....types.evals.runs.output_item_list_response import OutputItemListResponse +from ....types.evals.runs.output_item_retrieve_response import OutputItemRetrieveResponse + +__all__ = ["OutputItems", "AsyncOutputItems"] + + +class OutputItems(SyncAPIResource): + """Manage and run evals in the OpenAI platform.""" + + @cached_property + def with_raw_response(self) -> OutputItemsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return OutputItemsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> OutputItemsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return OutputItemsWithStreamingResponse(self) + + def retrieve( + self, + output_item_id: str, + *, + eval_id: str, + run_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OutputItemRetrieveResponse: + """ + Get an evaluation run output item by ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + if not output_item_id: + raise ValueError(f"Expected a non-empty value for `output_item_id` but received {output_item_id!r}") + return self._get( + path_template( + "/evals/{eval_id}/runs/{run_id}/output_items/{output_item_id}", + eval_id=eval_id, + run_id=run_id, + output_item_id=output_item_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=OutputItemRetrieveResponse, + ) + + def list( + self, + run_id: str, + *, + eval_id: str, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + status: Literal["fail", "pass"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[OutputItemListResponse]: + """ + Get a list of output items for an evaluation run. + + Args: + after: Identifier for the last output item from the previous pagination request. + + limit: Number of output items to retrieve. + + order: Sort order for output items by timestamp. Use `asc` for ascending order or + `desc` for descending order. Defaults to `asc`. + + status: Filter output items by status. Use `failed` to filter by failed output items or + `pass` to filter by passed output items. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return self._get_api_list( + path_template("/evals/{eval_id}/runs/{run_id}/output_items", eval_id=eval_id, run_id=run_id), + page=SyncCursorPage[OutputItemListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "status": status, + }, + output_item_list_params.OutputItemListParams, + ), + security={"bearer_auth": True}, + ), + model=OutputItemListResponse, + ) + + +class AsyncOutputItems(AsyncAPIResource): + """Manage and run evals in the OpenAI platform.""" + + @cached_property + def with_raw_response(self) -> AsyncOutputItemsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncOutputItemsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncOutputItemsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncOutputItemsWithStreamingResponse(self) + + async def retrieve( + self, + output_item_id: str, + *, + eval_id: str, + run_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> OutputItemRetrieveResponse: + """ + Get an evaluation run output item by ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + if not output_item_id: + raise ValueError(f"Expected a non-empty value for `output_item_id` but received {output_item_id!r}") + return await self._get( + path_template( + "/evals/{eval_id}/runs/{run_id}/output_items/{output_item_id}", + eval_id=eval_id, + run_id=run_id, + output_item_id=output_item_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=OutputItemRetrieveResponse, + ) + + def list( + self, + run_id: str, + *, + eval_id: str, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + status: Literal["fail", "pass"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[OutputItemListResponse, AsyncCursorPage[OutputItemListResponse]]: + """ + Get a list of output items for an evaluation run. + + Args: + after: Identifier for the last output item from the previous pagination request. + + limit: Number of output items to retrieve. + + order: Sort order for output items by timestamp. Use `asc` for ascending order or + `desc` for descending order. Defaults to `asc`. + + status: Filter output items by status. Use `failed` to filter by failed output items or + `pass` to filter by passed output items. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return self._get_api_list( + path_template("/evals/{eval_id}/runs/{run_id}/output_items", eval_id=eval_id, run_id=run_id), + page=AsyncCursorPage[OutputItemListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "status": status, + }, + output_item_list_params.OutputItemListParams, + ), + security={"bearer_auth": True}, + ), + model=OutputItemListResponse, + ) + + +class OutputItemsWithRawResponse: + def __init__(self, output_items: OutputItems) -> None: + self._output_items = output_items + + self.retrieve = _legacy_response.to_raw_response_wrapper( + output_items.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + output_items.list, + ) + + +class AsyncOutputItemsWithRawResponse: + def __init__(self, output_items: AsyncOutputItems) -> None: + self._output_items = output_items + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + output_items.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + output_items.list, + ) + + +class OutputItemsWithStreamingResponse: + def __init__(self, output_items: OutputItems) -> None: + self._output_items = output_items + + self.retrieve = to_streamed_response_wrapper( + output_items.retrieve, + ) + self.list = to_streamed_response_wrapper( + output_items.list, + ) + + +class AsyncOutputItemsWithStreamingResponse: + def __init__(self, output_items: AsyncOutputItems) -> None: + self._output_items = output_items + + self.retrieve = async_to_streamed_response_wrapper( + output_items.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + output_items.list, + ) diff --git a/src/openai/resources/evals/runs/runs.py b/src/openai/resources/evals/runs/runs.py new file mode 100644 index 0000000000..ffba38db2e --- /dev/null +++ b/src/openai/resources/evals/runs/runs.py @@ -0,0 +1,678 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .output_items import ( + OutputItems, + AsyncOutputItems, + OutputItemsWithRawResponse, + AsyncOutputItemsWithRawResponse, + OutputItemsWithStreamingResponse, + AsyncOutputItemsWithStreamingResponse, +) +from ....pagination import SyncCursorPage, AsyncCursorPage +from ....types.evals import run_list_params, run_create_params +from ...._base_client import AsyncPaginator, make_request_options +from ....types.shared_params.metadata import Metadata +from ....types.evals.run_list_response import RunListResponse +from ....types.evals.run_cancel_response import RunCancelResponse +from ....types.evals.run_create_response import RunCreateResponse +from ....types.evals.run_delete_response import RunDeleteResponse +from ....types.evals.run_retrieve_response import RunRetrieveResponse + +__all__ = ["Runs", "AsyncRuns"] + + +class Runs(SyncAPIResource): + """Manage and run evals in the OpenAI platform.""" + + @cached_property + def output_items(self) -> OutputItems: + """Manage and run evals in the OpenAI platform.""" + return OutputItems(self._client) + + @cached_property + def with_raw_response(self) -> RunsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return RunsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RunsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return RunsWithStreamingResponse(self) + + def create( + self, + eval_id: str, + *, + data_source: run_create_params.DataSource, + metadata: Optional[Metadata] | Omit = omit, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RunCreateResponse: + """ + Kicks off a new run for a given evaluation, specifying the data source, and what + model configuration to use to test. The datasource will be validated against the + schema specified in the config of the evaluation. + + Args: + data_source: Details about the run's data source. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + name: The name of the run. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + return self._post( + path_template("/evals/{eval_id}/runs", eval_id=eval_id), + body=maybe_transform( + { + "data_source": data_source, + "metadata": metadata, + "name": name, + }, + run_create_params.RunCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=RunCreateResponse, + ) + + def retrieve( + self, + run_id: str, + *, + eval_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RunRetrieveResponse: + """ + Get an evaluation run by ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return self._get( + path_template("/evals/{eval_id}/runs/{run_id}", eval_id=eval_id, run_id=run_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=RunRetrieveResponse, + ) + + def list( + self, + eval_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + status: Literal["queued", "in_progress", "completed", "canceled", "failed"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[RunListResponse]: + """ + Get a list of runs for an evaluation. + + Args: + after: Identifier for the last run from the previous pagination request. + + limit: Number of runs to retrieve. + + order: Sort order for runs by timestamp. Use `asc` for ascending order or `desc` for + descending order. Defaults to `asc`. + + status: Filter runs by status. One of `queued` | `in_progress` | `failed` | `completed` + | `canceled`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + return self._get_api_list( + path_template("/evals/{eval_id}/runs", eval_id=eval_id), + page=SyncCursorPage[RunListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "status": status, + }, + run_list_params.RunListParams, + ), + security={"bearer_auth": True}, + ), + model=RunListResponse, + ) + + def delete( + self, + run_id: str, + *, + eval_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RunDeleteResponse: + """ + Delete an eval run. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return self._delete( + path_template("/evals/{eval_id}/runs/{run_id}", eval_id=eval_id, run_id=run_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=RunDeleteResponse, + ) + + def cancel( + self, + run_id: str, + *, + eval_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RunCancelResponse: + """ + Cancel an ongoing evaluation run. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return self._post( + path_template("/evals/{eval_id}/runs/{run_id}", eval_id=eval_id, run_id=run_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=RunCancelResponse, + ) + + +class AsyncRuns(AsyncAPIResource): + """Manage and run evals in the OpenAI platform.""" + + @cached_property + def output_items(self) -> AsyncOutputItems: + """Manage and run evals in the OpenAI platform.""" + return AsyncOutputItems(self._client) + + @cached_property + def with_raw_response(self) -> AsyncRunsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncRunsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRunsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncRunsWithStreamingResponse(self) + + async def create( + self, + eval_id: str, + *, + data_source: run_create_params.DataSource, + metadata: Optional[Metadata] | Omit = omit, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RunCreateResponse: + """ + Kicks off a new run for a given evaluation, specifying the data source, and what + model configuration to use to test. The datasource will be validated against the + schema specified in the config of the evaluation. + + Args: + data_source: Details about the run's data source. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + name: The name of the run. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + return await self._post( + path_template("/evals/{eval_id}/runs", eval_id=eval_id), + body=await async_maybe_transform( + { + "data_source": data_source, + "metadata": metadata, + "name": name, + }, + run_create_params.RunCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=RunCreateResponse, + ) + + async def retrieve( + self, + run_id: str, + *, + eval_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RunRetrieveResponse: + """ + Get an evaluation run by ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return await self._get( + path_template("/evals/{eval_id}/runs/{run_id}", eval_id=eval_id, run_id=run_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=RunRetrieveResponse, + ) + + def list( + self, + eval_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + status: Literal["queued", "in_progress", "completed", "canceled", "failed"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[RunListResponse, AsyncCursorPage[RunListResponse]]: + """ + Get a list of runs for an evaluation. + + Args: + after: Identifier for the last run from the previous pagination request. + + limit: Number of runs to retrieve. + + order: Sort order for runs by timestamp. Use `asc` for ascending order or `desc` for + descending order. Defaults to `asc`. + + status: Filter runs by status. One of `queued` | `in_progress` | `failed` | `completed` + | `canceled`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + return self._get_api_list( + path_template("/evals/{eval_id}/runs", eval_id=eval_id), + page=AsyncCursorPage[RunListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "status": status, + }, + run_list_params.RunListParams, + ), + security={"bearer_auth": True}, + ), + model=RunListResponse, + ) + + async def delete( + self, + run_id: str, + *, + eval_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RunDeleteResponse: + """ + Delete an eval run. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return await self._delete( + path_template("/evals/{eval_id}/runs/{run_id}", eval_id=eval_id, run_id=run_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=RunDeleteResponse, + ) + + async def cancel( + self, + run_id: str, + *, + eval_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RunCancelResponse: + """ + Cancel an ongoing evaluation run. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not eval_id: + raise ValueError(f"Expected a non-empty value for `eval_id` but received {eval_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return await self._post( + path_template("/evals/{eval_id}/runs/{run_id}", eval_id=eval_id, run_id=run_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=RunCancelResponse, + ) + + +class RunsWithRawResponse: + def __init__(self, runs: Runs) -> None: + self._runs = runs + + self.create = _legacy_response.to_raw_response_wrapper( + runs.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + runs.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + runs.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + runs.delete, + ) + self.cancel = _legacy_response.to_raw_response_wrapper( + runs.cancel, + ) + + @cached_property + def output_items(self) -> OutputItemsWithRawResponse: + """Manage and run evals in the OpenAI platform.""" + return OutputItemsWithRawResponse(self._runs.output_items) + + +class AsyncRunsWithRawResponse: + def __init__(self, runs: AsyncRuns) -> None: + self._runs = runs + + self.create = _legacy_response.async_to_raw_response_wrapper( + runs.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + runs.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + runs.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + runs.delete, + ) + self.cancel = _legacy_response.async_to_raw_response_wrapper( + runs.cancel, + ) + + @cached_property + def output_items(self) -> AsyncOutputItemsWithRawResponse: + """Manage and run evals in the OpenAI platform.""" + return AsyncOutputItemsWithRawResponse(self._runs.output_items) + + +class RunsWithStreamingResponse: + def __init__(self, runs: Runs) -> None: + self._runs = runs + + self.create = to_streamed_response_wrapper( + runs.create, + ) + self.retrieve = to_streamed_response_wrapper( + runs.retrieve, + ) + self.list = to_streamed_response_wrapper( + runs.list, + ) + self.delete = to_streamed_response_wrapper( + runs.delete, + ) + self.cancel = to_streamed_response_wrapper( + runs.cancel, + ) + + @cached_property + def output_items(self) -> OutputItemsWithStreamingResponse: + """Manage and run evals in the OpenAI platform.""" + return OutputItemsWithStreamingResponse(self._runs.output_items) + + +class AsyncRunsWithStreamingResponse: + def __init__(self, runs: AsyncRuns) -> None: + self._runs = runs + + self.create = async_to_streamed_response_wrapper( + runs.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + runs.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + runs.list, + ) + self.delete = async_to_streamed_response_wrapper( + runs.delete, + ) + self.cancel = async_to_streamed_response_wrapper( + runs.cancel, + ) + + @cached_property + def output_items(self) -> AsyncOutputItemsWithStreamingResponse: + """Manage and run evals in the OpenAI platform.""" + return AsyncOutputItemsWithStreamingResponse(self._runs.output_items) diff --git a/src/openai/resources/files.py b/src/openai/resources/files.py index e24eeec711..868742f0f0 100644 --- a/src/openai/resources/files.py +++ b/src/openai/resources/files.py @@ -5,18 +5,15 @@ import time import typing_extensions from typing import Mapping, cast +from typing_extensions import Literal import httpx from .. import _legacy_response from ..types import FilePurpose, file_list_params, file_create_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from .._utils import ( - extract_files, - maybe_transform, - deepcopy_minimal, - async_maybe_transform, -) +from .._files import deepcopy_with_paths +from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given +from .._utils import extract_files, path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -27,11 +24,8 @@ to_custom_streamed_response_wrapper, async_to_custom_streamed_response_wrapper, ) -from ..pagination import SyncPage, AsyncPage -from .._base_client import ( - AsyncPaginator, - make_request_options, -) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options from ..types.file_object import FileObject from ..types.file_deleted import FileDeleted from ..types.file_purpose import FilePurpose @@ -40,10 +34,14 @@ class Files(SyncAPIResource): + """ + Files are used to upload documents that can be used with features like Assistants and Fine-tuning. + """ + @cached_property def with_raw_response(self) -> FilesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -64,33 +62,40 @@ def create( *, file: FileTypes, purpose: FilePurpose, + expires_after: file_create_params.ExpiresAfter | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FileObject: """Upload a file that can be used across various endpoints. Individual files can be - up to 512 MB, and the size of all files uploaded by one organization can be up - to 100 GB. - - The Assistants API supports files up to 2 million tokens and of specific file - types. See the - [Assistants Tools guide](https://platform.openai.com/docs/assistants/tools) for - details. - - The Fine-tuning API only supports `.jsonl` files. The input also has certain - required formats for fine-tuning - [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input) or - [completions](https://platform.openai.com/docs/api-reference/fine-tuning/completions-input) - models. - - The Batch API only supports `.jsonl` files up to 100 MB in size. The input also - has a specific required - [format](https://platform.openai.com/docs/api-reference/batch/request-input). + up to 512 MB, and each project can store up to 2.5 TB of files in total. There + is no organization-wide storage limit. Uploads to this endpoint are rate-limited + to 1,000 requests per minute per authenticated user. + + - The Assistants API supports files up to 2 million tokens and of specific file + types. See the + [Assistants Tools guide](https://platform.openai.com/docs/assistants/tools) + for details. + - The Fine-tuning API only supports `.jsonl` files. The input also has certain + required formats for fine-tuning + [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input) + or + [completions](https://platform.openai.com/docs/api-reference/fine-tuning/completions-input) + models. + - The Batch API only supports `.jsonl` files up to 200 MB in size. The input + also has a specific required + [format](https://platform.openai.com/docs/api-reference/batch/request-input). + - For Retrieval or `file_search` ingestion, upload files here first. If you need + to attach multiple uploaded files to the same vector store, use + [`/vector_stores/{vector_store_id}/file_batches`](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/createBatch) + instead of attaching them one by one. Vector store attachment has separate + limits from file upload, including 2,000 attached files per minute per + organization. Please [contact us](https://help.openai.com/) if you need to increase these storage limits. @@ -98,14 +103,18 @@ def create( Args: file: The File object (not file name) to be uploaded. - purpose: The intended purpose of the uploaded file. + purpose: + The intended purpose of the uploaded file. One of: - Use "assistants" for - [Assistants](https://platform.openai.com/docs/api-reference/assistants) and - [Message](https://platform.openai.com/docs/api-reference/messages) files, - "vision" for Assistants image file inputs, "batch" for - [Batch API](https://platform.openai.com/docs/guides/batch), and "fine-tune" for - [Fine-tuning](https://platform.openai.com/docs/api-reference/fine-tuning). + - `assistants`: Used in the Assistants API + - `batch`: Used in the Batch API + - `fine-tune`: Used for fine-tuning + - `vision`: Images used for vision fine-tuning + - `user_data`: Flexible file type for any purpose + - `evals`: Used for eval data sets + + expires_after: The expiration policy for a file. By default, files with `purpose=batch` expire + after 30 days and all other files are persisted until they are manually deleted. extra_headers: Send extra headers @@ -115,11 +124,13 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + body = deepcopy_with_paths( { "file": file, "purpose": purpose, - } + "expires_after": expires_after, + }, + [["file"]], ) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be @@ -131,7 +142,11 @@ def create( body=maybe_transform(body, file_create_params.FileCreateParams), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FileObject, ) @@ -145,7 +160,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FileObject: """ Returns information about a specific file. @@ -162,9 +177,13 @@ def retrieve( if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return self._get( - f"/files/{file_id}", + path_template("/files/{file_id}", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FileObject, ) @@ -172,18 +191,33 @@ def retrieve( def list( self, *, - purpose: str | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + purpose: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncPage[FileObject]: - """ - Returns a list of files that belong to the user's organization. + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[FileObject]: + """Returns a list of files. Args: + after: A cursor for use in pagination. + + `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 10,000, and the default is 10,000. + + order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending + order and `desc` for descending order. + purpose: Only return files with the given purpose. extra_headers: Send extra headers @@ -196,13 +230,22 @@ def list( """ return self._get_api_list( "/files", - page=SyncPage[FileObject], + page=SyncCursorPage[FileObject], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"purpose": purpose}, file_list_params.FileListParams), + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "purpose": purpose, + }, + file_list_params.FileListParams, + ), + security={"bearer_auth": True}, ), model=FileObject, ) @@ -216,10 +259,10 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FileDeleted: """ - Delete a file. + Delete a file and remove it from all vector stores. Args: extra_headers: Send extra headers @@ -233,9 +276,13 @@ def delete( if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return self._delete( - f"/files/{file_id}", + path_template("/files/{file_id}", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FileDeleted, ) @@ -249,7 +296,7 @@ def content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Returns the contents of the specified file. @@ -267,9 +314,13 @@ def content( raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") extra_headers = {"Accept": "application/binary", **(extra_headers or {})} return self._get( - f"/files/{file_id}/content", + path_template("/files/{file_id}/content", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=_legacy_response.HttpxBinaryResponseContent, ) @@ -284,7 +335,7 @@ def retrieve_content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> str: """ Returns the contents of the specified file. @@ -301,9 +352,13 @@ def retrieve_content( if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return self._get( - f"/files/{file_id}/content", + path_template("/files/{file_id}/content", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=str, ) @@ -333,10 +388,14 @@ def wait_for_processing( class AsyncFiles(AsyncAPIResource): + """ + Files are used to upload documents that can be used with features like Assistants and Fine-tuning. + """ + @cached_property def with_raw_response(self) -> AsyncFilesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -357,33 +416,40 @@ async def create( *, file: FileTypes, purpose: FilePurpose, + expires_after: file_create_params.ExpiresAfter | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FileObject: """Upload a file that can be used across various endpoints. Individual files can be - up to 512 MB, and the size of all files uploaded by one organization can be up - to 100 GB. - - The Assistants API supports files up to 2 million tokens and of specific file - types. See the - [Assistants Tools guide](https://platform.openai.com/docs/assistants/tools) for - details. - - The Fine-tuning API only supports `.jsonl` files. The input also has certain - required formats for fine-tuning - [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input) or - [completions](https://platform.openai.com/docs/api-reference/fine-tuning/completions-input) - models. - - The Batch API only supports `.jsonl` files up to 100 MB in size. The input also - has a specific required - [format](https://platform.openai.com/docs/api-reference/batch/request-input). + up to 512 MB, and each project can store up to 2.5 TB of files in total. There + is no organization-wide storage limit. Uploads to this endpoint are rate-limited + to 1,000 requests per minute per authenticated user. + + - The Assistants API supports files up to 2 million tokens and of specific file + types. See the + [Assistants Tools guide](https://platform.openai.com/docs/assistants/tools) + for details. + - The Fine-tuning API only supports `.jsonl` files. The input also has certain + required formats for fine-tuning + [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input) + or + [completions](https://platform.openai.com/docs/api-reference/fine-tuning/completions-input) + models. + - The Batch API only supports `.jsonl` files up to 200 MB in size. The input + also has a specific required + [format](https://platform.openai.com/docs/api-reference/batch/request-input). + - For Retrieval or `file_search` ingestion, upload files here first. If you need + to attach multiple uploaded files to the same vector store, use + [`/vector_stores/{vector_store_id}/file_batches`](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/createBatch) + instead of attaching them one by one. Vector store attachment has separate + limits from file upload, including 2,000 attached files per minute per + organization. Please [contact us](https://help.openai.com/) if you need to increase these storage limits. @@ -391,14 +457,18 @@ async def create( Args: file: The File object (not file name) to be uploaded. - purpose: The intended purpose of the uploaded file. + purpose: + The intended purpose of the uploaded file. One of: + + - `assistants`: Used in the Assistants API + - `batch`: Used in the Batch API + - `fine-tune`: Used for fine-tuning + - `vision`: Images used for vision fine-tuning + - `user_data`: Flexible file type for any purpose + - `evals`: Used for eval data sets - Use "assistants" for - [Assistants](https://platform.openai.com/docs/api-reference/assistants) and - [Message](https://platform.openai.com/docs/api-reference/messages) files, - "vision" for Assistants image file inputs, "batch" for - [Batch API](https://platform.openai.com/docs/guides/batch), and "fine-tune" for - [Fine-tuning](https://platform.openai.com/docs/api-reference/fine-tuning). + expires_after: The expiration policy for a file. By default, files with `purpose=batch` expire + after 30 days and all other files are persisted until they are manually deleted. extra_headers: Send extra headers @@ -408,11 +478,13 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + body = deepcopy_with_paths( { "file": file, "purpose": purpose, - } + "expires_after": expires_after, + }, + [["file"]], ) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be @@ -424,7 +496,11 @@ async def create( body=await async_maybe_transform(body, file_create_params.FileCreateParams), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FileObject, ) @@ -438,7 +514,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FileObject: """ Returns information about a specific file. @@ -455,9 +531,13 @@ async def retrieve( if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return await self._get( - f"/files/{file_id}", + path_template("/files/{file_id}", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FileObject, ) @@ -465,18 +545,33 @@ async def retrieve( def list( self, *, - purpose: str | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + purpose: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[FileObject, AsyncPage[FileObject]]: - """ - Returns a list of files that belong to the user's organization. + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[FileObject, AsyncCursorPage[FileObject]]: + """Returns a list of files. Args: + after: A cursor for use in pagination. + + `after` is an object ID that defines your place + in the list. For instance, if you make a list request and receive 100 objects, + ending with obj_foo, your subsequent call can include after=obj_foo in order to + fetch the next page of the list. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 10,000, and the default is 10,000. + + order: Sort order by the `created_at` timestamp of the objects. `asc` for ascending + order and `desc` for descending order. + purpose: Only return files with the given purpose. extra_headers: Send extra headers @@ -489,13 +584,22 @@ def list( """ return self._get_api_list( "/files", - page=AsyncPage[FileObject], + page=AsyncCursorPage[FileObject], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"purpose": purpose}, file_list_params.FileListParams), + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "purpose": purpose, + }, + file_list_params.FileListParams, + ), + security={"bearer_auth": True}, ), model=FileObject, ) @@ -509,10 +613,10 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FileDeleted: """ - Delete a file. + Delete a file and remove it from all vector stores. Args: extra_headers: Send extra headers @@ -526,9 +630,13 @@ async def delete( if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return await self._delete( - f"/files/{file_id}", + path_template("/files/{file_id}", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FileDeleted, ) @@ -542,7 +650,7 @@ async def content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> _legacy_response.HttpxBinaryResponseContent: """ Returns the contents of the specified file. @@ -560,9 +668,13 @@ async def content( raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") extra_headers = {"Accept": "application/binary", **(extra_headers or {})} return await self._get( - f"/files/{file_id}/content", + path_template("/files/{file_id}/content", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=_legacy_response.HttpxBinaryResponseContent, ) @@ -577,7 +689,7 @@ async def retrieve_content( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> str: """ Returns the contents of the specified file. @@ -594,9 +706,13 @@ async def retrieve_content( if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return await self._get( - f"/files/{file_id}/content", + path_template("/files/{file_id}/content", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=str, ) @@ -646,7 +762,7 @@ def __init__(self, files: Files) -> None: ) self.retrieve_content = ( # pyright: ignore[reportDeprecated] _legacy_response.to_raw_response_wrapper( - files.retrieve_content # pyright: ignore[reportDeprecated], + files.retrieve_content, # pyright: ignore[reportDeprecated], ) ) @@ -672,7 +788,7 @@ def __init__(self, files: AsyncFiles) -> None: ) self.retrieve_content = ( # pyright: ignore[reportDeprecated] _legacy_response.async_to_raw_response_wrapper( - files.retrieve_content # pyright: ignore[reportDeprecated], + files.retrieve_content, # pyright: ignore[reportDeprecated], ) ) @@ -699,7 +815,7 @@ def __init__(self, files: Files) -> None: ) self.retrieve_content = ( # pyright: ignore[reportDeprecated] to_streamed_response_wrapper( - files.retrieve_content # pyright: ignore[reportDeprecated], + files.retrieve_content, # pyright: ignore[reportDeprecated], ) ) @@ -726,6 +842,6 @@ def __init__(self, files: AsyncFiles) -> None: ) self.retrieve_content = ( # pyright: ignore[reportDeprecated] async_to_streamed_response_wrapper( - files.retrieve_content # pyright: ignore[reportDeprecated], + files.retrieve_content, # pyright: ignore[reportDeprecated], ) ) diff --git a/src/openai/resources/fine_tuning/__init__.py b/src/openai/resources/fine_tuning/__init__.py index 7765231fee..c76af83deb 100644 --- a/src/openai/resources/fine_tuning/__init__.py +++ b/src/openai/resources/fine_tuning/__init__.py @@ -8,6 +8,22 @@ JobsWithStreamingResponse, AsyncJobsWithStreamingResponse, ) +from .alpha import ( + Alpha, + AsyncAlpha, + AlphaWithRawResponse, + AsyncAlphaWithRawResponse, + AlphaWithStreamingResponse, + AsyncAlphaWithStreamingResponse, +) +from .checkpoints import ( + Checkpoints, + AsyncCheckpoints, + CheckpointsWithRawResponse, + AsyncCheckpointsWithRawResponse, + CheckpointsWithStreamingResponse, + AsyncCheckpointsWithStreamingResponse, +) from .fine_tuning import ( FineTuning, AsyncFineTuning, @@ -24,6 +40,18 @@ "AsyncJobsWithRawResponse", "JobsWithStreamingResponse", "AsyncJobsWithStreamingResponse", + "Checkpoints", + "AsyncCheckpoints", + "CheckpointsWithRawResponse", + "AsyncCheckpointsWithRawResponse", + "CheckpointsWithStreamingResponse", + "AsyncCheckpointsWithStreamingResponse", + "Alpha", + "AsyncAlpha", + "AlphaWithRawResponse", + "AsyncAlphaWithRawResponse", + "AlphaWithStreamingResponse", + "AsyncAlphaWithStreamingResponse", "FineTuning", "AsyncFineTuning", "FineTuningWithRawResponse", diff --git a/src/openai/resources/fine_tuning/alpha/__init__.py b/src/openai/resources/fine_tuning/alpha/__init__.py new file mode 100644 index 0000000000..8bed8af4fd --- /dev/null +++ b/src/openai/resources/fine_tuning/alpha/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .alpha import ( + Alpha, + AsyncAlpha, + AlphaWithRawResponse, + AsyncAlphaWithRawResponse, + AlphaWithStreamingResponse, + AsyncAlphaWithStreamingResponse, +) +from .graders import ( + Graders, + AsyncGraders, + GradersWithRawResponse, + AsyncGradersWithRawResponse, + GradersWithStreamingResponse, + AsyncGradersWithStreamingResponse, +) + +__all__ = [ + "Graders", + "AsyncGraders", + "GradersWithRawResponse", + "AsyncGradersWithRawResponse", + "GradersWithStreamingResponse", + "AsyncGradersWithStreamingResponse", + "Alpha", + "AsyncAlpha", + "AlphaWithRawResponse", + "AsyncAlphaWithRawResponse", + "AlphaWithStreamingResponse", + "AsyncAlphaWithStreamingResponse", +] diff --git a/src/openai/resources/fine_tuning/alpha/alpha.py b/src/openai/resources/fine_tuning/alpha/alpha.py new file mode 100644 index 0000000000..183208d0ab --- /dev/null +++ b/src/openai/resources/fine_tuning/alpha/alpha.py @@ -0,0 +1,108 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .graders import ( + Graders, + AsyncGraders, + GradersWithRawResponse, + AsyncGradersWithRawResponse, + GradersWithStreamingResponse, + AsyncGradersWithStreamingResponse, +) +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource + +__all__ = ["Alpha", "AsyncAlpha"] + + +class Alpha(SyncAPIResource): + @cached_property + def graders(self) -> Graders: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return Graders(self._client) + + @cached_property + def with_raw_response(self) -> AlphaWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AlphaWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AlphaWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AlphaWithStreamingResponse(self) + + +class AsyncAlpha(AsyncAPIResource): + @cached_property + def graders(self) -> AsyncGraders: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return AsyncGraders(self._client) + + @cached_property + def with_raw_response(self) -> AsyncAlphaWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncAlphaWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAlphaWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncAlphaWithStreamingResponse(self) + + +class AlphaWithRawResponse: + def __init__(self, alpha: Alpha) -> None: + self._alpha = alpha + + @cached_property + def graders(self) -> GradersWithRawResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return GradersWithRawResponse(self._alpha.graders) + + +class AsyncAlphaWithRawResponse: + def __init__(self, alpha: AsyncAlpha) -> None: + self._alpha = alpha + + @cached_property + def graders(self) -> AsyncGradersWithRawResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return AsyncGradersWithRawResponse(self._alpha.graders) + + +class AlphaWithStreamingResponse: + def __init__(self, alpha: Alpha) -> None: + self._alpha = alpha + + @cached_property + def graders(self) -> GradersWithStreamingResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return GradersWithStreamingResponse(self._alpha.graders) + + +class AsyncAlphaWithStreamingResponse: + def __init__(self, alpha: AsyncAlpha) -> None: + self._alpha = alpha + + @cached_property + def graders(self) -> AsyncGradersWithStreamingResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return AsyncGradersWithStreamingResponse(self._alpha.graders) diff --git a/src/openai/resources/fine_tuning/alpha/graders.py b/src/openai/resources/fine_tuning/alpha/graders.py new file mode 100644 index 0000000000..51c491b91f --- /dev/null +++ b/src/openai/resources/fine_tuning/alpha/graders.py @@ -0,0 +1,302 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...._base_client import make_request_options +from ....types.fine_tuning.alpha import grader_run_params, grader_validate_params +from ....types.fine_tuning.alpha.grader_run_response import GraderRunResponse +from ....types.fine_tuning.alpha.grader_validate_response import GraderValidateResponse + +__all__ = ["Graders", "AsyncGraders"] + + +class Graders(SyncAPIResource): + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + + @cached_property + def with_raw_response(self) -> GradersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return GradersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> GradersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return GradersWithStreamingResponse(self) + + def run( + self, + *, + grader: grader_run_params.Grader, + model_sample: str, + item: object | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GraderRunResponse: + """ + Run a grader. + + Args: + grader: The grader used for the fine-tuning job. + + model_sample: The model sample to be evaluated. This value will be used to populate the + `sample` namespace. See + [the guide](https://platform.openai.com/docs/guides/graders) for more details. + The `output_json` variable will be populated if the model sample is a valid JSON + string. + + item: The dataset item provided to the grader. This will be used to populate the + `item` namespace. See + [the guide](https://platform.openai.com/docs/guides/graders) for more details. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/fine_tuning/alpha/graders/run", + body=maybe_transform( + { + "grader": grader, + "model_sample": model_sample, + "item": item, + }, + grader_run_params.GraderRunParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=GraderRunResponse, + ) + + def validate( + self, + *, + grader: grader_validate_params.Grader, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GraderValidateResponse: + """ + Validate a grader. + + Args: + grader: The grader used for the fine-tuning job. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/fine_tuning/alpha/graders/validate", + body=maybe_transform({"grader": grader}, grader_validate_params.GraderValidateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=GraderValidateResponse, + ) + + +class AsyncGraders(AsyncAPIResource): + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + + @cached_property + def with_raw_response(self) -> AsyncGradersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncGradersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncGradersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncGradersWithStreamingResponse(self) + + async def run( + self, + *, + grader: grader_run_params.Grader, + model_sample: str, + item: object | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GraderRunResponse: + """ + Run a grader. + + Args: + grader: The grader used for the fine-tuning job. + + model_sample: The model sample to be evaluated. This value will be used to populate the + `sample` namespace. See + [the guide](https://platform.openai.com/docs/guides/graders) for more details. + The `output_json` variable will be populated if the model sample is a valid JSON + string. + + item: The dataset item provided to the grader. This will be used to populate the + `item` namespace. See + [the guide](https://platform.openai.com/docs/guides/graders) for more details. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/fine_tuning/alpha/graders/run", + body=await async_maybe_transform( + { + "grader": grader, + "model_sample": model_sample, + "item": item, + }, + grader_run_params.GraderRunParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=GraderRunResponse, + ) + + async def validate( + self, + *, + grader: grader_validate_params.Grader, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GraderValidateResponse: + """ + Validate a grader. + + Args: + grader: The grader used for the fine-tuning job. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/fine_tuning/alpha/graders/validate", + body=await async_maybe_transform({"grader": grader}, grader_validate_params.GraderValidateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=GraderValidateResponse, + ) + + +class GradersWithRawResponse: + def __init__(self, graders: Graders) -> None: + self._graders = graders + + self.run = _legacy_response.to_raw_response_wrapper( + graders.run, + ) + self.validate = _legacy_response.to_raw_response_wrapper( + graders.validate, + ) + + +class AsyncGradersWithRawResponse: + def __init__(self, graders: AsyncGraders) -> None: + self._graders = graders + + self.run = _legacy_response.async_to_raw_response_wrapper( + graders.run, + ) + self.validate = _legacy_response.async_to_raw_response_wrapper( + graders.validate, + ) + + +class GradersWithStreamingResponse: + def __init__(self, graders: Graders) -> None: + self._graders = graders + + self.run = to_streamed_response_wrapper( + graders.run, + ) + self.validate = to_streamed_response_wrapper( + graders.validate, + ) + + +class AsyncGradersWithStreamingResponse: + def __init__(self, graders: AsyncGraders) -> None: + self._graders = graders + + self.run = async_to_streamed_response_wrapper( + graders.run, + ) + self.validate = async_to_streamed_response_wrapper( + graders.validate, + ) diff --git a/src/openai/resources/fine_tuning/checkpoints/__init__.py b/src/openai/resources/fine_tuning/checkpoints/__init__.py new file mode 100644 index 0000000000..fdc37940f9 --- /dev/null +++ b/src/openai/resources/fine_tuning/checkpoints/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .checkpoints import ( + Checkpoints, + AsyncCheckpoints, + CheckpointsWithRawResponse, + AsyncCheckpointsWithRawResponse, + CheckpointsWithStreamingResponse, + AsyncCheckpointsWithStreamingResponse, +) +from .permissions import ( + Permissions, + AsyncPermissions, + PermissionsWithRawResponse, + AsyncPermissionsWithRawResponse, + PermissionsWithStreamingResponse, + AsyncPermissionsWithStreamingResponse, +) + +__all__ = [ + "Permissions", + "AsyncPermissions", + "PermissionsWithRawResponse", + "AsyncPermissionsWithRawResponse", + "PermissionsWithStreamingResponse", + "AsyncPermissionsWithStreamingResponse", + "Checkpoints", + "AsyncCheckpoints", + "CheckpointsWithRawResponse", + "AsyncCheckpointsWithRawResponse", + "CheckpointsWithStreamingResponse", + "AsyncCheckpointsWithStreamingResponse", +] diff --git a/src/openai/resources/fine_tuning/checkpoints/checkpoints.py b/src/openai/resources/fine_tuning/checkpoints/checkpoints.py new file mode 100644 index 0000000000..9c2ed6f576 --- /dev/null +++ b/src/openai/resources/fine_tuning/checkpoints/checkpoints.py @@ -0,0 +1,108 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ...._compat import cached_property +from .permissions import ( + Permissions, + AsyncPermissions, + PermissionsWithRawResponse, + AsyncPermissionsWithRawResponse, + PermissionsWithStreamingResponse, + AsyncPermissionsWithStreamingResponse, +) +from ...._resource import SyncAPIResource, AsyncAPIResource + +__all__ = ["Checkpoints", "AsyncCheckpoints"] + + +class Checkpoints(SyncAPIResource): + @cached_property + def permissions(self) -> Permissions: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return Permissions(self._client) + + @cached_property + def with_raw_response(self) -> CheckpointsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return CheckpointsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CheckpointsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return CheckpointsWithStreamingResponse(self) + + +class AsyncCheckpoints(AsyncAPIResource): + @cached_property + def permissions(self) -> AsyncPermissions: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return AsyncPermissions(self._client) + + @cached_property + def with_raw_response(self) -> AsyncCheckpointsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncCheckpointsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCheckpointsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncCheckpointsWithStreamingResponse(self) + + +class CheckpointsWithRawResponse: + def __init__(self, checkpoints: Checkpoints) -> None: + self._checkpoints = checkpoints + + @cached_property + def permissions(self) -> PermissionsWithRawResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return PermissionsWithRawResponse(self._checkpoints.permissions) + + +class AsyncCheckpointsWithRawResponse: + def __init__(self, checkpoints: AsyncCheckpoints) -> None: + self._checkpoints = checkpoints + + @cached_property + def permissions(self) -> AsyncPermissionsWithRawResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return AsyncPermissionsWithRawResponse(self._checkpoints.permissions) + + +class CheckpointsWithStreamingResponse: + def __init__(self, checkpoints: Checkpoints) -> None: + self._checkpoints = checkpoints + + @cached_property + def permissions(self) -> PermissionsWithStreamingResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return PermissionsWithStreamingResponse(self._checkpoints.permissions) + + +class AsyncCheckpointsWithStreamingResponse: + def __init__(self, checkpoints: AsyncCheckpoints) -> None: + self._checkpoints = checkpoints + + @cached_property + def permissions(self) -> AsyncPermissionsWithStreamingResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + return AsyncPermissionsWithStreamingResponse(self._checkpoints.permissions) diff --git a/src/openai/resources/fine_tuning/checkpoints/permissions.py b/src/openai/resources/fine_tuning/checkpoints/permissions.py new file mode 100644 index 0000000000..49687c15cd --- /dev/null +++ b/src/openai/resources/fine_tuning/checkpoints/permissions.py @@ -0,0 +1,622 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import typing_extensions +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncPage, AsyncPage, SyncConversationCursorPage, AsyncConversationCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.fine_tuning.checkpoints import ( + permission_list_params, + permission_create_params, + permission_retrieve_params, +) +from ....types.fine_tuning.checkpoints.permission_list_response import PermissionListResponse +from ....types.fine_tuning.checkpoints.permission_create_response import PermissionCreateResponse +from ....types.fine_tuning.checkpoints.permission_delete_response import PermissionDeleteResponse +from ....types.fine_tuning.checkpoints.permission_retrieve_response import PermissionRetrieveResponse + +__all__ = ["Permissions", "AsyncPermissions"] + + +class Permissions(SyncAPIResource): + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + + @cached_property + def with_raw_response(self) -> PermissionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return PermissionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PermissionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return PermissionsWithStreamingResponse(self) + + def create( + self, + fine_tuned_model_checkpoint: str, + *, + project_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncPage[PermissionCreateResponse]: + """ + **NOTE:** Calling this endpoint requires an [admin API key](../admin-api-keys). + + This enables organization owners to share fine-tuned models with other projects + in their organization. + + Args: + project_ids: The project identifiers to grant access to. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuned_model_checkpoint: + raise ValueError( + f"Expected a non-empty value for `fine_tuned_model_checkpoint` but received {fine_tuned_model_checkpoint!r}" + ) + return self._get_api_list( + path_template( + "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions", + fine_tuned_model_checkpoint=fine_tuned_model_checkpoint, + ), + page=SyncPage[PermissionCreateResponse], + body=maybe_transform({"project_ids": project_ids}, permission_create_params.PermissionCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + model=PermissionCreateResponse, + method="post", + ) + + @typing_extensions.deprecated("Retrieve is deprecated. Please swap to the paginated list method instead.") + def retrieve( + self, + fine_tuned_model_checkpoint: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["ascending", "descending"] | Omit = omit, + project_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PermissionRetrieveResponse: + """ + **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). + + Organization owners can use this endpoint to view all permissions for a + fine-tuned model checkpoint. + + Args: + after: Identifier for the last permission ID from the previous pagination request. + + limit: Number of permissions to retrieve. + + order: The order in which to retrieve permissions. + + project_id: The ID of the project to get permissions for. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuned_model_checkpoint: + raise ValueError( + f"Expected a non-empty value for `fine_tuned_model_checkpoint` but received {fine_tuned_model_checkpoint!r}" + ) + return self._get( + path_template( + "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions", + fine_tuned_model_checkpoint=fine_tuned_model_checkpoint, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "project_id": project_id, + }, + permission_retrieve_params.PermissionRetrieveParams, + ), + security={"bearer_auth": True}, + ), + cast_to=PermissionRetrieveResponse, + ) + + def list( + self, + fine_tuned_model_checkpoint: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["ascending", "descending"] | Omit = omit, + project_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[PermissionListResponse]: + """ + **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). + + Organization owners can use this endpoint to view all permissions for a + fine-tuned model checkpoint. + + Args: + after: Identifier for the last permission ID from the previous pagination request. + + limit: Number of permissions to retrieve. + + order: The order in which to retrieve permissions. + + project_id: The ID of the project to get permissions for. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuned_model_checkpoint: + raise ValueError( + f"Expected a non-empty value for `fine_tuned_model_checkpoint` but received {fine_tuned_model_checkpoint!r}" + ) + return self._get_api_list( + path_template( + "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions", + fine_tuned_model_checkpoint=fine_tuned_model_checkpoint, + ), + page=SyncConversationCursorPage[PermissionListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "project_id": project_id, + }, + permission_list_params.PermissionListParams, + ), + security={"bearer_auth": True}, + ), + model=PermissionListResponse, + ) + + def delete( + self, + permission_id: str, + *, + fine_tuned_model_checkpoint: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PermissionDeleteResponse: + """ + **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). + + Organization owners can use this endpoint to delete a permission for a + fine-tuned model checkpoint. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuned_model_checkpoint: + raise ValueError( + f"Expected a non-empty value for `fine_tuned_model_checkpoint` but received {fine_tuned_model_checkpoint!r}" + ) + if not permission_id: + raise ValueError(f"Expected a non-empty value for `permission_id` but received {permission_id!r}") + return self._delete( + path_template( + "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions/{permission_id}", + fine_tuned_model_checkpoint=fine_tuned_model_checkpoint, + permission_id=permission_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=PermissionDeleteResponse, + ) + + +class AsyncPermissions(AsyncAPIResource): + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + + @cached_property + def with_raw_response(self) -> AsyncPermissionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncPermissionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPermissionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncPermissionsWithStreamingResponse(self) + + def create( + self, + fine_tuned_model_checkpoint: str, + *, + project_ids: SequenceNotStr[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[PermissionCreateResponse, AsyncPage[PermissionCreateResponse]]: + """ + **NOTE:** Calling this endpoint requires an [admin API key](../admin-api-keys). + + This enables organization owners to share fine-tuned models with other projects + in their organization. + + Args: + project_ids: The project identifiers to grant access to. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuned_model_checkpoint: + raise ValueError( + f"Expected a non-empty value for `fine_tuned_model_checkpoint` but received {fine_tuned_model_checkpoint!r}" + ) + return self._get_api_list( + path_template( + "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions", + fine_tuned_model_checkpoint=fine_tuned_model_checkpoint, + ), + page=AsyncPage[PermissionCreateResponse], + body=maybe_transform({"project_ids": project_ids}, permission_create_params.PermissionCreateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + model=PermissionCreateResponse, + method="post", + ) + + @typing_extensions.deprecated("Retrieve is deprecated. Please swap to the paginated list method instead.") + async def retrieve( + self, + fine_tuned_model_checkpoint: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["ascending", "descending"] | Omit = omit, + project_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PermissionRetrieveResponse: + """ + **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). + + Organization owners can use this endpoint to view all permissions for a + fine-tuned model checkpoint. + + Args: + after: Identifier for the last permission ID from the previous pagination request. + + limit: Number of permissions to retrieve. + + order: The order in which to retrieve permissions. + + project_id: The ID of the project to get permissions for. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuned_model_checkpoint: + raise ValueError( + f"Expected a non-empty value for `fine_tuned_model_checkpoint` but received {fine_tuned_model_checkpoint!r}" + ) + return await self._get( + path_template( + "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions", + fine_tuned_model_checkpoint=fine_tuned_model_checkpoint, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "project_id": project_id, + }, + permission_retrieve_params.PermissionRetrieveParams, + ), + security={"bearer_auth": True}, + ), + cast_to=PermissionRetrieveResponse, + ) + + def list( + self, + fine_tuned_model_checkpoint: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["ascending", "descending"] | Omit = omit, + project_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[PermissionListResponse, AsyncConversationCursorPage[PermissionListResponse]]: + """ + **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). + + Organization owners can use this endpoint to view all permissions for a + fine-tuned model checkpoint. + + Args: + after: Identifier for the last permission ID from the previous pagination request. + + limit: Number of permissions to retrieve. + + order: The order in which to retrieve permissions. + + project_id: The ID of the project to get permissions for. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuned_model_checkpoint: + raise ValueError( + f"Expected a non-empty value for `fine_tuned_model_checkpoint` but received {fine_tuned_model_checkpoint!r}" + ) + return self._get_api_list( + path_template( + "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions", + fine_tuned_model_checkpoint=fine_tuned_model_checkpoint, + ), + page=AsyncConversationCursorPage[PermissionListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "project_id": project_id, + }, + permission_list_params.PermissionListParams, + ), + security={"bearer_auth": True}, + ), + model=PermissionListResponse, + ) + + async def delete( + self, + permission_id: str, + *, + fine_tuned_model_checkpoint: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PermissionDeleteResponse: + """ + **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). + + Organization owners can use this endpoint to delete a permission for a + fine-tuned model checkpoint. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuned_model_checkpoint: + raise ValueError( + f"Expected a non-empty value for `fine_tuned_model_checkpoint` but received {fine_tuned_model_checkpoint!r}" + ) + if not permission_id: + raise ValueError(f"Expected a non-empty value for `permission_id` but received {permission_id!r}") + return await self._delete( + path_template( + "/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions/{permission_id}", + fine_tuned_model_checkpoint=fine_tuned_model_checkpoint, + permission_id=permission_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=PermissionDeleteResponse, + ) + + +class PermissionsWithRawResponse: + def __init__(self, permissions: Permissions) -> None: + self._permissions = permissions + + self.create = _legacy_response.to_raw_response_wrapper( + permissions.create, + ) + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + permissions.retrieve, # pyright: ignore[reportDeprecated], + ) + ) + self.list = _legacy_response.to_raw_response_wrapper( + permissions.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + permissions.delete, + ) + + +class AsyncPermissionsWithRawResponse: + def __init__(self, permissions: AsyncPermissions) -> None: + self._permissions = permissions + + self.create = _legacy_response.async_to_raw_response_wrapper( + permissions.create, + ) + self.retrieve = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + permissions.retrieve, # pyright: ignore[reportDeprecated], + ) + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + permissions.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + permissions.delete, + ) + + +class PermissionsWithStreamingResponse: + def __init__(self, permissions: Permissions) -> None: + self._permissions = permissions + + self.create = to_streamed_response_wrapper( + permissions.create, + ) + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + permissions.retrieve, # pyright: ignore[reportDeprecated], + ) + ) + self.list = to_streamed_response_wrapper( + permissions.list, + ) + self.delete = to_streamed_response_wrapper( + permissions.delete, + ) + + +class AsyncPermissionsWithStreamingResponse: + def __init__(self, permissions: AsyncPermissions) -> None: + self._permissions = permissions + + self.create = async_to_streamed_response_wrapper( + permissions.create, + ) + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + permissions.retrieve, # pyright: ignore[reportDeprecated], + ) + ) + self.list = async_to_streamed_response_wrapper( + permissions.list, + ) + self.delete = async_to_streamed_response_wrapper( + permissions.delete, + ) diff --git a/src/openai/resources/fine_tuning/fine_tuning.py b/src/openai/resources/fine_tuning/fine_tuning.py index c386de3c2a..60f1f44bdc 100644 --- a/src/openai/resources/fine_tuning/fine_tuning.py +++ b/src/openai/resources/fine_tuning/fine_tuning.py @@ -2,7 +2,8 @@ from __future__ import annotations -from .jobs import ( +from ..._compat import cached_property +from .jobs.jobs import ( Jobs, AsyncJobs, JobsWithRawResponse, @@ -10,9 +11,23 @@ JobsWithStreamingResponse, AsyncJobsWithStreamingResponse, ) -from ..._compat import cached_property -from .jobs.jobs import Jobs, AsyncJobs from ..._resource import SyncAPIResource, AsyncAPIResource +from .alpha.alpha import ( + Alpha, + AsyncAlpha, + AlphaWithRawResponse, + AsyncAlphaWithRawResponse, + AlphaWithStreamingResponse, + AsyncAlphaWithStreamingResponse, +) +from .checkpoints.checkpoints import ( + Checkpoints, + AsyncCheckpoints, + CheckpointsWithRawResponse, + AsyncCheckpointsWithRawResponse, + CheckpointsWithStreamingResponse, + AsyncCheckpointsWithStreamingResponse, +) __all__ = ["FineTuning", "AsyncFineTuning"] @@ -20,12 +35,21 @@ class FineTuning(SyncAPIResource): @cached_property def jobs(self) -> Jobs: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return Jobs(self._client) + @cached_property + def checkpoints(self) -> Checkpoints: + return Checkpoints(self._client) + + @cached_property + def alpha(self) -> Alpha: + return Alpha(self._client) + @cached_property def with_raw_response(self) -> FineTuningWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -45,12 +69,21 @@ def with_streaming_response(self) -> FineTuningWithStreamingResponse: class AsyncFineTuning(AsyncAPIResource): @cached_property def jobs(self) -> AsyncJobs: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return AsyncJobs(self._client) + @cached_property + def checkpoints(self) -> AsyncCheckpoints: + return AsyncCheckpoints(self._client) + + @cached_property + def alpha(self) -> AsyncAlpha: + return AsyncAlpha(self._client) + @cached_property def with_raw_response(self) -> AsyncFineTuningWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -73,8 +106,17 @@ def __init__(self, fine_tuning: FineTuning) -> None: @cached_property def jobs(self) -> JobsWithRawResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return JobsWithRawResponse(self._fine_tuning.jobs) + @cached_property + def checkpoints(self) -> CheckpointsWithRawResponse: + return CheckpointsWithRawResponse(self._fine_tuning.checkpoints) + + @cached_property + def alpha(self) -> AlphaWithRawResponse: + return AlphaWithRawResponse(self._fine_tuning.alpha) + class AsyncFineTuningWithRawResponse: def __init__(self, fine_tuning: AsyncFineTuning) -> None: @@ -82,8 +124,17 @@ def __init__(self, fine_tuning: AsyncFineTuning) -> None: @cached_property def jobs(self) -> AsyncJobsWithRawResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return AsyncJobsWithRawResponse(self._fine_tuning.jobs) + @cached_property + def checkpoints(self) -> AsyncCheckpointsWithRawResponse: + return AsyncCheckpointsWithRawResponse(self._fine_tuning.checkpoints) + + @cached_property + def alpha(self) -> AsyncAlphaWithRawResponse: + return AsyncAlphaWithRawResponse(self._fine_tuning.alpha) + class FineTuningWithStreamingResponse: def __init__(self, fine_tuning: FineTuning) -> None: @@ -91,8 +142,17 @@ def __init__(self, fine_tuning: FineTuning) -> None: @cached_property def jobs(self) -> JobsWithStreamingResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return JobsWithStreamingResponse(self._fine_tuning.jobs) + @cached_property + def checkpoints(self) -> CheckpointsWithStreamingResponse: + return CheckpointsWithStreamingResponse(self._fine_tuning.checkpoints) + + @cached_property + def alpha(self) -> AlphaWithStreamingResponse: + return AlphaWithStreamingResponse(self._fine_tuning.alpha) + class AsyncFineTuningWithStreamingResponse: def __init__(self, fine_tuning: AsyncFineTuning) -> None: @@ -100,4 +160,13 @@ def __init__(self, fine_tuning: AsyncFineTuning) -> None: @cached_property def jobs(self) -> AsyncJobsWithStreamingResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return AsyncJobsWithStreamingResponse(self._fine_tuning.jobs) + + @cached_property + def checkpoints(self) -> AsyncCheckpointsWithStreamingResponse: + return AsyncCheckpointsWithStreamingResponse(self._fine_tuning.checkpoints) + + @cached_property + def alpha(self) -> AsyncAlphaWithStreamingResponse: + return AsyncAlphaWithStreamingResponse(self._fine_tuning.alpha) diff --git a/src/openai/resources/fine_tuning/jobs/checkpoints.py b/src/openai/resources/fine_tuning/jobs/checkpoints.py index 8b5e905ea5..b679372386 100644 --- a/src/openai/resources/fine_tuning/jobs/checkpoints.py +++ b/src/openai/resources/fine_tuning/jobs/checkpoints.py @@ -5,8 +5,8 @@ import httpx from .... import _legacy_response -from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ...._utils import maybe_transform +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -22,10 +22,12 @@ class Checkpoints(SyncAPIResource): + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + @cached_property def with_raw_response(self) -> CheckpointsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -45,14 +47,14 @@ def list( self, fine_tuning_job_id: str, *, - after: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[FineTuningJobCheckpoint]: """ List checkpoints for a fine-tuning job. @@ -73,7 +75,7 @@ def list( if not fine_tuning_job_id: raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") return self._get_api_list( - f"/fine_tuning/jobs/{fine_tuning_job_id}/checkpoints", + path_template("/fine_tuning/jobs/{fine_tuning_job_id}/checkpoints", fine_tuning_job_id=fine_tuning_job_id), page=SyncCursorPage[FineTuningJobCheckpoint], options=make_request_options( extra_headers=extra_headers, @@ -87,16 +89,19 @@ def list( }, checkpoint_list_params.CheckpointListParams, ), + security={"bearer_auth": True}, ), model=FineTuningJobCheckpoint, ) class AsyncCheckpoints(AsyncAPIResource): + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + @cached_property def with_raw_response(self) -> AsyncCheckpointsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -116,14 +121,14 @@ def list( self, fine_tuning_job_id: str, *, - after: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FineTuningJobCheckpoint, AsyncCursorPage[FineTuningJobCheckpoint]]: """ List checkpoints for a fine-tuning job. @@ -144,7 +149,7 @@ def list( if not fine_tuning_job_id: raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") return self._get_api_list( - f"/fine_tuning/jobs/{fine_tuning_job_id}/checkpoints", + path_template("/fine_tuning/jobs/{fine_tuning_job_id}/checkpoints", fine_tuning_job_id=fine_tuning_job_id), page=AsyncCursorPage[FineTuningJobCheckpoint], options=make_request_options( extra_headers=extra_headers, @@ -158,6 +163,7 @@ def list( }, checkpoint_list_params.CheckpointListParams, ), + security={"bearer_auth": True}, ), model=FineTuningJobCheckpoint, ) diff --git a/src/openai/resources/fine_tuning/jobs/jobs.py b/src/openai/resources/fine_tuning/jobs/jobs.py index 44abf1cfe1..e7e8903a84 100644 --- a/src/openai/resources/fine_tuning/jobs/jobs.py +++ b/src/openai/resources/fine_tuning/jobs/jobs.py @@ -2,17 +2,14 @@ from __future__ import annotations -from typing import Union, Iterable, Optional +from typing import Dict, Union, Iterable, Optional from typing_extensions import Literal import httpx from .... import _legacy_response -from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ...._utils import ( - maybe_transform, - async_maybe_transform, -) +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform from ...._compat import cached_property from .checkpoints import ( Checkpoints, @@ -30,6 +27,7 @@ make_request_options, ) from ....types.fine_tuning import job_list_params, job_create_params, job_list_events_params +from ....types.shared_params.metadata import Metadata from ....types.fine_tuning.fine_tuning_job import FineTuningJob from ....types.fine_tuning.fine_tuning_job_event import FineTuningJobEvent @@ -37,14 +35,17 @@ class Jobs(SyncAPIResource): + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + @cached_property def checkpoints(self) -> Checkpoints: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return Checkpoints(self._client) @cached_property def with_raw_response(self) -> JobsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -65,17 +66,19 @@ def create( *, model: Union[str, Literal["babbage-002", "davinci-002", "gpt-3.5-turbo", "gpt-4o-mini"]], training_file: str, - hyperparameters: job_create_params.Hyperparameters | NotGiven = NOT_GIVEN, - integrations: Optional[Iterable[job_create_params.Integration]] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - suffix: Optional[str] | NotGiven = NOT_GIVEN, - validation_file: Optional[str] | NotGiven = NOT_GIVEN, + hyperparameters: job_create_params.Hyperparameters | Omit = omit, + integrations: Optional[Iterable[job_create_params.Integration]] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + method: job_create_params.Method | Omit = omit, + seed: Optional[int] | Omit = omit, + suffix: Optional[str] | Omit = omit, + validation_file: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Creates a fine-tuning job which begins the process of creating a new model from @@ -84,11 +87,11 @@ def create( Response includes details of the enqueued job including job status and the name of the fine-tuned models once complete. - [Learn more about fine-tuning](https://platform.openai.com/docs/guides/fine-tuning) + [Learn more about fine-tuning](https://platform.openai.com/docs/guides/model-optimization) Args: model: The name of the model to fine-tune. You can select one of the - [supported models](https://platform.openai.com/docs/guides/fine-tuning/which-models-can-be-fine-tuned). + [supported models](https://platform.openai.com/docs/guides/fine-tuning#which-models-can-be-fine-tuned). training_file: The ID of an uploaded file that contains training data. @@ -99,17 +102,30 @@ def create( your file with the purpose `fine-tune`. The contents of the file should differ depending on if the model uses the - [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input) or + [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input), [completions](https://platform.openai.com/docs/api-reference/fine-tuning/completions-input) + format, or if the fine-tuning method uses the + [preference](https://platform.openai.com/docs/api-reference/fine-tuning/preference-input) format. - See the [fine-tuning guide](https://platform.openai.com/docs/guides/fine-tuning) + See the + [fine-tuning guide](https://platform.openai.com/docs/guides/model-optimization) for more details. - hyperparameters: The hyperparameters used for the fine-tuning job. + hyperparameters: The hyperparameters used for the fine-tuning job. This value is now deprecated + in favor of `method`, and should be passed in under the `method` parameter. integrations: A list of integrations to enable for your fine-tuning job. + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + method: The method used for fine-tuning. + seed: The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases. If a seed is not specified, one will be generated for you. @@ -130,7 +146,8 @@ def create( Your dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`. - See the [fine-tuning guide](https://platform.openai.com/docs/guides/fine-tuning) + See the + [fine-tuning guide](https://platform.openai.com/docs/guides/model-optimization) for more details. extra_headers: Send extra headers @@ -149,6 +166,8 @@ def create( "training_file": training_file, "hyperparameters": hyperparameters, "integrations": integrations, + "metadata": metadata, + "method": method, "seed": seed, "suffix": suffix, "validation_file": validation_file, @@ -156,7 +175,11 @@ def create( job_create_params.JobCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FineTuningJob, ) @@ -170,12 +193,12 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Get info about a fine-tuning job. - [Learn more about fine-tuning](https://platform.openai.com/docs/guides/fine-tuning) + [Learn more about fine-tuning](https://platform.openai.com/docs/guides/model-optimization) Args: extra_headers: Send extra headers @@ -189,9 +212,13 @@ def retrieve( if not fine_tuning_job_id: raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") return self._get( - f"/fine_tuning/jobs/{fine_tuning_job_id}", + path_template("/fine_tuning/jobs/{fine_tuning_job_id}", fine_tuning_job_id=fine_tuning_job_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FineTuningJob, ) @@ -199,14 +226,15 @@ def retrieve( def list( self, *, - after: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + limit: int | Omit = omit, + metadata: Optional[Dict[str, str]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[FineTuningJob]: """ List your organization's fine-tuning jobs @@ -216,6 +244,9 @@ def list( limit: Number of fine-tuning jobs to retrieve. + metadata: Optional metadata filter. To filter, use the syntax `metadata[k]=v`. + Alternatively, set `metadata=null` to indicate no metadata. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -236,9 +267,11 @@ def list( { "after": after, "limit": limit, + "metadata": metadata, }, job_list_params.JobListParams, ), + security={"bearer_auth": True}, ), model=FineTuningJob, ) @@ -252,7 +285,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Immediately cancel a fine-tune job. @@ -269,9 +302,13 @@ def cancel( if not fine_tuning_job_id: raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") return self._post( - f"/fine_tuning/jobs/{fine_tuning_job_id}/cancel", + path_template("/fine_tuning/jobs/{fine_tuning_job_id}/cancel", fine_tuning_job_id=fine_tuning_job_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FineTuningJob, ) @@ -280,14 +317,14 @@ def list_events( self, fine_tuning_job_id: str, *, - after: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[FineTuningJobEvent]: """ Get status updates for a fine-tuning job. @@ -308,7 +345,7 @@ def list_events( if not fine_tuning_job_id: raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") return self._get_api_list( - f"/fine_tuning/jobs/{fine_tuning_job_id}/events", + path_template("/fine_tuning/jobs/{fine_tuning_job_id}/events", fine_tuning_job_id=fine_tuning_job_id), page=SyncCursorPage[FineTuningJobEvent], options=make_request_options( extra_headers=extra_headers, @@ -322,20 +359,98 @@ def list_events( }, job_list_events_params.JobListEventsParams, ), + security={"bearer_auth": True}, ), model=FineTuningJobEvent, ) + def pause( + self, + fine_tuning_job_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FineTuningJob: + """ + Pause a fine-tune job. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuning_job_id: + raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") + return self._post( + path_template("/fine_tuning/jobs/{fine_tuning_job_id}/pause", fine_tuning_job_id=fine_tuning_job_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=FineTuningJob, + ) + + def resume( + self, + fine_tuning_job_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FineTuningJob: + """ + Resume a fine-tune job. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuning_job_id: + raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") + return self._post( + path_template("/fine_tuning/jobs/{fine_tuning_job_id}/resume", fine_tuning_job_id=fine_tuning_job_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=FineTuningJob, + ) + class AsyncJobs(AsyncAPIResource): + """Manage fine-tuning jobs to tailor a model to your specific training data.""" + @cached_property def checkpoints(self) -> AsyncCheckpoints: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return AsyncCheckpoints(self._client) @cached_property def with_raw_response(self) -> AsyncJobsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -356,17 +471,19 @@ async def create( *, model: Union[str, Literal["babbage-002", "davinci-002", "gpt-3.5-turbo", "gpt-4o-mini"]], training_file: str, - hyperparameters: job_create_params.Hyperparameters | NotGiven = NOT_GIVEN, - integrations: Optional[Iterable[job_create_params.Integration]] | NotGiven = NOT_GIVEN, - seed: Optional[int] | NotGiven = NOT_GIVEN, - suffix: Optional[str] | NotGiven = NOT_GIVEN, - validation_file: Optional[str] | NotGiven = NOT_GIVEN, + hyperparameters: job_create_params.Hyperparameters | Omit = omit, + integrations: Optional[Iterable[job_create_params.Integration]] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + method: job_create_params.Method | Omit = omit, + seed: Optional[int] | Omit = omit, + suffix: Optional[str] | Omit = omit, + validation_file: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Creates a fine-tuning job which begins the process of creating a new model from @@ -375,11 +492,11 @@ async def create( Response includes details of the enqueued job including job status and the name of the fine-tuned models once complete. - [Learn more about fine-tuning](https://platform.openai.com/docs/guides/fine-tuning) + [Learn more about fine-tuning](https://platform.openai.com/docs/guides/model-optimization) Args: model: The name of the model to fine-tune. You can select one of the - [supported models](https://platform.openai.com/docs/guides/fine-tuning/which-models-can-be-fine-tuned). + [supported models](https://platform.openai.com/docs/guides/fine-tuning#which-models-can-be-fine-tuned). training_file: The ID of an uploaded file that contains training data. @@ -390,17 +507,30 @@ async def create( your file with the purpose `fine-tune`. The contents of the file should differ depending on if the model uses the - [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input) or + [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input), [completions](https://platform.openai.com/docs/api-reference/fine-tuning/completions-input) + format, or if the fine-tuning method uses the + [preference](https://platform.openai.com/docs/api-reference/fine-tuning/preference-input) format. - See the [fine-tuning guide](https://platform.openai.com/docs/guides/fine-tuning) + See the + [fine-tuning guide](https://platform.openai.com/docs/guides/model-optimization) for more details. - hyperparameters: The hyperparameters used for the fine-tuning job. + hyperparameters: The hyperparameters used for the fine-tuning job. This value is now deprecated + in favor of `method`, and should be passed in under the `method` parameter. integrations: A list of integrations to enable for your fine-tuning job. + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + method: The method used for fine-tuning. + seed: The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases. If a seed is not specified, one will be generated for you. @@ -421,7 +551,8 @@ async def create( Your dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`. - See the [fine-tuning guide](https://platform.openai.com/docs/guides/fine-tuning) + See the + [fine-tuning guide](https://platform.openai.com/docs/guides/model-optimization) for more details. extra_headers: Send extra headers @@ -440,6 +571,8 @@ async def create( "training_file": training_file, "hyperparameters": hyperparameters, "integrations": integrations, + "metadata": metadata, + "method": method, "seed": seed, "suffix": suffix, "validation_file": validation_file, @@ -447,7 +580,11 @@ async def create( job_create_params.JobCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FineTuningJob, ) @@ -461,12 +598,12 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Get info about a fine-tuning job. - [Learn more about fine-tuning](https://platform.openai.com/docs/guides/fine-tuning) + [Learn more about fine-tuning](https://platform.openai.com/docs/guides/model-optimization) Args: extra_headers: Send extra headers @@ -480,9 +617,13 @@ async def retrieve( if not fine_tuning_job_id: raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") return await self._get( - f"/fine_tuning/jobs/{fine_tuning_job_id}", + path_template("/fine_tuning/jobs/{fine_tuning_job_id}", fine_tuning_job_id=fine_tuning_job_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FineTuningJob, ) @@ -490,14 +631,15 @@ async def retrieve( def list( self, *, - after: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + limit: int | Omit = omit, + metadata: Optional[Dict[str, str]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FineTuningJob, AsyncCursorPage[FineTuningJob]]: """ List your organization's fine-tuning jobs @@ -507,6 +649,9 @@ def list( limit: Number of fine-tuning jobs to retrieve. + metadata: Optional metadata filter. To filter, use the syntax `metadata[k]=v`. + Alternatively, set `metadata=null` to indicate no metadata. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -527,9 +672,11 @@ def list( { "after": after, "limit": limit, + "metadata": metadata, }, job_list_params.JobListParams, ), + security={"bearer_auth": True}, ), model=FineTuningJob, ) @@ -543,7 +690,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FineTuningJob: """ Immediately cancel a fine-tune job. @@ -560,9 +707,13 @@ async def cancel( if not fine_tuning_job_id: raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") return await self._post( - f"/fine_tuning/jobs/{fine_tuning_job_id}/cancel", + path_template("/fine_tuning/jobs/{fine_tuning_job_id}/cancel", fine_tuning_job_id=fine_tuning_job_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=FineTuningJob, ) @@ -571,14 +722,14 @@ def list_events( self, fine_tuning_job_id: str, *, - after: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FineTuningJobEvent, AsyncCursorPage[FineTuningJobEvent]]: """ Get status updates for a fine-tuning job. @@ -599,7 +750,7 @@ def list_events( if not fine_tuning_job_id: raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") return self._get_api_list( - f"/fine_tuning/jobs/{fine_tuning_job_id}/events", + path_template("/fine_tuning/jobs/{fine_tuning_job_id}/events", fine_tuning_job_id=fine_tuning_job_id), page=AsyncCursorPage[FineTuningJobEvent], options=make_request_options( extra_headers=extra_headers, @@ -613,10 +764,85 @@ def list_events( }, job_list_events_params.JobListEventsParams, ), + security={"bearer_auth": True}, ), model=FineTuningJobEvent, ) + async def pause( + self, + fine_tuning_job_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FineTuningJob: + """ + Pause a fine-tune job. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuning_job_id: + raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") + return await self._post( + path_template("/fine_tuning/jobs/{fine_tuning_job_id}/pause", fine_tuning_job_id=fine_tuning_job_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=FineTuningJob, + ) + + async def resume( + self, + fine_tuning_job_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FineTuningJob: + """ + Resume a fine-tune job. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not fine_tuning_job_id: + raise ValueError(f"Expected a non-empty value for `fine_tuning_job_id` but received {fine_tuning_job_id!r}") + return await self._post( + path_template("/fine_tuning/jobs/{fine_tuning_job_id}/resume", fine_tuning_job_id=fine_tuning_job_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=FineTuningJob, + ) + class JobsWithRawResponse: def __init__(self, jobs: Jobs) -> None: @@ -637,9 +863,16 @@ def __init__(self, jobs: Jobs) -> None: self.list_events = _legacy_response.to_raw_response_wrapper( jobs.list_events, ) + self.pause = _legacy_response.to_raw_response_wrapper( + jobs.pause, + ) + self.resume = _legacy_response.to_raw_response_wrapper( + jobs.resume, + ) @cached_property def checkpoints(self) -> CheckpointsWithRawResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return CheckpointsWithRawResponse(self._jobs.checkpoints) @@ -662,9 +895,16 @@ def __init__(self, jobs: AsyncJobs) -> None: self.list_events = _legacy_response.async_to_raw_response_wrapper( jobs.list_events, ) + self.pause = _legacy_response.async_to_raw_response_wrapper( + jobs.pause, + ) + self.resume = _legacy_response.async_to_raw_response_wrapper( + jobs.resume, + ) @cached_property def checkpoints(self) -> AsyncCheckpointsWithRawResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return AsyncCheckpointsWithRawResponse(self._jobs.checkpoints) @@ -687,9 +927,16 @@ def __init__(self, jobs: Jobs) -> None: self.list_events = to_streamed_response_wrapper( jobs.list_events, ) + self.pause = to_streamed_response_wrapper( + jobs.pause, + ) + self.resume = to_streamed_response_wrapper( + jobs.resume, + ) @cached_property def checkpoints(self) -> CheckpointsWithStreamingResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return CheckpointsWithStreamingResponse(self._jobs.checkpoints) @@ -712,7 +959,14 @@ def __init__(self, jobs: AsyncJobs) -> None: self.list_events = async_to_streamed_response_wrapper( jobs.list_events, ) + self.pause = async_to_streamed_response_wrapper( + jobs.pause, + ) + self.resume = async_to_streamed_response_wrapper( + jobs.resume, + ) @cached_property def checkpoints(self) -> AsyncCheckpointsWithStreamingResponse: + """Manage fine-tuning jobs to tailor a model to your specific training data.""" return AsyncCheckpointsWithStreamingResponse(self._jobs.checkpoints) diff --git a/src/openai/resources/images.py b/src/openai/resources/images.py index e9629d48fd..8140b5863f 100644 --- a/src/openai/resources/images.py +++ b/src/openai/resources/images.py @@ -3,34 +3,35 @@ from __future__ import annotations from typing import Union, Mapping, Optional, cast -from typing_extensions import Literal +from typing_extensions import Literal, overload import httpx from .. import _legacy_response from ..types import image_edit_params, image_generate_params, image_create_variation_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from .._utils import ( - extract_files, - maybe_transform, - deepcopy_minimal, - async_maybe_transform, -) +from .._files import deepcopy_with_paths +from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, SequenceNotStr, omit, not_given +from .._utils import extract_files, required_args, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .._streaming import Stream, AsyncStream from .._base_client import make_request_options from ..types.image_model import ImageModel from ..types.images_response import ImagesResponse +from ..types.image_gen_stream_event import ImageGenStreamEvent +from ..types.image_edit_stream_event import ImageEditStreamEvent __all__ = ["Images", "AsyncImages"] class Images(SyncAPIResource): + """Given a prompt and/or an input image, the model will generate a new image.""" + @cached_property def with_raw_response(self) -> ImagesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -50,20 +51,21 @@ def create_variation( self, *, image: FileTypes, - model: Union[str, ImageModel, None] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - response_format: Optional[Literal["url", "b64_json"]] | NotGiven = NOT_GIVEN, - size: Optional[Literal["256x256", "512x512", "1024x1024"]] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + model: Union[str, ImageModel, None] | Omit = omit, + n: Optional[int] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Optional[Literal["256x256", "512x512", "1024x1024"]] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: - """ - Creates a variation of a given image. + """Creates a variation of a given image. + + This endpoint only supports `dall-e-2`. Args: image: The image to use as the basis for the variation(s). Must be a valid PNG file, @@ -72,8 +74,7 @@ def create_variation( model: The model to use for image generation. Only `dall-e-2` is supported at this time. - n: The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only - `n=1` is supported. + n: The number of images to generate. Must be between 1 and 10. response_format: The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been @@ -84,7 +85,7 @@ def create_variation( user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -94,7 +95,7 @@ def create_variation( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + body = deepcopy_with_paths( { "image": image, "model": model, @@ -102,7 +103,8 @@ def create_variation( "response_format": response_format, "size": size, "user": user, - } + }, + [["image"]], ) files = extract_files(cast(Mapping[str, object], body), paths=[["image"]]) # It should be noted that the actual Content-Type header that will be @@ -114,58 +116,394 @@ def create_variation( body=maybe_transform(body, image_create_variation_params.ImageCreateVariationParams), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ImagesResponse, ) + @overload def edit( self, *, - image: FileTypes, + image: Union[FileTypes, SequenceNotStr[FileTypes]], prompt: str, - mask: FileTypes | NotGiven = NOT_GIVEN, - model: Union[str, ImageModel, None] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - response_format: Optional[Literal["url", "b64_json"]] | NotGiven = NOT_GIVEN, - size: Optional[Literal["256x256", "512x512", "1024x1024"]] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + input_fidelity: Optional[Literal["high", "low"]] | Omit = omit, + mask: FileTypes | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"], None] + | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: + """Creates an edited or extended image given one or more source images and a + prompt. + + This endpoint supports GPT Image models (`gpt-image-1.5`, `gpt-image-1`, + `gpt-image-1-mini`, and `chatgpt-image-latest`) and `dall-e-2`. + + Args: + image: The image(s) to edit. Must be a supported image file or an array of images. + + For the GPT image models (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, + `gpt-image-2`, `gpt-image-2-2026-04-21`, and `chatgpt-image-latest`), each image + should be a `png`, `webp`, or `jpg` file less than 50MB. You can provide up to + 16 images. + + For `dall-e-2`, you can only provide one image, and it should be a square `png` + file less than 4MB. + + prompt: A text description of the desired image(s). The maximum length is 1000 + characters for `dall-e-2`, and 32000 characters for the GPT image models. + + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + + input_fidelity: Control how much effort the model will exert to match the style and features, + especially facial features, of input images. This parameter is only supported + for `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for + `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`. + + mask: An additional image whose fully transparent areas (e.g. where alpha is zero) + indicate where `image` should be edited. If there are multiple images provided, + the mask will be applied on the first image. Must be a valid PNG file, less than + 4MB, and have the same dimensions as `image`. + + model: The model to use for image generation. One of `dall-e-2` or a GPT image model + (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + `gpt-image-2-2026-04-21`, or `chatgpt-image-latest`). Defaults to + `gpt-image-1.5`. + + n: The number of images to generate. Must be between 1 and 10. + + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. The + default value is `png`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated for GPT image models. Defaults + to `auto`. + + response_format: The format in which the generated images are returned. Must be one of `url` or + `b64_json`. URLs are only valid for 60 minutes after the image has been + generated. This parameter is only supported for `dall-e-2` (default is `url` for + `dall-e-2`), as GPT image models always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. + + stream: Edit the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. + + user: A unique identifier representing your end-user, which can help OpenAI to monitor + and detect abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds """ - Creates an edited or extended image given an original image and a prompt. + ... + + @overload + def edit( + self, + *, + image: Union[FileTypes, SequenceNotStr[FileTypes]], + prompt: str, + stream: Literal[True], + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + input_fidelity: Optional[Literal["high", "low"]] | Omit = omit, + mask: FileTypes | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"], None] + | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Stream[ImageEditStreamEvent]: + """Creates an edited or extended image given one or more source images and a + prompt. + + This endpoint supports GPT Image models (`gpt-image-1.5`, `gpt-image-1`, + `gpt-image-1-mini`, and `chatgpt-image-latest`) and `dall-e-2`. Args: - image: The image to edit. Must be a valid PNG file, less than 4MB, and square. If mask - is not provided, image must have transparency, which will be used as the mask. + image: The image(s) to edit. Must be a supported image file or an array of images. + + For the GPT image models (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, + `gpt-image-2`, `gpt-image-2-2026-04-21`, and `chatgpt-image-latest`), each image + should be a `png`, `webp`, or `jpg` file less than 50MB. You can provide up to + 16 images. + + For `dall-e-2`, you can only provide one image, and it should be a square `png` + file less than 4MB. prompt: A text description of the desired image(s). The maximum length is 1000 - characters. + characters for `dall-e-2`, and 32000 characters for the GPT image models. + + stream: Edit the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. + + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + + input_fidelity: Control how much effort the model will exert to match the style and features, + especially facial features, of input images. This parameter is only supported + for `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for + `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`. mask: An additional image whose fully transparent areas (e.g. where alpha is zero) - indicate where `image` should be edited. Must be a valid PNG file, less than + indicate where `image` should be edited. If there are multiple images provided, + the mask will be applied on the first image. Must be a valid PNG file, less than 4MB, and have the same dimensions as `image`. - model: The model to use for image generation. Only `dall-e-2` is supported at this - time. + model: The model to use for image generation. One of `dall-e-2` or a GPT image model + (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + `gpt-image-2-2026-04-21`, or `chatgpt-image-latest`). Defaults to + `gpt-image-1.5`. n: The number of images to generate. Must be between 1 and 10. + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. The + default value is `png`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated for GPT image models. Defaults + to `auto`. + response_format: The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been - generated. + generated. This parameter is only supported for `dall-e-2` (default is `url` for + `dall-e-2`), as GPT image models always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. - size: The size of the generated images. Must be one of `256x256`, `512x512`, or - `1024x1024`. + user: A unique identifier representing your end-user, which can help OpenAI to monitor + and detect abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def edit( + self, + *, + image: Union[FileTypes, SequenceNotStr[FileTypes]], + prompt: str, + stream: bool, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + input_fidelity: Optional[Literal["high", "low"]] | Omit = omit, + mask: FileTypes | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"], None] + | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ImagesResponse | Stream[ImageEditStreamEvent]: + """Creates an edited or extended image given one or more source images and a + prompt. + + This endpoint supports GPT Image models (`gpt-image-1.5`, `gpt-image-1`, + `gpt-image-1-mini`, and `chatgpt-image-latest`) and `dall-e-2`. + + Args: + image: The image(s) to edit. Must be a supported image file or an array of images. + + For the GPT image models (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, + `gpt-image-2`, `gpt-image-2-2026-04-21`, and `chatgpt-image-latest`), each image + should be a `png`, `webp`, or `jpg` file less than 50MB. You can provide up to + 16 images. + + For `dall-e-2`, you can only provide one image, and it should be a square `png` + file less than 4MB. + + prompt: A text description of the desired image(s). The maximum length is 1000 + characters for `dall-e-2`, and 32000 characters for the GPT image models. + + stream: Edit the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. + + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + + input_fidelity: Control how much effort the model will exert to match the style and features, + especially facial features, of input images. This parameter is only supported + for `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for + `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`. + + mask: An additional image whose fully transparent areas (e.g. where alpha is zero) + indicate where `image` should be edited. If there are multiple images provided, + the mask will be applied on the first image. Must be a valid PNG file, less than + 4MB, and have the same dimensions as `image`. + + model: The model to use for image generation. One of `dall-e-2` or a GPT image model + (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + `gpt-image-2-2026-04-21`, or `chatgpt-image-latest`). Defaults to + `gpt-image-1.5`. + + n: The number of images to generate. Must be between 1 and 10. + + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. The + default value is `png`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated for GPT image models. Defaults + to `auto`. + + response_format: The format in which the generated images are returned. Must be one of `url` or + `b64_json`. URLs are only valid for 60 minutes after the image has been + generated. This parameter is only supported for `dall-e-2` (default is `url` for + `dall-e-2`), as GPT image models always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -175,83 +513,446 @@ def edit( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + ... + + @required_args(["image", "prompt"], ["image", "prompt", "stream"]) + def edit( + self, + *, + image: Union[FileTypes, SequenceNotStr[FileTypes]], + prompt: str, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + input_fidelity: Optional[Literal["high", "low"]] | Omit = omit, + mask: FileTypes | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"], None] + | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ImagesResponse | Stream[ImageEditStreamEvent]: + body = deepcopy_with_paths( { "image": image, "prompt": prompt, + "background": background, + "input_fidelity": input_fidelity, "mask": mask, "model": model, "n": n, + "output_compression": output_compression, + "output_format": output_format, + "partial_images": partial_images, + "quality": quality, "response_format": response_format, "size": size, + "stream": stream, "user": user, - } + }, + [["image"], ["image", ""], ["mask"]], ) - files = extract_files(cast(Mapping[str, object], body), paths=[["image"], ["mask"]]) + files = extract_files(cast(Mapping[str, object], body), paths=[["image"], ["image", ""], ["mask"]]) # It should be noted that the actual Content-Type header that will be # sent to the server will contain a `boundary` parameter, e.g. # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return self._post( "/images/edits", - body=maybe_transform(body, image_edit_params.ImageEditParams), + body=maybe_transform( + body, + image_edit_params.ImageEditParamsStreaming if stream else image_edit_params.ImageEditParamsNonStreaming, + ), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ImagesResponse, + stream=stream or False, + stream_cls=Stream[ImageEditStreamEvent], ) + @overload def generate( self, *, prompt: str, - model: Union[str, ImageModel, None] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - quality: Literal["standard", "hd"] | NotGiven = NOT_GIVEN, - response_format: Optional[Literal["url", "b64_json"]] | NotGiven = NOT_GIVEN, - size: Optional[Literal["256x256", "512x512", "1024x1024", "1792x1024", "1024x1792"]] | NotGiven = NOT_GIVEN, - style: Optional[Literal["vivid", "natural"]] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + moderation: Optional[Literal["low", "auto"]] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "hd", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[ + str, + Literal["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792"], + None, + ] + | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + style: Optional[Literal["vivid", "natural"]] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: """ Creates an image given a prompt. + [Learn more](https://platform.openai.com/docs/guides/images). Args: - prompt: A text description of the desired image(s). The maximum length is 1000 - characters for `dall-e-2` and 4000 characters for `dall-e-3`. + prompt: A text description of the desired image(s). The maximum length is 32000 + characters for the GPT image models, 1000 characters for `dall-e-2` and 4000 + characters for `dall-e-3`. + + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. - model: The model to use for image generation. + model: The model to use for image generation. One of `dall-e-2`, `dall-e-3`, or a GPT + image model (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + or `gpt-image-2-2026-04-21`). Defaults to `dall-e-2` unless a parameter specific + to the GPT image models is used. + + moderation: Control the content-moderation level for images generated by the GPT image + models. Must be either `low` for less restrictive filtering or `auto` (default + value). n: The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only `n=1` is supported. - quality: The quality of the image that will be generated. `hd` creates images with finer - details and greater consistency across the image. This param is only supported - for `dall-e-3`. + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated. + + - `auto` (default value) will automatically select the best quality for the + given model. + - `high`, `medium` and `low` are supported for the GPT image models. + - `hd` and `standard` are supported for `dall-e-3`. + - `standard` is the only option for `dall-e-2`. + + response_format: The format in which generated images with `dall-e-2` and `dall-e-3` are + returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes + after the image has been generated. This parameter isn't supported for the GPT + image models, which always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. + + stream: Generate the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. This parameter is only supported for the GPT image models. + + style: The style of the generated images. This parameter is only supported for + `dall-e-3`. Must be one of `vivid` or `natural`. Vivid causes the model to lean + towards generating hyper-real and dramatic images. Natural causes the model to + produce more natural, less hyper-real looking images. - response_format: The format in which the generated images are returned. Must be one of `url` or - `b64_json`. URLs are only valid for 60 minutes after the image has been - generated. + user: A unique identifier representing your end-user, which can help OpenAI to monitor + and detect abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). - size: The size of the generated images. Must be one of `256x256`, `512x512`, or - `1024x1024` for `dall-e-2`. Must be one of `1024x1024`, `1792x1024`, or - `1024x1792` for `dall-e-3` models. + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def generate( + self, + *, + prompt: str, + stream: Literal[True], + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + moderation: Optional[Literal["low", "auto"]] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "hd", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[ + str, + Literal["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792"], + None, + ] + | Omit = omit, + style: Optional[Literal["vivid", "natural"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Stream[ImageGenStreamEvent]: + """ + Creates an image given a prompt. + [Learn more](https://platform.openai.com/docs/guides/images). + + Args: + prompt: A text description of the desired image(s). The maximum length is 32000 + characters for the GPT image models, 1000 characters for `dall-e-2` and 4000 + characters for `dall-e-3`. + + stream: Generate the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. This parameter is only supported for the GPT image models. - style: The style of the generated images. Must be one of `vivid` or `natural`. Vivid - causes the model to lean towards generating hyper-real and dramatic images. - Natural causes the model to produce more natural, less hyper-real looking - images. This param is only supported for `dall-e-3`. + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + + model: The model to use for image generation. One of `dall-e-2`, `dall-e-3`, or a GPT + image model (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + or `gpt-image-2-2026-04-21`). Defaults to `dall-e-2` unless a parameter specific + to the GPT image models is used. + + moderation: Control the content-moderation level for images generated by the GPT image + models. Must be either `low` for less restrictive filtering or `auto` (default + value). + + n: The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only + `n=1` is supported. + + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated. + + - `auto` (default value) will automatically select the best quality for the + given model. + - `high`, `medium` and `low` are supported for the GPT image models. + - `hd` and `standard` are supported for `dall-e-3`. + - `standard` is the only option for `dall-e-2`. + + response_format: The format in which generated images with `dall-e-2` and `dall-e-3` are + returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes + after the image has been generated. This parameter isn't supported for the GPT + image models, which always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. + + style: The style of the generated images. This parameter is only supported for + `dall-e-3`. Must be one of `vivid` or `natural`. Vivid causes the model to lean + towards generating hyper-real and dramatic images. Natural causes the model to + produce more natural, less hyper-real looking images. + + user: A unique identifier representing your end-user, which can help OpenAI to monitor + and detect abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def generate( + self, + *, + prompt: str, + stream: bool, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + moderation: Optional[Literal["low", "auto"]] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "hd", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[ + str, + Literal["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792"], + None, + ] + | Omit = omit, + style: Optional[Literal["vivid", "natural"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ImagesResponse | Stream[ImageGenStreamEvent]: + """ + Creates an image given a prompt. + [Learn more](https://platform.openai.com/docs/guides/images). + + Args: + prompt: A text description of the desired image(s). The maximum length is 32000 + characters for the GPT image models, 1000 characters for `dall-e-2` and 4000 + characters for `dall-e-3`. + + stream: Generate the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. This parameter is only supported for the GPT image models. + + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + + model: The model to use for image generation. One of `dall-e-2`, `dall-e-3`, or a GPT + image model (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + or `gpt-image-2-2026-04-21`). Defaults to `dall-e-2` unless a parameter specific + to the GPT image models is used. + + moderation: Control the content-moderation level for images generated by the GPT image + models. Must be either `low` for less restrictive filtering or `auto` (default + value). + + n: The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only + `n=1` is supported. + + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated. + + - `auto` (default value) will automatically select the best quality for the + given model. + - `high`, `medium` and `low` are supported for the GPT image models. + - `hd` and `standard` are supported for `dall-e-3`. + - `standard` is the only option for `dall-e-2`. + + response_format: The format in which generated images with `dall-e-2` and `dall-e-3` are + returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes + after the image has been generated. This parameter isn't supported for the GPT + image models, which always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. + + style: The style of the generated images. This parameter is only supported for + `dall-e-3`. Must be one of `vivid` or `natural`. Vivid causes the model to lean + towards generating hyper-real and dramatic images. Natural causes the model to + produce more natural, less hyper-real looking images. user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -261,33 +962,81 @@ def generate( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + @required_args(["prompt"], ["prompt", "stream"]) + def generate( + self, + *, + prompt: str, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + moderation: Optional[Literal["low", "auto"]] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "hd", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[ + str, + Literal["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792"], + None, + ] + | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + style: Optional[Literal["vivid", "natural"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ImagesResponse | Stream[ImageGenStreamEvent]: return self._post( "/images/generations", body=maybe_transform( { "prompt": prompt, + "background": background, "model": model, + "moderation": moderation, "n": n, + "output_compression": output_compression, + "output_format": output_format, + "partial_images": partial_images, "quality": quality, "response_format": response_format, "size": size, + "stream": stream, "style": style, "user": user, }, - image_generate_params.ImageGenerateParams, + image_generate_params.ImageGenerateParamsStreaming + if stream + else image_generate_params.ImageGenerateParamsNonStreaming, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ImagesResponse, + stream=stream or False, + stream_cls=Stream[ImageGenStreamEvent], ) class AsyncImages(AsyncAPIResource): + """Given a prompt and/or an input image, the model will generate a new image.""" + @cached_property def with_raw_response(self) -> AsyncImagesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -307,20 +1056,21 @@ async def create_variation( self, *, image: FileTypes, - model: Union[str, ImageModel, None] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - response_format: Optional[Literal["url", "b64_json"]] | NotGiven = NOT_GIVEN, - size: Optional[Literal["256x256", "512x512", "1024x1024"]] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + model: Union[str, ImageModel, None] | Omit = omit, + n: Optional[int] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Optional[Literal["256x256", "512x512", "1024x1024"]] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: - """ - Creates a variation of a given image. + """Creates a variation of a given image. + + This endpoint only supports `dall-e-2`. Args: image: The image to use as the basis for the variation(s). Must be a valid PNG file, @@ -329,8 +1079,7 @@ async def create_variation( model: The model to use for image generation. Only `dall-e-2` is supported at this time. - n: The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only - `n=1` is supported. + n: The number of images to generate. Must be between 1 and 10. response_format: The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been @@ -341,7 +1090,7 @@ async def create_variation( user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -351,7 +1100,7 @@ async def create_variation( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + body = deepcopy_with_paths( { "image": image, "model": model, @@ -359,7 +1108,8 @@ async def create_variation( "response_format": response_format, "size": size, "user": user, - } + }, + [["image"]], ) files = extract_files(cast(Mapping[str, object], body), paths=[["image"]]) # It should be noted that the actual Content-Type header that will be @@ -371,58 +1121,394 @@ async def create_variation( body=await async_maybe_transform(body, image_create_variation_params.ImageCreateVariationParams), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ImagesResponse, ) + @overload async def edit( self, *, - image: FileTypes, + image: Union[FileTypes, SequenceNotStr[FileTypes]], prompt: str, - mask: FileTypes | NotGiven = NOT_GIVEN, - model: Union[str, ImageModel, None] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - response_format: Optional[Literal["url", "b64_json"]] | NotGiven = NOT_GIVEN, - size: Optional[Literal["256x256", "512x512", "1024x1024"]] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + input_fidelity: Optional[Literal["high", "low"]] | Omit = omit, + mask: FileTypes | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"], None] + | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: + """Creates an edited or extended image given one or more source images and a + prompt. + + This endpoint supports GPT Image models (`gpt-image-1.5`, `gpt-image-1`, + `gpt-image-1-mini`, and `chatgpt-image-latest`) and `dall-e-2`. + + Args: + image: The image(s) to edit. Must be a supported image file or an array of images. + + For the GPT image models (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, + `gpt-image-2`, `gpt-image-2-2026-04-21`, and `chatgpt-image-latest`), each image + should be a `png`, `webp`, or `jpg` file less than 50MB. You can provide up to + 16 images. + + For `dall-e-2`, you can only provide one image, and it should be a square `png` + file less than 4MB. + + prompt: A text description of the desired image(s). The maximum length is 1000 + characters for `dall-e-2`, and 32000 characters for the GPT image models. + + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + + input_fidelity: Control how much effort the model will exert to match the style and features, + especially facial features, of input images. This parameter is only supported + for `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for + `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`. + + mask: An additional image whose fully transparent areas (e.g. where alpha is zero) + indicate where `image` should be edited. If there are multiple images provided, + the mask will be applied on the first image. Must be a valid PNG file, less than + 4MB, and have the same dimensions as `image`. + + model: The model to use for image generation. One of `dall-e-2` or a GPT image model + (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + `gpt-image-2-2026-04-21`, or `chatgpt-image-latest`). Defaults to + `gpt-image-1.5`. + + n: The number of images to generate. Must be between 1 and 10. + + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. The + default value is `png`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated for GPT image models. Defaults + to `auto`. + + response_format: The format in which the generated images are returned. Must be one of `url` or + `b64_json`. URLs are only valid for 60 minutes after the image has been + generated. This parameter is only supported for `dall-e-2` (default is `url` for + `dall-e-2`), as GPT image models always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. + + stream: Edit the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. + + user: A unique identifier representing your end-user, which can help OpenAI to monitor + and detect abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds """ - Creates an edited or extended image given an original image and a prompt. + ... + + @overload + async def edit( + self, + *, + image: Union[FileTypes, SequenceNotStr[FileTypes]], + prompt: str, + stream: Literal[True], + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + input_fidelity: Optional[Literal["high", "low"]] | Omit = omit, + mask: FileTypes | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"], None] + | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncStream[ImageEditStreamEvent]: + """Creates an edited or extended image given one or more source images and a + prompt. + + This endpoint supports GPT Image models (`gpt-image-1.5`, `gpt-image-1`, + `gpt-image-1-mini`, and `chatgpt-image-latest`) and `dall-e-2`. Args: - image: The image to edit. Must be a valid PNG file, less than 4MB, and square. If mask - is not provided, image must have transparency, which will be used as the mask. + image: The image(s) to edit. Must be a supported image file or an array of images. + + For the GPT image models (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, + `gpt-image-2`, `gpt-image-2-2026-04-21`, and `chatgpt-image-latest`), each image + should be a `png`, `webp`, or `jpg` file less than 50MB. You can provide up to + 16 images. + + For `dall-e-2`, you can only provide one image, and it should be a square `png` + file less than 4MB. prompt: A text description of the desired image(s). The maximum length is 1000 - characters. + characters for `dall-e-2`, and 32000 characters for the GPT image models. + + stream: Edit the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. + + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + + input_fidelity: Control how much effort the model will exert to match the style and features, + especially facial features, of input images. This parameter is only supported + for `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for + `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`. mask: An additional image whose fully transparent areas (e.g. where alpha is zero) - indicate where `image` should be edited. Must be a valid PNG file, less than + indicate where `image` should be edited. If there are multiple images provided, + the mask will be applied on the first image. Must be a valid PNG file, less than 4MB, and have the same dimensions as `image`. - model: The model to use for image generation. Only `dall-e-2` is supported at this - time. + model: The model to use for image generation. One of `dall-e-2` or a GPT image model + (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + `gpt-image-2-2026-04-21`, or `chatgpt-image-latest`). Defaults to + `gpt-image-1.5`. n: The number of images to generate. Must be between 1 and 10. + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. The + default value is `png`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated for GPT image models. Defaults + to `auto`. + response_format: The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been - generated. + generated. This parameter is only supported for `dall-e-2` (default is `url` for + `dall-e-2`), as GPT image models always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. - size: The size of the generated images. Must be one of `256x256`, `512x512`, or - `1024x1024`. + user: A unique identifier representing your end-user, which can help OpenAI to monitor + and detect abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def edit( + self, + *, + image: Union[FileTypes, SequenceNotStr[FileTypes]], + prompt: str, + stream: bool, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + input_fidelity: Optional[Literal["high", "low"]] | Omit = omit, + mask: FileTypes | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"], None] + | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ImagesResponse | AsyncStream[ImageEditStreamEvent]: + """Creates an edited or extended image given one or more source images and a + prompt. + + This endpoint supports GPT Image models (`gpt-image-1.5`, `gpt-image-1`, + `gpt-image-1-mini`, and `chatgpt-image-latest`) and `dall-e-2`. + + Args: + image: The image(s) to edit. Must be a supported image file or an array of images. + + For the GPT image models (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, + `gpt-image-2`, `gpt-image-2-2026-04-21`, and `chatgpt-image-latest`), each image + should be a `png`, `webp`, or `jpg` file less than 50MB. You can provide up to + 16 images. + + For `dall-e-2`, you can only provide one image, and it should be a square `png` + file less than 4MB. + + prompt: A text description of the desired image(s). The maximum length is 1000 + characters for `dall-e-2`, and 32000 characters for the GPT image models. + + stream: Edit the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. + + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + + input_fidelity: Control how much effort the model will exert to match the style and features, + especially facial features, of input images. This parameter is only supported + for `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for + `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`. + + mask: An additional image whose fully transparent areas (e.g. where alpha is zero) + indicate where `image` should be edited. If there are multiple images provided, + the mask will be applied on the first image. Must be a valid PNG file, less than + 4MB, and have the same dimensions as `image`. + + model: The model to use for image generation. One of `dall-e-2` or a GPT image model + (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + `gpt-image-2-2026-04-21`, or `chatgpt-image-latest`). Defaults to + `gpt-image-1.5`. + + n: The number of images to generate. Must be between 1 and 10. + + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. The + default value is `png`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated for GPT image models. Defaults + to `auto`. + + response_format: The format in which the generated images are returned. Must be one of `url` or + `b64_json`. URLs are only valid for 60 minutes after the image has been + generated. This parameter is only supported for `dall-e-2` (default is `url` for + `dall-e-2`), as GPT image models always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -432,83 +1518,446 @@ async def edit( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + ... + + @required_args(["image", "prompt"], ["image", "prompt", "stream"]) + async def edit( + self, + *, + image: Union[FileTypes, SequenceNotStr[FileTypes]], + prompt: str, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + input_fidelity: Optional[Literal["high", "low"]] | Omit = omit, + mask: FileTypes | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"], None] + | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ImagesResponse | AsyncStream[ImageEditStreamEvent]: + body = deepcopy_with_paths( { "image": image, "prompt": prompt, + "background": background, + "input_fidelity": input_fidelity, "mask": mask, "model": model, "n": n, + "output_compression": output_compression, + "output_format": output_format, + "partial_images": partial_images, + "quality": quality, "response_format": response_format, "size": size, + "stream": stream, "user": user, - } + }, + [["image"], ["image", ""], ["mask"]], ) - files = extract_files(cast(Mapping[str, object], body), paths=[["image"], ["mask"]]) + files = extract_files(cast(Mapping[str, object], body), paths=[["image"], ["image", ""], ["mask"]]) # It should be noted that the actual Content-Type header that will be # sent to the server will contain a `boundary` parameter, e.g. # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return await self._post( "/images/edits", - body=await async_maybe_transform(body, image_edit_params.ImageEditParams), + body=await async_maybe_transform( + body, + image_edit_params.ImageEditParamsStreaming if stream else image_edit_params.ImageEditParamsNonStreaming, + ), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ImagesResponse, + stream=stream or False, + stream_cls=AsyncStream[ImageEditStreamEvent], ) + @overload async def generate( self, *, prompt: str, - model: Union[str, ImageModel, None] | NotGiven = NOT_GIVEN, - n: Optional[int] | NotGiven = NOT_GIVEN, - quality: Literal["standard", "hd"] | NotGiven = NOT_GIVEN, - response_format: Optional[Literal["url", "b64_json"]] | NotGiven = NOT_GIVEN, - size: Optional[Literal["256x256", "512x512", "1024x1024", "1792x1024", "1024x1792"]] | NotGiven = NOT_GIVEN, - style: Optional[Literal["vivid", "natural"]] | NotGiven = NOT_GIVEN, - user: str | NotGiven = NOT_GIVEN, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + moderation: Optional[Literal["low", "auto"]] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "hd", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[ + str, + Literal["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792"], + None, + ] + | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + style: Optional[Literal["vivid", "natural"]] | Omit = omit, + user: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ImagesResponse: """ Creates an image given a prompt. + [Learn more](https://platform.openai.com/docs/guides/images). Args: - prompt: A text description of the desired image(s). The maximum length is 1000 - characters for `dall-e-2` and 4000 characters for `dall-e-3`. + prompt: A text description of the desired image(s). The maximum length is 32000 + characters for the GPT image models, 1000 characters for `dall-e-2` and 4000 + characters for `dall-e-3`. + + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. - model: The model to use for image generation. + model: The model to use for image generation. One of `dall-e-2`, `dall-e-3`, or a GPT + image model (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + or `gpt-image-2-2026-04-21`). Defaults to `dall-e-2` unless a parameter specific + to the GPT image models is used. + + moderation: Control the content-moderation level for images generated by the GPT image + models. Must be either `low` for less restrictive filtering or `auto` (default + value). n: The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only `n=1` is supported. - quality: The quality of the image that will be generated. `hd` creates images with finer - details and greater consistency across the image. This param is only supported - for `dall-e-3`. + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated. + + - `auto` (default value) will automatically select the best quality for the + given model. + - `high`, `medium` and `low` are supported for the GPT image models. + - `hd` and `standard` are supported for `dall-e-3`. + - `standard` is the only option for `dall-e-2`. + + response_format: The format in which generated images with `dall-e-2` and `dall-e-3` are + returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes + after the image has been generated. This parameter isn't supported for the GPT + image models, which always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. + + stream: Generate the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. This parameter is only supported for the GPT image models. + + style: The style of the generated images. This parameter is only supported for + `dall-e-3`. Must be one of `vivid` or `natural`. Vivid causes the model to lean + towards generating hyper-real and dramatic images. Natural causes the model to + produce more natural, less hyper-real looking images. - response_format: The format in which the generated images are returned. Must be one of `url` or - `b64_json`. URLs are only valid for 60 minutes after the image has been - generated. + user: A unique identifier representing your end-user, which can help OpenAI to monitor + and detect abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). - size: The size of the generated images. Must be one of `256x256`, `512x512`, or - `1024x1024` for `dall-e-2`. Must be one of `1024x1024`, `1792x1024`, or - `1024x1792` for `dall-e-3` models. + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def generate( + self, + *, + prompt: str, + stream: Literal[True], + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + moderation: Optional[Literal["low", "auto"]] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "hd", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[ + str, + Literal["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792"], + None, + ] + | Omit = omit, + style: Optional[Literal["vivid", "natural"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncStream[ImageGenStreamEvent]: + """ + Creates an image given a prompt. + [Learn more](https://platform.openai.com/docs/guides/images). + + Args: + prompt: A text description of the desired image(s). The maximum length is 32000 + characters for the GPT image models, 1000 characters for `dall-e-2` and 4000 + characters for `dall-e-3`. + + stream: Generate the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. This parameter is only supported for the GPT image models. - style: The style of the generated images. Must be one of `vivid` or `natural`. Vivid - causes the model to lean towards generating hyper-real and dramatic images. - Natural causes the model to produce more natural, less hyper-real looking - images. This param is only supported for `dall-e-3`. + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + + model: The model to use for image generation. One of `dall-e-2`, `dall-e-3`, or a GPT + image model (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + or `gpt-image-2-2026-04-21`). Defaults to `dall-e-2` unless a parameter specific + to the GPT image models is used. + + moderation: Control the content-moderation level for images generated by the GPT image + models. Must be either `low` for less restrictive filtering or `auto` (default + value). + + n: The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only + `n=1` is supported. + + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated. + + - `auto` (default value) will automatically select the best quality for the + given model. + - `high`, `medium` and `low` are supported for the GPT image models. + - `hd` and `standard` are supported for `dall-e-3`. + - `standard` is the only option for `dall-e-2`. + + response_format: The format in which generated images with `dall-e-2` and `dall-e-3` are + returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes + after the image has been generated. This parameter isn't supported for the GPT + image models, which always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. + + style: The style of the generated images. This parameter is only supported for + `dall-e-3`. Must be one of `vivid` or `natural`. Vivid causes the model to lean + towards generating hyper-real and dramatic images. Natural causes the model to + produce more natural, less hyper-real looking images. + + user: A unique identifier representing your end-user, which can help OpenAI to monitor + and detect abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def generate( + self, + *, + prompt: str, + stream: bool, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + moderation: Optional[Literal["low", "auto"]] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "hd", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[ + str, + Literal["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792"], + None, + ] + | Omit = omit, + style: Optional[Literal["vivid", "natural"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ImagesResponse | AsyncStream[ImageGenStreamEvent]: + """ + Creates an image given a prompt. + [Learn more](https://platform.openai.com/docs/guides/images). + + Args: + prompt: A text description of the desired image(s). The maximum length is 32000 + characters for the GPT image models, 1000 characters for `dall-e-2` and 4000 + characters for `dall-e-3`. + + stream: Generate the image in streaming mode. Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. This parameter is only supported for the GPT image models. + + background: Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + + model: The model to use for image generation. One of `dall-e-2`, `dall-e-3`, or a GPT + image model (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, + or `gpt-image-2-2026-04-21`). Defaults to `dall-e-2` unless a parameter specific + to the GPT image models is used. + + moderation: Control the content-moderation level for images generated by the GPT image + models. Must be either `low` for less restrictive filtering or `auto` (default + value). + + n: The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only + `n=1` is supported. + + output_compression: The compression level (0-100%) for the generated images. This parameter is only + supported for the GPT image models with the `webp` or `jpeg` output formats, and + defaults to 100. + + output_format: The format in which the generated images are returned. This parameter is only + supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`. + + partial_images: The number of partial images to generate. This parameter is used for streaming + responses that return partial images. Value must be between 0 and 3. When set to + 0, the response will be a single image sent in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + + quality: The quality of the image that will be generated. + + - `auto` (default value) will automatically select the best quality for the + given model. + - `high`, `medium` and `low` are supported for the GPT image models. + - `hd` and `standard` are supported for `dall-e-3`. + - `standard` is the only option for `dall-e-2`. + + response_format: The format in which generated images with `dall-e-2` and `dall-e-3` are + returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes + after the image has been generated. This parameter isn't supported for the GPT + image models, which always return base64-encoded images. + + size: The size of the generated images. For `gpt-image-2` and + `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` + strings, for example `1536x864`. Width and height must both be divisible by 16 + and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above + `2560x1440` are experimental, and the maximum supported resolution is + `3840x2160`. The requested size must also satisfy the model's current pixel and + edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are + supported by the GPT image models; `auto` is supported for models that allow + automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or + `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or + `1024x1792`. + + style: The style of the generated images. This parameter is only supported for + `dall-e-3`. Must be one of `vivid` or `natural`. Vivid causes the model to lean + towards generating hyper-real and dramatic images. Natural causes the model to + produce more natural, less hyper-real looking images. user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). extra_headers: Send extra headers @@ -518,25 +1967,71 @@ async def generate( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + @required_args(["prompt"], ["prompt", "stream"]) + async def generate( + self, + *, + prompt: str, + background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit, + model: Union[str, ImageModel, None] | Omit = omit, + moderation: Optional[Literal["low", "auto"]] | Omit = omit, + n: Optional[int] | Omit = omit, + output_compression: Optional[int] | Omit = omit, + output_format: Optional[Literal["png", "jpeg", "webp"]] | Omit = omit, + partial_images: Optional[int] | Omit = omit, + quality: Optional[Literal["standard", "hd", "low", "medium", "high", "auto"]] | Omit = omit, + response_format: Optional[Literal["url", "b64_json"]] | Omit = omit, + size: Union[ + str, + Literal["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792"], + None, + ] + | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + style: Optional[Literal["vivid", "natural"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ImagesResponse | AsyncStream[ImageGenStreamEvent]: return await self._post( "/images/generations", body=await async_maybe_transform( { "prompt": prompt, + "background": background, "model": model, + "moderation": moderation, "n": n, + "output_compression": output_compression, + "output_format": output_format, + "partial_images": partial_images, "quality": quality, "response_format": response_format, "size": size, + "stream": stream, "style": style, "user": user, }, - image_generate_params.ImageGenerateParams, + image_generate_params.ImageGenerateParamsStreaming + if stream + else image_generate_params.ImageGenerateParamsNonStreaming, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ImagesResponse, + stream=stream or False, + stream_cls=AsyncStream[ImageGenStreamEvent], ) diff --git a/src/openai/resources/models.py b/src/openai/resources/models.py index d6062de230..a68fd83360 100644 --- a/src/openai/resources/models.py +++ b/src/openai/resources/models.py @@ -5,7 +5,8 @@ import httpx from .. import _legacy_response -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -21,10 +22,12 @@ class Models(SyncAPIResource): + """List and describe the various models available in the API.""" + @cached_property def with_raw_response(self) -> ModelsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -49,7 +52,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Model: """ Retrieves a model instance, providing basic information about the model such as @@ -67,9 +70,13 @@ def retrieve( if not model: raise ValueError(f"Expected a non-empty value for `model` but received {model!r}") return self._get( - f"/models/{model}", + path_template("/models/{model}", model=model), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Model, ) @@ -82,7 +89,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncPage[Model]: """ Lists the currently available models, and provides basic information about each @@ -92,7 +99,11 @@ def list( "/models", page=SyncPage[Model], options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), model=Model, ) @@ -106,7 +117,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ModelDeleted: """Delete a fine-tuned model. @@ -125,19 +136,25 @@ def delete( if not model: raise ValueError(f"Expected a non-empty value for `model` but received {model!r}") return self._delete( - f"/models/{model}", + path_template("/models/{model}", model=model), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ModelDeleted, ) class AsyncModels(AsyncAPIResource): + """List and describe the various models available in the API.""" + @cached_property def with_raw_response(self) -> AsyncModelsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -162,7 +179,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Model: """ Retrieves a model instance, providing basic information about the model such as @@ -180,9 +197,13 @@ async def retrieve( if not model: raise ValueError(f"Expected a non-empty value for `model` but received {model!r}") return await self._get( - f"/models/{model}", + path_template("/models/{model}", model=model), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Model, ) @@ -195,7 +216,7 @@ def list( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Model, AsyncPage[Model]]: """ Lists the currently available models, and provides basic information about each @@ -205,7 +226,11 @@ def list( "/models", page=AsyncPage[Model], options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), model=Model, ) @@ -219,7 +244,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ModelDeleted: """Delete a fine-tuned model. @@ -238,9 +263,13 @@ async def delete( if not model: raise ValueError(f"Expected a non-empty value for `model` but received {model!r}") return await self._delete( - f"/models/{model}", + path_template("/models/{model}", model=model), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ModelDeleted, ) diff --git a/src/openai/resources/moderations.py b/src/openai/resources/moderations.py index 5283554373..5ef4efeaa5 100644 --- a/src/openai/resources/moderations.py +++ b/src/openai/resources/moderations.py @@ -2,32 +2,34 @@ from __future__ import annotations -from typing import List, Union +from typing import Union, Iterable import httpx from .. import _legacy_response from ..types import moderation_create_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .._base_client import make_request_options from ..types.moderation_model import ModerationModel from ..types.moderation_create_response import ModerationCreateResponse +from ..types.moderation_multi_modal_input_param import ModerationMultiModalInputParam __all__ = ["Moderations", "AsyncModerations"] class Moderations(SyncAPIResource): + """ + Given text and/or image inputs, classifies if those inputs are potentially harmful. + """ + @cached_property def with_raw_response(self) -> ModerationsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -46,29 +48,28 @@ def with_streaming_response(self) -> ModerationsWithStreamingResponse: def create( self, *, - input: Union[str, List[str]], - model: Union[str, ModerationModel] | NotGiven = NOT_GIVEN, + input: Union[str, SequenceNotStr[str], Iterable[ModerationMultiModalInputParam]], + model: Union[str, ModerationModel] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ModerationCreateResponse: - """ - Classifies if text is potentially harmful. + """Classifies if text and/or image inputs are potentially harmful. - Args: - input: The input text to classify + Learn more in + the [moderation guide](https://platform.openai.com/docs/guides/moderation). - model: Two content moderations models are available: `text-moderation-stable` and - `text-moderation-latest`. + Args: + input: Input (or inputs) to classify. Can be a single string, an array of strings, or + an array of multi-modal input objects similar to other models. - The default is `text-moderation-latest` which will be automatically upgraded - over time. This ensures you are always using our most accurate model. If you use - `text-moderation-stable`, we will provide advanced notice before updating the - model. Accuracy of `text-moderation-stable` may be slightly lower than for - `text-moderation-latest`. + model: The content moderation model you would like to use. Learn more in + [the moderation guide](https://platform.openai.com/docs/guides/moderation), and + learn about available models + [here](https://platform.openai.com/docs/models#moderation). extra_headers: Send extra headers @@ -88,17 +89,25 @@ def create( moderation_create_params.ModerationCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ModerationCreateResponse, ) class AsyncModerations(AsyncAPIResource): + """ + Given text and/or image inputs, classifies if those inputs are potentially harmful. + """ + @cached_property def with_raw_response(self) -> AsyncModerationsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -117,29 +126,28 @@ def with_streaming_response(self) -> AsyncModerationsWithStreamingResponse: async def create( self, *, - input: Union[str, List[str]], - model: Union[str, ModerationModel] | NotGiven = NOT_GIVEN, + input: Union[str, SequenceNotStr[str], Iterable[ModerationMultiModalInputParam]], + model: Union[str, ModerationModel] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ModerationCreateResponse: - """ - Classifies if text is potentially harmful. + """Classifies if text and/or image inputs are potentially harmful. - Args: - input: The input text to classify + Learn more in + the [moderation guide](https://platform.openai.com/docs/guides/moderation). - model: Two content moderations models are available: `text-moderation-stable` and - `text-moderation-latest`. + Args: + input: Input (or inputs) to classify. Can be a single string, an array of strings, or + an array of multi-modal input objects similar to other models. - The default is `text-moderation-latest` which will be automatically upgraded - over time. This ensures you are always using our most accurate model. If you use - `text-moderation-stable`, we will provide advanced notice before updating the - model. Accuracy of `text-moderation-stable` may be slightly lower than for - `text-moderation-latest`. + model: The content moderation model you would like to use. Learn more in + [the moderation guide](https://platform.openai.com/docs/guides/moderation), and + learn about available models + [here](https://platform.openai.com/docs/models#moderation). extra_headers: Send extra headers @@ -159,7 +167,11 @@ async def create( moderation_create_params.ModerationCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=ModerationCreateResponse, ) diff --git a/src/openai/resources/realtime/__init__.py b/src/openai/resources/realtime/__init__.py new file mode 100644 index 0000000000..c11841017f --- /dev/null +++ b/src/openai/resources/realtime/__init__.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .calls import ( + Calls, + AsyncCalls, + CallsWithRawResponse, + AsyncCallsWithRawResponse, + CallsWithStreamingResponse, + AsyncCallsWithStreamingResponse, +) +from .realtime import ( + Realtime, + AsyncRealtime, + RealtimeWithRawResponse, + AsyncRealtimeWithRawResponse, + RealtimeWithStreamingResponse, + AsyncRealtimeWithStreamingResponse, +) +from .client_secrets import ( + ClientSecrets, + AsyncClientSecrets, + ClientSecretsWithRawResponse, + AsyncClientSecretsWithRawResponse, + ClientSecretsWithStreamingResponse, + AsyncClientSecretsWithStreamingResponse, +) + +__all__ = [ + "ClientSecrets", + "AsyncClientSecrets", + "ClientSecretsWithRawResponse", + "AsyncClientSecretsWithRawResponse", + "ClientSecretsWithStreamingResponse", + "AsyncClientSecretsWithStreamingResponse", + "Calls", + "AsyncCalls", + "CallsWithRawResponse", + "AsyncCallsWithRawResponse", + "CallsWithStreamingResponse", + "AsyncCallsWithStreamingResponse", + "Realtime", + "AsyncRealtime", + "RealtimeWithRawResponse", + "AsyncRealtimeWithRawResponse", + "RealtimeWithStreamingResponse", + "AsyncRealtimeWithStreamingResponse", +] diff --git a/src/openai/resources/realtime/api.md b/src/openai/resources/realtime/api.md new file mode 100644 index 0000000000..2be1b85cbf --- /dev/null +++ b/src/openai/resources/realtime/api.md @@ -0,0 +1,154 @@ +# Realtime + +Types: + +```python +from openai.types.realtime import ( + AudioTranscription, + ConversationCreatedEvent, + ConversationItem, + ConversationItemAdded, + ConversationItemCreateEvent, + ConversationItemCreatedEvent, + ConversationItemDeleteEvent, + ConversationItemDeletedEvent, + ConversationItemDone, + ConversationItemInputAudioTranscriptionCompletedEvent, + ConversationItemInputAudioTranscriptionDeltaEvent, + ConversationItemInputAudioTranscriptionFailedEvent, + ConversationItemInputAudioTranscriptionSegment, + ConversationItemRetrieveEvent, + ConversationItemTruncateEvent, + ConversationItemTruncatedEvent, + ConversationItemWithReference, + InputAudioBufferAppendEvent, + InputAudioBufferClearEvent, + InputAudioBufferClearedEvent, + InputAudioBufferCommitEvent, + InputAudioBufferCommittedEvent, + InputAudioBufferDtmfEventReceivedEvent, + InputAudioBufferSpeechStartedEvent, + InputAudioBufferSpeechStoppedEvent, + InputAudioBufferTimeoutTriggered, + LogProbProperties, + McpListToolsCompleted, + McpListToolsFailed, + McpListToolsInProgress, + NoiseReductionType, + OutputAudioBufferClearEvent, + RateLimitsUpdatedEvent, + RealtimeAudioConfig, + RealtimeAudioConfigInput, + RealtimeAudioConfigOutput, + RealtimeAudioFormats, + RealtimeAudioInputTurnDetection, + RealtimeClientEvent, + RealtimeConversationItemAssistantMessage, + RealtimeConversationItemFunctionCall, + RealtimeConversationItemFunctionCallOutput, + RealtimeConversationItemSystemMessage, + RealtimeConversationItemUserMessage, + RealtimeError, + RealtimeErrorEvent, + RealtimeFunctionTool, + RealtimeMcpApprovalRequest, + RealtimeMcpApprovalResponse, + RealtimeMcpListTools, + RealtimeMcpProtocolError, + RealtimeMcpToolCall, + RealtimeMcpToolExecutionError, + RealtimeMcphttpError, + RealtimeReasoning, + RealtimeReasoningEffort, + RealtimeResponse, + RealtimeResponseCreateAudioOutput, + RealtimeResponseCreateMcpTool, + RealtimeResponseCreateParams, + RealtimeResponseStatus, + RealtimeResponseUsage, + RealtimeResponseUsageInputTokenDetails, + RealtimeResponseUsageOutputTokenDetails, + RealtimeServerEvent, + RealtimeSession, + RealtimeSessionCreateRequest, + RealtimeToolChoiceConfig, + RealtimeToolsConfig, + RealtimeToolsConfigUnion, + RealtimeTracingConfig, + RealtimeTranscriptionSessionAudio, + RealtimeTranscriptionSessionAudioInput, + RealtimeTranscriptionSessionAudioInputTurnDetection, + RealtimeTranscriptionSessionCreateRequest, + RealtimeTranslationClientEvent, + RealtimeTranslationClientSecretCreateRequest, + RealtimeTranslationClientSecretCreateResponse, + RealtimeTranslationInputAudioBufferAppendEvent, + RealtimeTranslationInputTranscriptDeltaEvent, + RealtimeTranslationOutputAudioDeltaEvent, + RealtimeTranslationOutputTranscriptDeltaEvent, + RealtimeTranslationServerEvent, + RealtimeTranslationSession, + RealtimeTranslationSessionCloseEvent, + RealtimeTranslationSessionClosedEvent, + RealtimeTranslationSessionCreateRequest, + RealtimeTranslationSessionCreatedEvent, + RealtimeTranslationSessionUpdateEvent, + RealtimeTranslationSessionUpdateRequest, + RealtimeTranslationSessionUpdatedEvent, + RealtimeTruncation, + RealtimeTruncationRetentionRatio, + ResponseAudioDeltaEvent, + ResponseAudioDoneEvent, + ResponseAudioTranscriptDeltaEvent, + ResponseAudioTranscriptDoneEvent, + ResponseCancelEvent, + ResponseContentPartAddedEvent, + ResponseContentPartDoneEvent, + ResponseCreateEvent, + ResponseCreatedEvent, + ResponseDoneEvent, + ResponseFunctionCallArgumentsDeltaEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseMcpCallArgumentsDelta, + ResponseMcpCallArgumentsDone, + ResponseMcpCallCompleted, + ResponseMcpCallFailed, + ResponseMcpCallInProgress, + ResponseOutputItemAddedEvent, + ResponseOutputItemDoneEvent, + ResponseTextDeltaEvent, + ResponseTextDoneEvent, + SessionCreatedEvent, + SessionUpdateEvent, + SessionUpdatedEvent, + TranscriptionSessionUpdate, + TranscriptionSessionUpdatedEvent, +) +``` + +## ClientSecrets + +Types: + +```python +from openai.types.realtime import ( + RealtimeSessionCreateResponse, + RealtimeTranscriptionSessionCreateResponse, + RealtimeTranscriptionSessionTurnDetection, + ClientSecretCreateResponse, +) +``` + +Methods: + +- client.realtime.client_secrets.create(\*\*params) -> ClientSecretCreateResponse + +## Calls + +Methods: + +- client.realtime.calls.create(\*\*params) -> HttpxBinaryResponseContent +- client.realtime.calls.accept(call_id, \*\*params) -> None +- client.realtime.calls.hangup(call_id) -> None +- client.realtime.calls.refer(call_id, \*\*params) -> None +- client.realtime.calls.reject(call_id, \*\*params) -> None diff --git a/src/openai/resources/realtime/calls.py b/src/openai/resources/realtime/calls.py new file mode 100644 index 0000000000..0674b2b010 --- /dev/null +++ b/src/openai/resources/realtime/calls.py @@ -0,0 +1,845 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from typing_extensions import Literal + +import httpx + +from ... import _legacy_response +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, + to_streamed_response_wrapper, + async_to_streamed_response_wrapper, + to_custom_streamed_response_wrapper, + async_to_custom_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.realtime import ( + call_refer_params, + call_accept_params, + call_create_params, + call_reject_params, +) +from ...types.responses.response_prompt_param import ResponsePromptParam +from ...types.realtime.realtime_reasoning_param import RealtimeReasoningParam +from ...types.realtime.realtime_truncation_param import RealtimeTruncationParam +from ...types.realtime.realtime_audio_config_param import RealtimeAudioConfigParam +from ...types.realtime.realtime_tools_config_param import RealtimeToolsConfigParam +from ...types.realtime.realtime_tracing_config_param import RealtimeTracingConfigParam +from ...types.realtime.realtime_tool_choice_config_param import RealtimeToolChoiceConfigParam +from ...types.realtime.realtime_session_create_request_param import RealtimeSessionCreateRequestParam + +__all__ = ["Calls", "AsyncCalls"] + + +class Calls(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CallsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return CallsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CallsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return CallsWithStreamingResponse(self) + + def create( + self, + *, + sdp: str, + session: RealtimeSessionCreateRequestParam | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + """ + Create a new Realtime API call over WebRTC and receive the SDP answer needed to + complete the peer connection. + + Args: + sdp: WebRTC Session Description Protocol (SDP) offer generated by the caller. + + session: Realtime session object configuration. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"Accept": "application/sdp", **(extra_headers or {})} + return self._post( + "/realtime/calls", + body=maybe_transform( + { + "sdp": sdp, + "session": session, + }, + call_create_params.CallCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + def accept( + self, + call_id: str, + *, + type: Literal["realtime"], + audio: RealtimeAudioConfigParam | Omit = omit, + include: List[Literal["item.input_audio_transcription.logprobs"]] | Omit = omit, + instructions: str | Omit = omit, + max_output_tokens: Union[int, Literal["inf"]] | Omit = omit, + model: Union[ + str, + Literal[ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15", + ], + ] + | Omit = omit, + output_modalities: List[Literal["text", "audio"]] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + reasoning: RealtimeReasoningParam | Omit = omit, + tool_choice: RealtimeToolChoiceConfigParam | Omit = omit, + tools: RealtimeToolsConfigParam | Omit = omit, + tracing: Optional[RealtimeTracingConfigParam] | Omit = omit, + truncation: RealtimeTruncationParam | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Accept an incoming SIP call and configure the realtime session that will handle + it. + + Args: + type: The type of session to create. Always `realtime` for the Realtime API. + + audio: Configuration for input and output audio. + + include: Additional fields to include in server outputs. + + `item.input_audio_transcription.logprobs`: Include logprobs for input audio + transcription. + + instructions: The default system instructions (i.e. system message) prepended to model calls. + This field allows the client to guide the model on desired responses. The model + can be instructed on response content and format, (e.g. "be extremely succinct", + "act friendly", "here are examples of good responses") and on audio behavior + (e.g. "talk quickly", "inject emotion into your voice", "laugh frequently"). The + instructions are not guaranteed to be followed by the model, but they provide + guidance to the model on the desired behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + + max_output_tokens: Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + + model: The Realtime model used for this session. + + output_modalities: The set of modalities the model can respond with. It defaults to `["audio"]`, + indicating that the model will respond with audio plus a transcript. `["text"]` + can be used to make the model respond with text only. It is not possible to + request both `text` and `audio` at the same time. + + parallel_tool_calls: Whether the model may call multiple tools in parallel. Only supported by + reasoning Realtime models such as `gpt-realtime-2`. + + prompt: Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + + reasoning: Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`. + + tool_choice: How the model chooses tools. Provide one of the string modes or force a specific + function/MCP tool. + + tools: Tools available to the model. + + tracing: Realtime API can write session traces to the + [Traces Dashboard](https://platform.openai.com/logs?api=traces). Set to null to + disable tracing. Once tracing is enabled for a session, the configuration cannot + be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + + truncation: When the number of tokens in a conversation exceeds the model's input token + limit, the conversation be truncated, meaning messages (starting from the + oldest) will not be included in the model's context. A 32k context model with + 4,096 max output tokens can only include 28,224 tokens in the context before + truncation occurs. + + Clients can configure truncation behavior to truncate with a lower max token + limit, which is an effective way to control token usage and cost. + + Truncation will reduce the number of cached tokens on the next turn (busting the + cache), since messages are dropped from the beginning of the context. However, + clients can also configure truncation to retain messages up to a fraction of the + maximum context size, which will reduce the need for future truncations and thus + improve the cache rate. + + Truncation can be disabled entirely, which means the server will never truncate + but would instead return an error if the conversation exceeds the model's input + token limit. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not call_id: + raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + path_template("/realtime/calls/{call_id}/accept", call_id=call_id), + body=maybe_transform( + { + "type": type, + "audio": audio, + "include": include, + "instructions": instructions, + "max_output_tokens": max_output_tokens, + "model": model, + "output_modalities": output_modalities, + "parallel_tool_calls": parallel_tool_calls, + "prompt": prompt, + "reasoning": reasoning, + "tool_choice": tool_choice, + "tools": tools, + "tracing": tracing, + "truncation": truncation, + }, + call_accept_params.CallAcceptParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + def hangup( + self, + call_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + End an active Realtime API call, whether it was initiated over SIP or WebRTC. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not call_id: + raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + path_template("/realtime/calls/{call_id}/hangup", call_id=call_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + def refer( + self, + call_id: str, + *, + target_uri: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Transfer an active SIP call to a new destination using the SIP REFER verb. + + Args: + target_uri: URI that should appear in the SIP Refer-To header. Supports values like + `tel:+14155550123` or `sip:agent@example.com`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not call_id: + raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + path_template("/realtime/calls/{call_id}/refer", call_id=call_id), + body=maybe_transform({"target_uri": target_uri}, call_refer_params.CallReferParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + def reject( + self, + call_id: str, + *, + status_code: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Decline an incoming SIP call by returning a SIP status code to the caller. + + Args: + status_code: SIP response code to send back to the caller. Defaults to `603` (Decline) when + omitted. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not call_id: + raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + path_template("/realtime/calls/{call_id}/reject", call_id=call_id), + body=maybe_transform({"status_code": status_code}, call_reject_params.CallRejectParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + +class AsyncCalls(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCallsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncCallsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCallsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncCallsWithStreamingResponse(self) + + async def create( + self, + *, + sdp: str, + session: RealtimeSessionCreateRequestParam | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + """ + Create a new Realtime API call over WebRTC and receive the SDP answer needed to + complete the peer connection. + + Args: + sdp: WebRTC Session Description Protocol (SDP) offer generated by the caller. + + session: Realtime session object configuration. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"Accept": "application/sdp", **(extra_headers or {})} + return await self._post( + "/realtime/calls", + body=await async_maybe_transform( + { + "sdp": sdp, + "session": session, + }, + call_create_params.CallCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + async def accept( + self, + call_id: str, + *, + type: Literal["realtime"], + audio: RealtimeAudioConfigParam | Omit = omit, + include: List[Literal["item.input_audio_transcription.logprobs"]] | Omit = omit, + instructions: str | Omit = omit, + max_output_tokens: Union[int, Literal["inf"]] | Omit = omit, + model: Union[ + str, + Literal[ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15", + ], + ] + | Omit = omit, + output_modalities: List[Literal["text", "audio"]] | Omit = omit, + parallel_tool_calls: bool | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + reasoning: RealtimeReasoningParam | Omit = omit, + tool_choice: RealtimeToolChoiceConfigParam | Omit = omit, + tools: RealtimeToolsConfigParam | Omit = omit, + tracing: Optional[RealtimeTracingConfigParam] | Omit = omit, + truncation: RealtimeTruncationParam | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Accept an incoming SIP call and configure the realtime session that will handle + it. + + Args: + type: The type of session to create. Always `realtime` for the Realtime API. + + audio: Configuration for input and output audio. + + include: Additional fields to include in server outputs. + + `item.input_audio_transcription.logprobs`: Include logprobs for input audio + transcription. + + instructions: The default system instructions (i.e. system message) prepended to model calls. + This field allows the client to guide the model on desired responses. The model + can be instructed on response content and format, (e.g. "be extremely succinct", + "act friendly", "here are examples of good responses") and on audio behavior + (e.g. "talk quickly", "inject emotion into your voice", "laugh frequently"). The + instructions are not guaranteed to be followed by the model, but they provide + guidance to the model on the desired behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + + max_output_tokens: Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + + model: The Realtime model used for this session. + + output_modalities: The set of modalities the model can respond with. It defaults to `["audio"]`, + indicating that the model will respond with audio plus a transcript. `["text"]` + can be used to make the model respond with text only. It is not possible to + request both `text` and `audio` at the same time. + + parallel_tool_calls: Whether the model may call multiple tools in parallel. Only supported by + reasoning Realtime models such as `gpt-realtime-2`. + + prompt: Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + + reasoning: Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`. + + tool_choice: How the model chooses tools. Provide one of the string modes or force a specific + function/MCP tool. + + tools: Tools available to the model. + + tracing: Realtime API can write session traces to the + [Traces Dashboard](https://platform.openai.com/logs?api=traces). Set to null to + disable tracing. Once tracing is enabled for a session, the configuration cannot + be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + + truncation: When the number of tokens in a conversation exceeds the model's input token + limit, the conversation be truncated, meaning messages (starting from the + oldest) will not be included in the model's context. A 32k context model with + 4,096 max output tokens can only include 28,224 tokens in the context before + truncation occurs. + + Clients can configure truncation behavior to truncate with a lower max token + limit, which is an effective way to control token usage and cost. + + Truncation will reduce the number of cached tokens on the next turn (busting the + cache), since messages are dropped from the beginning of the context. However, + clients can also configure truncation to retain messages up to a fraction of the + maximum context size, which will reduce the need for future truncations and thus + improve the cache rate. + + Truncation can be disabled entirely, which means the server will never truncate + but would instead return an error if the conversation exceeds the model's input + token limit. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not call_id: + raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + path_template("/realtime/calls/{call_id}/accept", call_id=call_id), + body=await async_maybe_transform( + { + "type": type, + "audio": audio, + "include": include, + "instructions": instructions, + "max_output_tokens": max_output_tokens, + "model": model, + "output_modalities": output_modalities, + "parallel_tool_calls": parallel_tool_calls, + "prompt": prompt, + "reasoning": reasoning, + "tool_choice": tool_choice, + "tools": tools, + "tracing": tracing, + "truncation": truncation, + }, + call_accept_params.CallAcceptParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + async def hangup( + self, + call_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + End an active Realtime API call, whether it was initiated over SIP or WebRTC. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not call_id: + raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + path_template("/realtime/calls/{call_id}/hangup", call_id=call_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + async def refer( + self, + call_id: str, + *, + target_uri: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Transfer an active SIP call to a new destination using the SIP REFER verb. + + Args: + target_uri: URI that should appear in the SIP Refer-To header. Supports values like + `tel:+14155550123` or `sip:agent@example.com`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not call_id: + raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + path_template("/realtime/calls/{call_id}/refer", call_id=call_id), + body=await async_maybe_transform({"target_uri": target_uri}, call_refer_params.CallReferParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + async def reject( + self, + call_id: str, + *, + status_code: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Decline an incoming SIP call by returning a SIP status code to the caller. + + Args: + status_code: SIP response code to send back to the caller. Defaults to `603` (Decline) when + omitted. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not call_id: + raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + path_template("/realtime/calls/{call_id}/reject", call_id=call_id), + body=await async_maybe_transform({"status_code": status_code}, call_reject_params.CallRejectParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + +class CallsWithRawResponse: + def __init__(self, calls: Calls) -> None: + self._calls = calls + + self.create = _legacy_response.to_raw_response_wrapper( + calls.create, + ) + self.accept = _legacy_response.to_raw_response_wrapper( + calls.accept, + ) + self.hangup = _legacy_response.to_raw_response_wrapper( + calls.hangup, + ) + self.refer = _legacy_response.to_raw_response_wrapper( + calls.refer, + ) + self.reject = _legacy_response.to_raw_response_wrapper( + calls.reject, + ) + + +class AsyncCallsWithRawResponse: + def __init__(self, calls: AsyncCalls) -> None: + self._calls = calls + + self.create = _legacy_response.async_to_raw_response_wrapper( + calls.create, + ) + self.accept = _legacy_response.async_to_raw_response_wrapper( + calls.accept, + ) + self.hangup = _legacy_response.async_to_raw_response_wrapper( + calls.hangup, + ) + self.refer = _legacy_response.async_to_raw_response_wrapper( + calls.refer, + ) + self.reject = _legacy_response.async_to_raw_response_wrapper( + calls.reject, + ) + + +class CallsWithStreamingResponse: + def __init__(self, calls: Calls) -> None: + self._calls = calls + + self.create = to_custom_streamed_response_wrapper( + calls.create, + StreamedBinaryAPIResponse, + ) + self.accept = to_streamed_response_wrapper( + calls.accept, + ) + self.hangup = to_streamed_response_wrapper( + calls.hangup, + ) + self.refer = to_streamed_response_wrapper( + calls.refer, + ) + self.reject = to_streamed_response_wrapper( + calls.reject, + ) + + +class AsyncCallsWithStreamingResponse: + def __init__(self, calls: AsyncCalls) -> None: + self._calls = calls + + self.create = async_to_custom_streamed_response_wrapper( + calls.create, + AsyncStreamedBinaryAPIResponse, + ) + self.accept = async_to_streamed_response_wrapper( + calls.accept, + ) + self.hangup = async_to_streamed_response_wrapper( + calls.hangup, + ) + self.refer = async_to_streamed_response_wrapper( + calls.refer, + ) + self.reject = async_to_streamed_response_wrapper( + calls.reject, + ) diff --git a/src/openai/resources/realtime/client_secrets.py b/src/openai/resources/realtime/client_secrets.py new file mode 100644 index 0000000000..7478e35e27 --- /dev/null +++ b/src/openai/resources/realtime/client_secrets.py @@ -0,0 +1,225 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ... import _legacy_response +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options +from ...types.realtime import client_secret_create_params +from ...types.realtime.client_secret_create_response import ClientSecretCreateResponse + +__all__ = ["ClientSecrets", "AsyncClientSecrets"] + + +class ClientSecrets(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ClientSecretsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ClientSecretsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ClientSecretsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ClientSecretsWithStreamingResponse(self) + + def create( + self, + *, + expires_after: client_secret_create_params.ExpiresAfter | Omit = omit, + session: client_secret_create_params.Session | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ClientSecretCreateResponse: + """ + Create a Realtime client secret with an associated session configuration. + + Client secrets are short-lived tokens that can be passed to a client app, such + as a web frontend or mobile client, which grants access to the Realtime API + without leaking your main API key. You can configure a custom TTL for each + client secret. + + You can also attach session configuration options to the client secret, which + will be applied to any sessions created using that client secret, but these can + also be overridden by the client connection. + + [Learn more about authentication with client secrets over WebRTC](https://platform.openai.com/docs/guides/realtime-webrtc). + + Returns the created client secret and the effective session object. The client + secret is a string that looks like `ek_1234`. + + Args: + expires_after: Configuration for the client secret expiration. Expiration refers to the time + after which a client secret will no longer be valid for creating sessions. The + session itself may continue after that time once started. A secret can be used + to create multiple sessions until it expires. + + session: Session configuration to use for the client secret. Choose either a realtime + session or a transcription session. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/realtime/client_secrets", + body=maybe_transform( + { + "expires_after": expires_after, + "session": session, + }, + client_secret_create_params.ClientSecretCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ClientSecretCreateResponse, + ) + + +class AsyncClientSecrets(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncClientSecretsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncClientSecretsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncClientSecretsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncClientSecretsWithStreamingResponse(self) + + async def create( + self, + *, + expires_after: client_secret_create_params.ExpiresAfter | Omit = omit, + session: client_secret_create_params.Session | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ClientSecretCreateResponse: + """ + Create a Realtime client secret with an associated session configuration. + + Client secrets are short-lived tokens that can be passed to a client app, such + as a web frontend or mobile client, which grants access to the Realtime API + without leaking your main API key. You can configure a custom TTL for each + client secret. + + You can also attach session configuration options to the client secret, which + will be applied to any sessions created using that client secret, but these can + also be overridden by the client connection. + + [Learn more about authentication with client secrets over WebRTC](https://platform.openai.com/docs/guides/realtime-webrtc). + + Returns the created client secret and the effective session object. The client + secret is a string that looks like `ek_1234`. + + Args: + expires_after: Configuration for the client secret expiration. Expiration refers to the time + after which a client secret will no longer be valid for creating sessions. The + session itself may continue after that time once started. A secret can be used + to create multiple sessions until it expires. + + session: Session configuration to use for the client secret. Choose either a realtime + session or a transcription session. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/realtime/client_secrets", + body=await async_maybe_transform( + { + "expires_after": expires_after, + "session": session, + }, + client_secret_create_params.ClientSecretCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=ClientSecretCreateResponse, + ) + + +class ClientSecretsWithRawResponse: + def __init__(self, client_secrets: ClientSecrets) -> None: + self._client_secrets = client_secrets + + self.create = _legacy_response.to_raw_response_wrapper( + client_secrets.create, + ) + + +class AsyncClientSecretsWithRawResponse: + def __init__(self, client_secrets: AsyncClientSecrets) -> None: + self._client_secrets = client_secrets + + self.create = _legacy_response.async_to_raw_response_wrapper( + client_secrets.create, + ) + + +class ClientSecretsWithStreamingResponse: + def __init__(self, client_secrets: ClientSecrets) -> None: + self._client_secrets = client_secrets + + self.create = to_streamed_response_wrapper( + client_secrets.create, + ) + + +class AsyncClientSecretsWithStreamingResponse: + def __init__(self, client_secrets: AsyncClientSecrets) -> None: + self._client_secrets = client_secrets + + self.create = async_to_streamed_response_wrapper( + client_secrets.create, + ) diff --git a/src/openai/resources/realtime/realtime.py b/src/openai/resources/realtime/realtime.py new file mode 100644 index 0000000000..e4c5bd8163 --- /dev/null +++ b/src/openai/resources/realtime/realtime.py @@ -0,0 +1,1675 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import json +import time +import random +import logging +from types import TracebackType +from typing import TYPE_CHECKING, Any, Union, Callable, Iterator, Awaitable, cast +from typing_extensions import AsyncIterator + +import httpx +from pydantic import BaseModel + +from .calls import ( + Calls, + AsyncCalls, + CallsWithRawResponse, + AsyncCallsWithRawResponse, + CallsWithStreamingResponse, + AsyncCallsWithStreamingResponse, +) +from ..._types import Omit, Query, Headers, omit +from ..._utils import ( + is_azure_client, + maybe_transform, + strip_not_given, + async_maybe_transform, + is_async_azure_client, +) +from ..._compat import cached_property +from ..._models import construct_type_unchecked +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._exceptions import OpenAIError, WebSocketConnectionClosedError +from ..._send_queue import SendQueue +from ..._base_client import _merge_mappings +from .client_secrets import ( + ClientSecrets, + AsyncClientSecrets, + ClientSecretsWithRawResponse, + AsyncClientSecretsWithRawResponse, + ClientSecretsWithStreamingResponse, + AsyncClientSecretsWithStreamingResponse, +) +from ..._event_handler import EventHandlerRegistry +from ...types.realtime import session_update_event_param +from ...types.websocket_reconnection import ReconnectingEvent, ReconnectingOverrides, is_recoverable_close +from ...types.websocket_connection_options import WebSocketConnectionOptions +from ...types.realtime.realtime_error_event import RealtimeErrorEvent +from ...types.realtime.realtime_client_event import RealtimeClientEvent +from ...types.realtime.realtime_server_event import RealtimeServerEvent +from ...types.realtime.conversation_item_param import ConversationItemParam +from ...types.realtime.realtime_client_event_param import RealtimeClientEventParam +from ...types.realtime.realtime_response_create_params_param import RealtimeResponseCreateParamsParam + +if TYPE_CHECKING: + from websockets.sync.client import ClientConnection as WebSocketConnection + from websockets.asyncio.client import ClientConnection as AsyncWebSocketConnection + + from ..._client import OpenAI, AsyncOpenAI + +__all__ = ["Realtime", "AsyncRealtime"] + +log: logging.Logger = logging.getLogger(__name__) + + +class Realtime(SyncAPIResource): + @cached_property + def client_secrets(self) -> ClientSecrets: + return ClientSecrets(self._client) + + @cached_property + def calls(self) -> Calls: + from ...lib._realtime import _Calls + + return _Calls(self._client) + + @cached_property + def with_raw_response(self) -> RealtimeWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return RealtimeWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RealtimeWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return RealtimeWithStreamingResponse(self) + + def connect( + self, + *, + call_id: str | Omit = omit, + model: str | Omit = omit, + extra_query: Query = {}, + extra_headers: Headers = {}, + websocket_connection_options: WebSocketConnectionOptions = {}, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + max_queue_size: int = 1_048_576, + ) -> RealtimeConnectionManager: + """ + The Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as function calling. + + Some notable benefits of the API include: + + - Native speech-to-speech: Skipping an intermediate text format means low latency and nuanced output. + - Natural, steerable voices: The models have natural inflection and can laugh, whisper, and adhere to tone direction. + - Simultaneous multimodal output: Text is useful for moderation; faster-than-realtime audio ensures stable playback. + + The Realtime API is a stateful, event-based API that communicates over a WebSocket. + """ + return RealtimeConnectionManager( + client=self._client, + extra_query=extra_query, + extra_headers=extra_headers, + websocket_connection_options=websocket_connection_options, + on_reconnecting=on_reconnecting, + max_retries=max_retries, + initial_delay=initial_delay, + max_delay=max_delay, + max_queue_size=max_queue_size, + call_id=call_id, + model=model, + ) + + +class AsyncRealtime(AsyncAPIResource): + @cached_property + def client_secrets(self) -> AsyncClientSecrets: + return AsyncClientSecrets(self._client) + + @cached_property + def calls(self) -> AsyncCalls: + from ...lib._realtime import _AsyncCalls + + return _AsyncCalls(self._client) + + @cached_property + def with_raw_response(self) -> AsyncRealtimeWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncRealtimeWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRealtimeWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncRealtimeWithStreamingResponse(self) + + def connect( + self, + *, + call_id: str | Omit = omit, + model: str | Omit = omit, + extra_query: Query = {}, + extra_headers: Headers = {}, + websocket_connection_options: WebSocketConnectionOptions = {}, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + max_queue_size: int = 1_048_576, + ) -> AsyncRealtimeConnectionManager: + """ + The Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as function calling. + + Some notable benefits of the API include: + + - Native speech-to-speech: Skipping an intermediate text format means low latency and nuanced output. + - Natural, steerable voices: The models have natural inflection and can laugh, whisper, and adhere to tone direction. + - Simultaneous multimodal output: Text is useful for moderation; faster-than-realtime audio ensures stable playback. + + The Realtime API is a stateful, event-based API that communicates over a WebSocket. + """ + return AsyncRealtimeConnectionManager( + client=self._client, + extra_query=extra_query, + extra_headers=extra_headers, + websocket_connection_options=websocket_connection_options, + on_reconnecting=on_reconnecting, + max_retries=max_retries, + initial_delay=initial_delay, + max_delay=max_delay, + max_queue_size=max_queue_size, + call_id=call_id, + model=model, + ) + + +class RealtimeWithRawResponse: + def __init__(self, realtime: Realtime) -> None: + self._realtime = realtime + + @cached_property + def client_secrets(self) -> ClientSecretsWithRawResponse: + return ClientSecretsWithRawResponse(self._realtime.client_secrets) + + @cached_property + def calls(self) -> CallsWithRawResponse: + return CallsWithRawResponse(self._realtime.calls) + + +class AsyncRealtimeWithRawResponse: + def __init__(self, realtime: AsyncRealtime) -> None: + self._realtime = realtime + + @cached_property + def client_secrets(self) -> AsyncClientSecretsWithRawResponse: + return AsyncClientSecretsWithRawResponse(self._realtime.client_secrets) + + @cached_property + def calls(self) -> AsyncCallsWithRawResponse: + return AsyncCallsWithRawResponse(self._realtime.calls) + + +class RealtimeWithStreamingResponse: + def __init__(self, realtime: Realtime) -> None: + self._realtime = realtime + + @cached_property + def client_secrets(self) -> ClientSecretsWithStreamingResponse: + return ClientSecretsWithStreamingResponse(self._realtime.client_secrets) + + @cached_property + def calls(self) -> CallsWithStreamingResponse: + return CallsWithStreamingResponse(self._realtime.calls) + + +class AsyncRealtimeWithStreamingResponse: + def __init__(self, realtime: AsyncRealtime) -> None: + self._realtime = realtime + + @cached_property + def client_secrets(self) -> AsyncClientSecretsWithStreamingResponse: + return AsyncClientSecretsWithStreamingResponse(self._realtime.client_secrets) + + @cached_property + def calls(self) -> AsyncCallsWithStreamingResponse: + return AsyncCallsWithStreamingResponse(self._realtime.calls) + + +class AsyncRealtimeConnection: + """Represents a live WebSocket connection to the Realtime API""" + + session: AsyncRealtimeSessionResource + response: AsyncRealtimeResponseResource + input_audio_buffer: AsyncRealtimeInputAudioBufferResource + conversation: AsyncRealtimeConversationResource + output_audio_buffer: AsyncRealtimeOutputAudioBufferResource + + _connection: AsyncWebSocketConnection + + def __init__( + self, + connection: AsyncWebSocketConnection, + *, + make_ws: Callable[[Query, Headers], Awaitable[AsyncWebSocketConnection]] | None = None, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + extra_query: Query = {}, + extra_headers: Headers = {}, + send_queue: SendQueue | None = None, + ) -> None: + self._connection = connection + self._make_ws = make_ws + self._on_reconnecting = on_reconnecting + self._max_retries = max_retries + self._initial_delay = initial_delay + self._max_delay = max_delay + self._extra_query = extra_query + self._extra_headers = extra_headers + self._intentionally_closed = False + self._is_reconnecting = False + self._send_queue = send_queue or SendQueue() + self._event_handler_registry = EventHandlerRegistry(use_lock=False) + + self.session = AsyncRealtimeSessionResource(self) + self.response = AsyncRealtimeResponseResource(self) + self.input_audio_buffer = AsyncRealtimeInputAudioBufferResource(self) + self.conversation = AsyncRealtimeConversationResource(self) + self.output_audio_buffer = AsyncRealtimeOutputAudioBufferResource(self) + + async def __aiter__(self) -> AsyncIterator[RealtimeServerEvent]: + """ + An infinite-iterator that will continue to yield events until + the connection is closed. + """ + from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError + + while True: + try: + yield await self.recv() + except ConnectionClosedOK: + return + except ConnectionClosedError as exc: + if not await self._reconnect(exc): + unsent = self._send_queue.drain() + if unsent: + raise WebSocketConnectionClosedError( + "WebSocket connection closed with unsent messages", + unsent_messages=unsent, + ) from exc + raise + + async def recv(self) -> RealtimeServerEvent: + """ + Receive the next message from the connection and parses it into a `RealtimeServerEvent` object. + + Canceling this method is safe. There's no risk of losing data. + """ + return self.parse_event(await self.recv_bytes()) + + async def recv_bytes(self) -> bytes: + """Receive the next message from the connection as raw bytes. + + Canceling this method is safe. There's no risk of losing data. + + If you want to parse the message into a `RealtimeServerEvent` object like `.recv()` does, + then you can call `.parse_event(data)`. + """ + message = await self._connection.recv(decode=False) + log.debug(f"Received WebSocket message: %s", message) + return message + + async def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: + data = ( + event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + if isinstance(event, BaseModel) + else json.dumps(await async_maybe_transform(event, RealtimeClientEventParam)) + ) + if self._is_reconnecting: + self._send_queue.enqueue(data) + return + try: + await self._connection.send(data) + except Exception: + self._send_queue.enqueue(data) + raise + + async def send_raw(self, data: bytes | str) -> None: + if self._is_reconnecting: + raw = data if isinstance(data, str) else data.decode("utf-8") + self._send_queue.enqueue(raw) + return + await self._connection.send(data) + + async def close(self, *, code: int = 1000, reason: str = "") -> None: + self._intentionally_closed = True + await self._connection.close(code=code, reason=reason) + + def parse_event(self, data: str | bytes) -> RealtimeServerEvent: + """ + Converts a raw `str` or `bytes` message into a `RealtimeServerEvent` object. + + This is helpful if you're using `.recv_bytes()`. + """ + return cast( + RealtimeServerEvent, construct_type_unchecked(value=json.loads(data), type_=cast(Any, RealtimeServerEvent)) + ) + + async def _reconnect(self, exc: Exception) -> bool: + """Attempt to reconnect after a connection failure. + + Returns ``True`` if a new connection was established, ``False`` if the + caller should re-raise the original exception. + """ + import asyncio + + if self._on_reconnecting is None or self._make_ws is None: + return False + + from websockets.exceptions import ConnectionClosedError + + close_code = 1006 + if isinstance(exc, ConnectionClosedError) and exc.rcvd is not None: + close_code = exc.rcvd.code + + if not is_recoverable_close(close_code): + return False + + self._is_reconnecting = True + + for attempt in range(1, self._max_retries + 1): + base_delay = min(self._initial_delay * (2 ** (attempt - 1)), self._max_delay) + jitter = 0.75 + random.random() * 0.25 + delay = base_delay * jitter + + event = ReconnectingEvent( + attempt=attempt, + max_attempts=self._max_retries, + delay=delay, + close_code=close_code, + extra_query=self._extra_query, + extra_headers=self._extra_headers, + ) + + try: + result = self._on_reconnecting(event) + except Exception: + self._is_reconnecting = False + return False + + if result is not None and result.get("abort"): + self._is_reconnecting = False + return False + + if result is not None: + if "extra_query" in result: + self._extra_query = result["extra_query"] + if "extra_headers" in result: + self._extra_headers = result["extra_headers"] + + log.info( + "Reconnecting to WebSocket API (attempt %d/%d) after %.1fs delay", + attempt, + self._max_retries, + delay, + ) + await asyncio.sleep(delay) + + if self._intentionally_closed: + self._is_reconnecting = False + return False + + try: + self._connection = await self._make_ws(self._extra_query, self._extra_headers) + log.info("Reconnected to WebSocket API") + self._is_reconnecting = False + await self._flush_send_queue() + return True + except Exception: + pass + + self._is_reconnecting = False + return False + + async def _flush_send_queue(self) -> None: + """Send all queued messages over the current connection.""" + + async def _send(data: str) -> None: + await self._connection.send(data) + + try: + await self._send_queue.flush_async(_send) + except Exception: + log.warning("Failed to flush send queue after reconnect", exc_info=True) + + def on( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[AsyncRealtimeConnection, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Adds the handler to the end of the handlers list for the given event type. + + No checks are made to see if the handler has already been added. Multiple calls + passing the same combination of event type and handler will result in the handler + being added, and called, multiple times. + + Can be used as a method (returns ``self`` for chaining):: + + connection.on("conversation.created", my_handler) + + Or as a decorator:: + + @connection.on("conversation.created") + async def my_handler(event): ... + """ + if handler is not None: + self._event_handler_registry.add(event_type, handler) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self._event_handler_registry.add(event_type, fn) + return fn + + return decorator + + def off(self, event_type: str, handler: Callable[..., Any]) -> AsyncRealtimeConnection: + """Remove a previously registered event handler.""" + self._event_handler_registry.remove(event_type, handler) + return self + + def once( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[AsyncRealtimeConnection, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register a one-time event handler. + + Automatically removed after first invocation. + """ + if handler is not None: + self._event_handler_registry.add(event_type, handler, once=True) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self._event_handler_registry.add(event_type, fn, once=True) + return fn + + return decorator + + async def dispatch_events(self) -> None: + """Run the event loop, dispatching received events to registered handlers. + + Blocks until the connection is closed. This is the push-based + alternative to iterating with ``async for event in connection``. + + If an ``"error"`` event arrives and no handler is registered for + ``"error"`` or ``"event"``, an ``OpenAIError`` is raised. + """ + import asyncio + + async for event in self: + event_type = event.type + specific = self._event_handler_registry.get_handlers(event_type) + generic = self._event_handler_registry.get_handlers("event") + + if event_type == "error" and not specific and not generic: + if isinstance(event, RealtimeErrorEvent): + raise OpenAIError(f"WebSocket error: {event}") + + for handler in specific: + result = handler(event) + if asyncio.iscoroutine(result): + await result + + for handler in generic: + result = handler(event) + if asyncio.iscoroutine(result): + await result + + +class AsyncRealtimeConnectionManager: + """ + Context manager over a `AsyncRealtimeConnection` that is returned by `realtime.connect()` + + This context manager ensures that the connection will be closed when it exits. + + --- + + Note that if your application doesn't work well with the context manager approach then you + can call the `.enter()` method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = await client.realtime.connect(...).enter() + # ... + await connection.close() + ``` + """ + + def __init__( + self, + *, + client: AsyncOpenAI, + call_id: str | Omit = omit, + model: str | Omit = omit, + extra_query: Query, + extra_headers: Headers, + websocket_connection_options: WebSocketConnectionOptions, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + max_queue_size: int = 1_048_576, + ) -> None: + self.__client = client + self.__call_id = call_id + self.__model = model + self.__connection: AsyncRealtimeConnection | None = None + self.__extra_query = extra_query + self.__extra_headers = extra_headers + self.__websocket_connection_options = websocket_connection_options + self.__on_reconnecting = on_reconnecting + self.__max_retries = max_retries + self.__initial_delay = initial_delay + self.__max_delay = max_delay + self.__send_queue = SendQueue(max_bytes=max_queue_size) + self.__event_handler_registry = EventHandlerRegistry(use_lock=False) + + def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: + """Queue a message to be sent when the connection is established. + + This can be called before entering the context manager. Queued messages + are automatically sent once the WebSocket connection opens. + """ + data = ( + event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + if isinstance(event, BaseModel) + else json.dumps(event) + ) + self.__send_queue.enqueue(data) + + def on( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[AsyncRealtimeConnectionManager, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register an event handler before the connection is established. + + Handlers are transferred to the connection on enter. Supports the + same method and decorator forms as ``AsyncRealtimeConnection.on``. + """ + if handler is not None: + self.__event_handler_registry.add(event_type, handler) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self.__event_handler_registry.add(event_type, fn) + return fn + + return decorator + + def off(self, event_type: str, handler: Callable[..., Any]) -> AsyncRealtimeConnectionManager: + """Remove a previously registered event handler.""" + self.__event_handler_registry.remove(event_type, handler) + return self + + def once( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[AsyncRealtimeConnectionManager, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register a one-time event handler before the connection is established.""" + if handler is not None: + self.__event_handler_registry.add(event_type, handler, once=True) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self.__event_handler_registry.add(event_type, fn, once=True) + return fn + + return decorator + + async def __aenter__(self) -> AsyncRealtimeConnection: + """ + If your application doesn't work well with the context manager approach then you + can call this method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = await client.realtime.connect(...).enter() + # ... + await connection.close() + ``` + """ + ws = await self._connect_ws(self.__extra_query, self.__extra_headers) + + self.__connection = AsyncRealtimeConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) + + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + await self.__connection._flush_send_queue() + + return self.__connection + + enter = __aenter__ + + async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> AsyncWebSocketConnection: + try: + from websockets.asyncio.client import connect + except ImportError as exc: + raise OpenAIError("You need to install `openai[realtime]` to use this method") from exc + + await self.__client._refresh_api_key() + auth_headers = self.__client.auth_headers + if self.__call_id is not omit: + extra_query = {**extra_query, "call_id": self.__call_id} + if is_async_azure_client(self.__client): + model = self.__model + if not model: + raise OpenAIError("`model` is required for Azure Realtime API") + else: + url, auth_headers = await self.__client._configure_realtime(model, extra_query) + else: + url = self._prepare_url().copy_with( + params={ + **self.__client.base_url.params, + **({"model": self.__model} if self.__model is not omit else {}), + **extra_query, + }, + ) + log.debug("Connecting to %s", url) + if self.__websocket_connection_options: + log.debug("Connection options: %s", self.__websocket_connection_options) + + return await connect( + str(url), + user_agent_header=self.__client.user_agent, + additional_headers=_merge_mappings( + { + **auth_headers, + }, + extra_headers, + ), + **self.__websocket_connection_options, + ) + + def _prepare_url(self) -> httpx.URL: + if self.__client.websocket_base_url is not None: + base_url = httpx.URL(self.__client.websocket_base_url) + else: + scheme = self.__client._base_url.scheme + ws_scheme = "ws" if scheme == "http" else "wss" + base_url = self.__client._base_url.copy_with(scheme=ws_scheme) + + merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime" + return base_url.copy_with(raw_path=merge_raw_path) + + async def __aexit__( + self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None + ) -> None: + if self.__connection is not None: + await self.__connection.close() + + +class RealtimeConnection: + """Represents a live WebSocket connection to the Realtime API""" + + session: RealtimeSessionResource + response: RealtimeResponseResource + input_audio_buffer: RealtimeInputAudioBufferResource + conversation: RealtimeConversationResource + output_audio_buffer: RealtimeOutputAudioBufferResource + + _connection: WebSocketConnection + + def __init__( + self, + connection: WebSocketConnection, + *, + make_ws: Callable[[Query, Headers], WebSocketConnection] | None = None, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + extra_query: Query = {}, + extra_headers: Headers = {}, + send_queue: SendQueue | None = None, + ) -> None: + self._connection = connection + self._make_ws = make_ws + self._on_reconnecting = on_reconnecting + self._max_retries = max_retries + self._initial_delay = initial_delay + self._max_delay = max_delay + self._extra_query = extra_query + self._extra_headers = extra_headers + self._intentionally_closed = False + self._is_reconnecting = False + self._send_queue = send_queue or SendQueue() + self._event_handler_registry = EventHandlerRegistry(use_lock=True) + + self.session = RealtimeSessionResource(self) + self.response = RealtimeResponseResource(self) + self.input_audio_buffer = RealtimeInputAudioBufferResource(self) + self.conversation = RealtimeConversationResource(self) + self.output_audio_buffer = RealtimeOutputAudioBufferResource(self) + + def __iter__(self) -> Iterator[RealtimeServerEvent]: + """ + An infinite-iterator that will continue to yield events until + the connection is closed. + """ + from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError + + while True: + try: + yield self.recv() + except ConnectionClosedOK: + return + except ConnectionClosedError as exc: + if not self._reconnect(exc): + unsent = self._send_queue.drain() + if unsent: + raise WebSocketConnectionClosedError( + "WebSocket connection closed with unsent messages", + unsent_messages=unsent, + ) from exc + raise + + def recv(self) -> RealtimeServerEvent: + """ + Receive the next message from the connection and parses it into a `RealtimeServerEvent` object. + + Canceling this method is safe. There's no risk of losing data. + """ + return self.parse_event(self.recv_bytes()) + + def recv_bytes(self) -> bytes: + """Receive the next message from the connection as raw bytes. + + Canceling this method is safe. There's no risk of losing data. + + If you want to parse the message into a `RealtimeServerEvent` object like `.recv()` does, + then you can call `.parse_event(data)`. + """ + message = self._connection.recv(decode=False) + log.debug(f"Received WebSocket message: %s", message) + return message + + def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: + data = ( + event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + if isinstance(event, BaseModel) + else json.dumps(maybe_transform(event, RealtimeClientEventParam)) + ) + if self._is_reconnecting: + self._send_queue.enqueue(data) + return + try: + self._connection.send(data) + except Exception: + self._send_queue.enqueue(data) + raise + + def send_raw(self, data: bytes | str) -> None: + if self._is_reconnecting: + raw = data if isinstance(data, str) else data.decode("utf-8") + self._send_queue.enqueue(raw) + return + self._connection.send(data) + + def close(self, *, code: int = 1000, reason: str = "") -> None: + self._intentionally_closed = True + self._connection.close(code=code, reason=reason) + + def parse_event(self, data: str | bytes) -> RealtimeServerEvent: + """ + Converts a raw `str` or `bytes` message into a `RealtimeServerEvent` object. + + This is helpful if you're using `.recv_bytes()`. + """ + return cast( + RealtimeServerEvent, construct_type_unchecked(value=json.loads(data), type_=cast(Any, RealtimeServerEvent)) + ) + + def _reconnect(self, exc: Exception) -> bool: + """Attempt to reconnect after a connection failure. + + Returns ``True`` if a new connection was established, ``False`` if the + caller should re-raise the original exception. + """ + if self._on_reconnecting is None or self._make_ws is None: + return False + + from websockets.exceptions import ConnectionClosedError + + close_code = 1006 + if isinstance(exc, ConnectionClosedError) and exc.rcvd is not None: + close_code = exc.rcvd.code + + if not is_recoverable_close(close_code): + return False + + self._is_reconnecting = True + + for attempt in range(1, self._max_retries + 1): + base_delay = min(self._initial_delay * (2 ** (attempt - 1)), self._max_delay) + jitter = 0.75 + random.random() * 0.25 + delay = base_delay * jitter + + event = ReconnectingEvent( + attempt=attempt, + max_attempts=self._max_retries, + delay=delay, + close_code=close_code, + extra_query=self._extra_query, + extra_headers=self._extra_headers, + ) + + try: + result = self._on_reconnecting(event) + except Exception: + self._is_reconnecting = False + return False + + if result is not None and result.get("abort"): + self._is_reconnecting = False + return False + + if result is not None: + if "extra_query" in result: + self._extra_query = result["extra_query"] + if "extra_headers" in result: + self._extra_headers = result["extra_headers"] + + log.info( + "Reconnecting to WebSocket API (attempt %d/%d) after %.1fs delay", + attempt, + self._max_retries, + delay, + ) + time.sleep(delay) + + if self._intentionally_closed: + self._is_reconnecting = False + return False + + try: + self._connection = self._make_ws(self._extra_query, self._extra_headers) + log.info("Reconnected to WebSocket API") + self._is_reconnecting = False + self._flush_send_queue() + return True + except Exception: + pass + + self._is_reconnecting = False + return False + + def _flush_send_queue(self) -> None: + """Send all queued messages over the current connection.""" + try: + self._send_queue.flush_sync(lambda data: self._connection.send(data)) + except Exception: + log.warning("Failed to flush send queue after reconnect", exc_info=True) + + def on( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[RealtimeConnection, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Adds the handler to the end of the handlers list for the given event type. + + No checks are made to see if the handler has already been added. Multiple calls + passing the same combination of event type and handler will result in the handler + being added, and called, multiple times. + + Can be used as a method (returns ``self`` for chaining):: + + connection.on("conversation.created", my_handler) + + Or as a decorator:: + + @connection.on("conversation.created") + def my_handler(event): ... + """ + if handler is not None: + self._event_handler_registry.add(event_type, handler) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self._event_handler_registry.add(event_type, fn) + return fn + + return decorator + + def off(self, event_type: str, handler: Callable[..., Any]) -> RealtimeConnection: + """Remove a previously registered event handler.""" + self._event_handler_registry.remove(event_type, handler) + return self + + def once( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[RealtimeConnection, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register a one-time event handler. + + Automatically removed after first invocation. + """ + if handler is not None: + self._event_handler_registry.add(event_type, handler, once=True) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self._event_handler_registry.add(event_type, fn, once=True) + return fn + + return decorator + + def dispatch_events(self) -> None: + """Run the event loop, dispatching received events to registered handlers. + + Blocks the current thread until the connection is closed. This is the push-based + alternative to iterating with ``for event in connection``. + + If an ``"error"`` event arrives and no handler is registered for + ``"error"`` or ``"event"``, an ``OpenAIError`` is raised. + """ + for event in self: + event_type = event.type + specific = self._event_handler_registry.get_handlers(event_type) + generic = self._event_handler_registry.get_handlers("event") + + if event_type == "error" and not specific and not generic: + if isinstance(event, RealtimeErrorEvent): + raise OpenAIError(f"WebSocket error: {event}") + + for handler in specific: + handler(event) + + for handler in generic: + handler(event) + + +class RealtimeConnectionManager: + """ + Context manager over a `RealtimeConnection` that is returned by `realtime.connect()` + + This context manager ensures that the connection will be closed when it exits. + + --- + + Note that if your application doesn't work well with the context manager approach then you + can call the `.enter()` method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = client.realtime.connect(...).enter() + # ... + connection.close() + ``` + """ + + def __init__( + self, + *, + client: OpenAI, + call_id: str | Omit = omit, + model: str | Omit = omit, + extra_query: Query, + extra_headers: Headers, + websocket_connection_options: WebSocketConnectionOptions, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + max_queue_size: int = 1_048_576, + ) -> None: + self.__client = client + self.__call_id = call_id + self.__model = model + self.__connection: RealtimeConnection | None = None + self.__extra_query = extra_query + self.__extra_headers = extra_headers + self.__websocket_connection_options = websocket_connection_options + self.__on_reconnecting = on_reconnecting + self.__max_retries = max_retries + self.__initial_delay = initial_delay + self.__max_delay = max_delay + self.__send_queue = SendQueue(max_bytes=max_queue_size) + self.__event_handler_registry = EventHandlerRegistry(use_lock=True) + + def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: + """Queue a message to be sent when the connection is established. + + This can be called before entering the context manager. Queued messages + are automatically sent once the WebSocket connection opens. + """ + data = ( + event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + if isinstance(event, BaseModel) + else json.dumps(event) + ) + self.__send_queue.enqueue(data) + + def on( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[RealtimeConnectionManager, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register an event handler before the connection is established. + + Handlers are transferred to the connection on enter. Supports the + same method and decorator forms as ``RealtimeConnection.on``. + """ + if handler is not None: + self.__event_handler_registry.add(event_type, handler) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self.__event_handler_registry.add(event_type, fn) + return fn + + return decorator + + def off(self, event_type: str, handler: Callable[..., Any]) -> RealtimeConnectionManager: + """Remove a previously registered event handler.""" + self.__event_handler_registry.remove(event_type, handler) + return self + + def once( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[RealtimeConnectionManager, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register a one-time event handler before the connection is established.""" + if handler is not None: + self.__event_handler_registry.add(event_type, handler, once=True) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self.__event_handler_registry.add(event_type, fn, once=True) + return fn + + return decorator + + def __enter__(self) -> RealtimeConnection: + """ + If your application doesn't work well with the context manager approach then you + can call this method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = client.realtime.connect(...).enter() + # ... + connection.close() + ``` + """ + ws = self._connect_ws(self.__extra_query, self.__extra_headers) + + self.__connection = RealtimeConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) + + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + self.__connection._flush_send_queue() + + return self.__connection + + enter = __enter__ + + def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketConnection: + try: + from websockets.sync.client import connect + except ImportError as exc: + raise OpenAIError("You need to install `openai[realtime]` to use this method") from exc + + self.__client._refresh_api_key() + auth_headers = self.__client.auth_headers + if self.__call_id is not omit: + extra_query = {**extra_query, "call_id": self.__call_id} + if is_azure_client(self.__client): + model = self.__model + if not model: + raise OpenAIError("`model` is required for Azure Realtime API") + else: + url, auth_headers = self.__client._configure_realtime(model, extra_query) + else: + url = self._prepare_url().copy_with( + params={ + **self.__client.base_url.params, + **({"model": self.__model} if self.__model is not omit else {}), + **extra_query, + }, + ) + log.debug("Connecting to %s", url) + if self.__websocket_connection_options: + log.debug("Connection options: %s", self.__websocket_connection_options) + + return connect( + str(url), + user_agent_header=self.__client.user_agent, + additional_headers=_merge_mappings( + { + **auth_headers, + }, + extra_headers, + ), + **self.__websocket_connection_options, + ) + + def _prepare_url(self) -> httpx.URL: + if self.__client.websocket_base_url is not None: + base_url = httpx.URL(self.__client.websocket_base_url) + else: + scheme = self.__client._base_url.scheme + ws_scheme = "ws" if scheme == "http" else "wss" + base_url = self.__client._base_url.copy_with(scheme=ws_scheme) + + merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime" + return base_url.copy_with(raw_path=merge_raw_path) + + def __exit__( + self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None + ) -> None: + if self.__connection is not None: + self.__connection.close() + + +class BaseRealtimeConnectionResource: + def __init__(self, connection: RealtimeConnection) -> None: + self._connection = connection + + +class RealtimeSessionResource(BaseRealtimeConnectionResource): + def update(self, *, session: session_update_event_param.Session, event_id: str | Omit = omit) -> None: + """ + Send this event to update the session’s configuration. + The client may send this event at any time to update any field + except for `voice` and `model`. `voice` can be updated only if there have been no other audio outputs yet. + + When the server receives a `session.update`, it will respond + with a `session.updated` event showing the full, effective configuration. + Only the fields that are present in the `session.update` are updated. To clear a field like + `instructions`, pass an empty string. To clear a field like `tools`, pass an empty array. + To clear a field like `turn_detection`, pass `null`. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "session.update", "session": session, "event_id": event_id}), + ) + ) + + +class RealtimeResponseResource(BaseRealtimeConnectionResource): + def create(self, *, event_id: str | Omit = omit, response: RealtimeResponseCreateParamsParam | Omit = omit) -> None: + """ + This event instructs the server to create a Response, which means triggering + model inference. When in Server VAD mode, the server will create Responses + automatically. + + A Response will include at least one Item, and may have two, in which case + the second will be a function call. These Items will be appended to the + conversation history by default. + + The server will respond with a `response.created` event, events for Items + and content created, and finally a `response.done` event to indicate the + Response is complete. + + The `response.create` event includes inference configuration like + `instructions` and `tools`. If these are set, they will override the Session's + configuration for this Response only. + + Responses can be created out-of-band of the default Conversation, meaning that they can + have arbitrary input, and it's possible to disable writing the output to the Conversation. + Only one Response can write to the default Conversation at a time, but otherwise multiple + Responses can be created in parallel. The `metadata` field is a good way to disambiguate + multiple simultaneous Responses. + + Clients can set `conversation` to `none` to create a Response that does not write to the default + Conversation. Arbitrary input can be provided with the `input` field, which is an array accepting + raw Items and references to existing Items. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "response.create", "event_id": event_id, "response": response}), + ) + ) + + def cancel(self, *, event_id: str | Omit = omit, response_id: str | Omit = omit) -> None: + """Send this event to cancel an in-progress response. + + The server will respond + with a `response.done` event with a status of `response.status=cancelled`. If + there is no response to cancel, the server will respond with an error. It's safe + to call `response.cancel` even if no response is in progress, an error will be + returned the session will remain unaffected. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "response.cancel", "event_id": event_id, "response_id": response_id}), + ) + ) + + +class RealtimeInputAudioBufferResource(BaseRealtimeConnectionResource): + def clear(self, *, event_id: str | Omit = omit) -> None: + """Send this event to clear the audio bytes in the buffer. + + The server will + respond with an `input_audio_buffer.cleared` event. + """ + self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "input_audio_buffer.clear", "event_id": event_id})) + ) + + def commit(self, *, event_id: str | Omit = omit) -> None: + """ + Send this event to commit the user input audio buffer, which will create a new user message item in the conversation. This event will produce an error if the input audio buffer is empty. When in Server VAD mode, the client does not need to send this event, the server will commit the audio buffer automatically. + + Committing the input audio buffer will trigger input audio transcription (if enabled in session configuration), but it will not create a response from the model. The server will respond with an `input_audio_buffer.committed` event. + """ + self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "input_audio_buffer.commit", "event_id": event_id})) + ) + + def append(self, *, audio: str, event_id: str | Omit = omit) -> None: + """Send this event to append audio bytes to the input audio buffer. + + The audio + buffer is temporary storage you can write to and later commit. A "commit" will create a new + user message item in the conversation history from the buffer content and clear the buffer. + Input audio transcription (if enabled) will be generated when the buffer is committed. + + If VAD is enabled the audio buffer is used to detect speech and the server will decide + when to commit. When Server VAD is disabled, you must commit the audio buffer + manually. Input audio noise reduction operates on writes to the audio buffer. + + The client may choose how much audio to place in each event up to a maximum + of 15 MiB, for example streaming smaller chunks from the client may allow the + VAD to be more responsive. Unlike most other client events, the server will + not send a confirmation response to this event. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "input_audio_buffer.append", "audio": audio, "event_id": event_id}), + ) + ) + + +class RealtimeConversationResource(BaseRealtimeConnectionResource): + @cached_property + def item(self) -> RealtimeConversationItemResource: + return RealtimeConversationItemResource(self._connection) + + +class RealtimeConversationItemResource(BaseRealtimeConnectionResource): + def delete(self, *, item_id: str, event_id: str | Omit = omit) -> None: + """Send this event when you want to remove any item from the conversation + history. + + The server will respond with a `conversation.item.deleted` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "conversation.item.delete", "item_id": item_id, "event_id": event_id}), + ) + ) + + def create( + self, *, item: ConversationItemParam, event_id: str | Omit = omit, previous_item_id: str | Omit = omit + ) -> None: + """ + Add a new Item to the Conversation's context, including messages, function + calls, and function call responses. This event can be used both to populate a + "history" of the conversation and to add new items mid-stream, but has the + current limitation that it cannot populate assistant audio messages. + + If successful, the server will respond with a `conversation.item.created` + event, otherwise an `error` event will be sent. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given( + { + "type": "conversation.item.create", + "item": item, + "event_id": event_id, + "previous_item_id": previous_item_id, + } + ), + ) + ) + + def truncate(self, *, audio_end_ms: int, content_index: int, item_id: str, event_id: str | Omit = omit) -> None: + """Send this event to truncate a previous assistant message’s audio. + + The server + will produce audio faster than realtime, so this event is useful when the user + interrupts to truncate audio that has already been sent to the client but not + yet played. This will synchronize the server's understanding of the audio with + the client's playback. + + Truncating audio will delete the server-side text transcript to ensure there + is not text in the context that hasn't been heard by the user. + + If successful, the server will respond with a `conversation.item.truncated` + event. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given( + { + "type": "conversation.item.truncate", + "audio_end_ms": audio_end_ms, + "content_index": content_index, + "item_id": item_id, + "event_id": event_id, + } + ), + ) + ) + + def retrieve(self, *, item_id: str, event_id: str | Omit = omit) -> None: + """ + Send this event when you want to retrieve the server's representation of a specific item in the conversation history. This is useful, for example, to inspect user audio after noise cancellation and VAD. + The server will respond with a `conversation.item.retrieved` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "conversation.item.retrieve", "item_id": item_id, "event_id": event_id}), + ) + ) + + +class RealtimeOutputAudioBufferResource(BaseRealtimeConnectionResource): + def clear(self, *, event_id: str | Omit = omit) -> None: + """**WebRTC/SIP Only:** Emit to cut off the current audio response. + + This will trigger the server to + stop generating audio and emit a `output_audio_buffer.cleared` event. This + event should be preceded by a `response.cancel` client event to stop the + generation of the current response. + [Learn more](https://platform.openai.com/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc). + """ + self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "output_audio_buffer.clear", "event_id": event_id})) + ) + + +class BaseAsyncRealtimeConnectionResource: + def __init__(self, connection: AsyncRealtimeConnection) -> None: + self._connection = connection + + +class AsyncRealtimeSessionResource(BaseAsyncRealtimeConnectionResource): + async def update(self, *, session: session_update_event_param.Session, event_id: str | Omit = omit) -> None: + """ + Send this event to update the session’s configuration. + The client may send this event at any time to update any field + except for `voice` and `model`. `voice` can be updated only if there have been no other audio outputs yet. + + When the server receives a `session.update`, it will respond + with a `session.updated` event showing the full, effective configuration. + Only the fields that are present in the `session.update` are updated. To clear a field like + `instructions`, pass an empty string. To clear a field like `tools`, pass an empty array. + To clear a field like `turn_detection`, pass `null`. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "session.update", "session": session, "event_id": event_id}), + ) + ) + + +class AsyncRealtimeResponseResource(BaseAsyncRealtimeConnectionResource): + async def create( + self, *, event_id: str | Omit = omit, response: RealtimeResponseCreateParamsParam | Omit = omit + ) -> None: + """ + This event instructs the server to create a Response, which means triggering + model inference. When in Server VAD mode, the server will create Responses + automatically. + + A Response will include at least one Item, and may have two, in which case + the second will be a function call. These Items will be appended to the + conversation history by default. + + The server will respond with a `response.created` event, events for Items + and content created, and finally a `response.done` event to indicate the + Response is complete. + + The `response.create` event includes inference configuration like + `instructions` and `tools`. If these are set, they will override the Session's + configuration for this Response only. + + Responses can be created out-of-band of the default Conversation, meaning that they can + have arbitrary input, and it's possible to disable writing the output to the Conversation. + Only one Response can write to the default Conversation at a time, but otherwise multiple + Responses can be created in parallel. The `metadata` field is a good way to disambiguate + multiple simultaneous Responses. + + Clients can set `conversation` to `none` to create a Response that does not write to the default + Conversation. Arbitrary input can be provided with the `input` field, which is an array accepting + raw Items and references to existing Items. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "response.create", "event_id": event_id, "response": response}), + ) + ) + + async def cancel(self, *, event_id: str | Omit = omit, response_id: str | Omit = omit) -> None: + """Send this event to cancel an in-progress response. + + The server will respond + with a `response.done` event with a status of `response.status=cancelled`. If + there is no response to cancel, the server will respond with an error. It's safe + to call `response.cancel` even if no response is in progress, an error will be + returned the session will remain unaffected. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "response.cancel", "event_id": event_id, "response_id": response_id}), + ) + ) + + +class AsyncRealtimeInputAudioBufferResource(BaseAsyncRealtimeConnectionResource): + async def clear(self, *, event_id: str | Omit = omit) -> None: + """Send this event to clear the audio bytes in the buffer. + + The server will + respond with an `input_audio_buffer.cleared` event. + """ + await self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "input_audio_buffer.clear", "event_id": event_id})) + ) + + async def commit(self, *, event_id: str | Omit = omit) -> None: + """ + Send this event to commit the user input audio buffer, which will create a new user message item in the conversation. This event will produce an error if the input audio buffer is empty. When in Server VAD mode, the client does not need to send this event, the server will commit the audio buffer automatically. + + Committing the input audio buffer will trigger input audio transcription (if enabled in session configuration), but it will not create a response from the model. The server will respond with an `input_audio_buffer.committed` event. + """ + await self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "input_audio_buffer.commit", "event_id": event_id})) + ) + + async def append(self, *, audio: str, event_id: str | Omit = omit) -> None: + """Send this event to append audio bytes to the input audio buffer. + + The audio + buffer is temporary storage you can write to and later commit. A "commit" will create a new + user message item in the conversation history from the buffer content and clear the buffer. + Input audio transcription (if enabled) will be generated when the buffer is committed. + + If VAD is enabled the audio buffer is used to detect speech and the server will decide + when to commit. When Server VAD is disabled, you must commit the audio buffer + manually. Input audio noise reduction operates on writes to the audio buffer. + + The client may choose how much audio to place in each event up to a maximum + of 15 MiB, for example streaming smaller chunks from the client may allow the + VAD to be more responsive. Unlike most other client events, the server will + not send a confirmation response to this event. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "input_audio_buffer.append", "audio": audio, "event_id": event_id}), + ) + ) + + +class AsyncRealtimeConversationResource(BaseAsyncRealtimeConnectionResource): + @cached_property + def item(self) -> AsyncRealtimeConversationItemResource: + return AsyncRealtimeConversationItemResource(self._connection) + + +class AsyncRealtimeConversationItemResource(BaseAsyncRealtimeConnectionResource): + async def delete(self, *, item_id: str, event_id: str | Omit = omit) -> None: + """Send this event when you want to remove any item from the conversation + history. + + The server will respond with a `conversation.item.deleted` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "conversation.item.delete", "item_id": item_id, "event_id": event_id}), + ) + ) + + async def create( + self, *, item: ConversationItemParam, event_id: str | Omit = omit, previous_item_id: str | Omit = omit + ) -> None: + """ + Add a new Item to the Conversation's context, including messages, function + calls, and function call responses. This event can be used both to populate a + "history" of the conversation and to add new items mid-stream, but has the + current limitation that it cannot populate assistant audio messages. + + If successful, the server will respond with a `conversation.item.created` + event, otherwise an `error` event will be sent. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given( + { + "type": "conversation.item.create", + "item": item, + "event_id": event_id, + "previous_item_id": previous_item_id, + } + ), + ) + ) + + async def truncate( + self, *, audio_end_ms: int, content_index: int, item_id: str, event_id: str | Omit = omit + ) -> None: + """Send this event to truncate a previous assistant message’s audio. + + The server + will produce audio faster than realtime, so this event is useful when the user + interrupts to truncate audio that has already been sent to the client but not + yet played. This will synchronize the server's understanding of the audio with + the client's playback. + + Truncating audio will delete the server-side text transcript to ensure there + is not text in the context that hasn't been heard by the user. + + If successful, the server will respond with a `conversation.item.truncated` + event. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given( + { + "type": "conversation.item.truncate", + "audio_end_ms": audio_end_ms, + "content_index": content_index, + "item_id": item_id, + "event_id": event_id, + } + ), + ) + ) + + async def retrieve(self, *, item_id: str, event_id: str | Omit = omit) -> None: + """ + Send this event when you want to retrieve the server's representation of a specific item in the conversation history. This is useful, for example, to inspect user audio after noise cancellation and VAD. + The server will respond with a `conversation.item.retrieved` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + await self._connection.send( + cast( + RealtimeClientEventParam, + strip_not_given({"type": "conversation.item.retrieve", "item_id": item_id, "event_id": event_id}), + ) + ) + + +class AsyncRealtimeOutputAudioBufferResource(BaseAsyncRealtimeConnectionResource): + async def clear(self, *, event_id: str | Omit = omit) -> None: + """**WebRTC/SIP Only:** Emit to cut off the current audio response. + + This will trigger the server to + stop generating audio and emit a `output_audio_buffer.cleared` event. This + event should be preceded by a `response.cancel` client event to stop the + generation of the current response. + [Learn more](https://platform.openai.com/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc). + """ + await self._connection.send( + cast(RealtimeClientEventParam, strip_not_given({"type": "output_audio_buffer.clear", "event_id": event_id})) + ) diff --git a/src/openai/resources/responses/__init__.py b/src/openai/resources/responses/__init__.py new file mode 100644 index 0000000000..51d318ad8d --- /dev/null +++ b/src/openai/resources/responses/__init__.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .responses import ( + Responses, + AsyncResponses, + ResponsesWithRawResponse, + AsyncResponsesWithRawResponse, + ResponsesWithStreamingResponse, + AsyncResponsesWithStreamingResponse, +) +from .input_items import ( + InputItems, + AsyncInputItems, + InputItemsWithRawResponse, + AsyncInputItemsWithRawResponse, + InputItemsWithStreamingResponse, + AsyncInputItemsWithStreamingResponse, +) +from .input_tokens import ( + InputTokens, + AsyncInputTokens, + InputTokensWithRawResponse, + AsyncInputTokensWithRawResponse, + InputTokensWithStreamingResponse, + AsyncInputTokensWithStreamingResponse, +) + +__all__ = [ + "InputItems", + "AsyncInputItems", + "InputItemsWithRawResponse", + "AsyncInputItemsWithRawResponse", + "InputItemsWithStreamingResponse", + "AsyncInputItemsWithStreamingResponse", + "InputTokens", + "AsyncInputTokens", + "InputTokensWithRawResponse", + "AsyncInputTokensWithRawResponse", + "InputTokensWithStreamingResponse", + "AsyncInputTokensWithStreamingResponse", + "Responses", + "AsyncResponses", + "ResponsesWithRawResponse", + "AsyncResponsesWithRawResponse", + "ResponsesWithStreamingResponse", + "AsyncResponsesWithStreamingResponse", +] diff --git a/src/openai/resources/responses/api.md b/src/openai/resources/responses/api.md new file mode 100644 index 0000000000..891e0f9796 --- /dev/null +++ b/src/openai/resources/responses/api.md @@ -0,0 +1,187 @@ +# Responses + +Types: + +```python +from openai.types.responses import ( + ApplyPatchTool, + CompactedResponse, + ComputerAction, + ComputerActionList, + ComputerTool, + ComputerUsePreviewTool, + ContainerAuto, + ContainerNetworkPolicyAllowlist, + ContainerNetworkPolicyDisabled, + ContainerNetworkPolicyDomainSecret, + ContainerReference, + CustomTool, + EasyInputMessage, + FileSearchTool, + FunctionShellTool, + FunctionTool, + InlineSkill, + InlineSkillSource, + LocalEnvironment, + LocalSkill, + NamespaceTool, + Response, + ResponseApplyPatchToolCall, + ResponseApplyPatchToolCallOutput, + ResponseAudioDeltaEvent, + ResponseAudioDoneEvent, + ResponseAudioTranscriptDeltaEvent, + ResponseAudioTranscriptDoneEvent, + ResponseCodeInterpreterCallCodeDeltaEvent, + ResponseCodeInterpreterCallCodeDoneEvent, + ResponseCodeInterpreterCallCompletedEvent, + ResponseCodeInterpreterCallInProgressEvent, + ResponseCodeInterpreterCallInterpretingEvent, + ResponseCodeInterpreterToolCall, + ResponseCompactionItem, + ResponseCompactionItemParam, + ResponseCompletedEvent, + ResponseComputerToolCall, + ResponseComputerToolCallOutputItem, + ResponseComputerToolCallOutputScreenshot, + ResponseContainerReference, + ResponseContent, + ResponseContentPartAddedEvent, + ResponseContentPartDoneEvent, + ResponseConversationParam, + ResponseCreatedEvent, + ResponseCustomToolCall, + ResponseCustomToolCallInputDeltaEvent, + ResponseCustomToolCallInputDoneEvent, + ResponseCustomToolCallItem, + ResponseCustomToolCallOutput, + ResponseCustomToolCallOutputItem, + ResponseError, + ResponseErrorEvent, + ResponseFailedEvent, + ResponseFileSearchCallCompletedEvent, + ResponseFileSearchCallInProgressEvent, + ResponseFileSearchCallSearchingEvent, + ResponseFileSearchToolCall, + ResponseFormatTextConfig, + ResponseFormatTextJSONSchemaConfig, + ResponseFunctionCallArgumentsDeltaEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseFunctionCallOutputItem, + ResponseFunctionCallOutputItemList, + ResponseFunctionShellCallOutputContent, + ResponseFunctionShellToolCall, + ResponseFunctionShellToolCallOutput, + ResponseFunctionToolCall, + ResponseFunctionToolCallItem, + ResponseFunctionToolCallOutputItem, + ResponseFunctionWebSearch, + ResponseImageGenCallCompletedEvent, + ResponseImageGenCallGeneratingEvent, + ResponseImageGenCallInProgressEvent, + ResponseImageGenCallPartialImageEvent, + ResponseInProgressEvent, + ResponseIncludable, + ResponseIncompleteEvent, + ResponseInput, + ResponseInputAudio, + ResponseInputContent, + ResponseInputFile, + ResponseInputFileContent, + ResponseInputImage, + ResponseInputImageContent, + ResponseInputItem, + ResponseInputMessageContentList, + ResponseInputMessageItem, + ResponseInputText, + ResponseInputTextContent, + ResponseItem, + ResponseLocalEnvironment, + ResponseMcpCallArgumentsDeltaEvent, + ResponseMcpCallArgumentsDoneEvent, + ResponseMcpCallCompletedEvent, + ResponseMcpCallFailedEvent, + ResponseMcpCallInProgressEvent, + ResponseMcpListToolsCompletedEvent, + ResponseMcpListToolsFailedEvent, + ResponseMcpListToolsInProgressEvent, + ResponseOutputAudio, + ResponseOutputItem, + ResponseOutputItemAddedEvent, + ResponseOutputItemDoneEvent, + ResponseOutputMessage, + ResponseOutputRefusal, + ResponseOutputText, + ResponseOutputTextAnnotationAddedEvent, + ResponsePrompt, + ResponseQueuedEvent, + ResponseReasoningItem, + ResponseReasoningSummaryPartAddedEvent, + ResponseReasoningSummaryPartDoneEvent, + ResponseReasoningSummaryTextDeltaEvent, + ResponseReasoningSummaryTextDoneEvent, + ResponseReasoningTextDeltaEvent, + ResponseReasoningTextDoneEvent, + ResponseRefusalDeltaEvent, + ResponseRefusalDoneEvent, + ResponseStatus, + ResponseStreamEvent, + ResponseTextConfig, + ResponseTextDeltaEvent, + ResponseTextDoneEvent, + ResponseToolSearchCall, + ResponseToolSearchOutputItem, + ResponseToolSearchOutputItemParam, + ResponseUsage, + ResponseWebSearchCallCompletedEvent, + ResponseWebSearchCallInProgressEvent, + ResponseWebSearchCallSearchingEvent, + ResponsesClientEvent, + ResponsesServerEvent, + SkillReference, + Tool, + ToolChoiceAllowed, + ToolChoiceApplyPatch, + ToolChoiceCustom, + ToolChoiceFunction, + ToolChoiceMcp, + ToolChoiceOptions, + ToolChoiceShell, + ToolChoiceTypes, + ToolSearchTool, + WebSearchPreviewTool, + WebSearchTool, +) +``` + +Methods: + +- client.responses.create(\*\*params) -> Response +- client.responses.retrieve(response_id, \*\*params) -> Response +- client.responses.delete(response_id) -> None +- client.responses.cancel(response_id) -> Response +- client.responses.compact(\*\*params) -> CompactedResponse + +## InputItems + +Types: + +```python +from openai.types.responses import ResponseItemList +``` + +Methods: + +- client.responses.input_items.list(response_id, \*\*params) -> SyncCursorPage[ResponseItem] + +## InputTokens + +Types: + +```python +from openai.types.responses import InputTokenCountResponse +``` + +Methods: + +- client.responses.input_tokens.count(\*\*params) -> InputTokenCountResponse diff --git a/src/openai/resources/responses/input_items.py b/src/openai/resources/responses/input_items.py new file mode 100644 index 0000000000..1d3ed62543 --- /dev/null +++ b/src/openai/resources/responses/input_items.py @@ -0,0 +1,228 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Any, List, cast +from typing_extensions import Literal + +import httpx + +from ... import _legacy_response +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncCursorPage, AsyncCursorPage +from ..._base_client import AsyncPaginator, make_request_options +from ...types.responses import input_item_list_params +from ...types.responses.response_item import ResponseItem +from ...types.responses.response_includable import ResponseIncludable + +__all__ = ["InputItems", "AsyncInputItems"] + + +class InputItems(SyncAPIResource): + @cached_property + def with_raw_response(self) -> InputItemsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return InputItemsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> InputItemsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return InputItemsWithStreamingResponse(self) + + def list( + self, + response_id: str, + *, + after: str | Omit = omit, + include: List[ResponseIncludable] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[ResponseItem]: + """ + Returns a list of input items for a given response. + + Args: + after: An item ID to list items after, used in pagination. + + include: Additional fields to include in the response. See the `include` parameter for + Response creation above for more information. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + order: The order to return the input items in. Default is `desc`. + + - `asc`: Return the input items in ascending order. + - `desc`: Return the input items in descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not response_id: + raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") + return self._get_api_list( + path_template("/responses/{response_id}/input_items", response_id=response_id), + page=SyncCursorPage[ResponseItem], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "include": include, + "limit": limit, + "order": order, + }, + input_item_list_params.InputItemListParams, + ), + security={"bearer_auth": True}, + ), + model=cast(Any, ResponseItem), # Union types cannot be passed in as arguments in the type system + ) + + +class AsyncInputItems(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncInputItemsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncInputItemsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncInputItemsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncInputItemsWithStreamingResponse(self) + + def list( + self, + response_id: str, + *, + after: str | Omit = omit, + include: List[ResponseIncludable] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ResponseItem, AsyncCursorPage[ResponseItem]]: + """ + Returns a list of input items for a given response. + + Args: + after: An item ID to list items after, used in pagination. + + include: Additional fields to include in the response. See the `include` parameter for + Response creation above for more information. + + limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. + + order: The order to return the input items in. Default is `desc`. + + - `asc`: Return the input items in ascending order. + - `desc`: Return the input items in descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not response_id: + raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") + return self._get_api_list( + path_template("/responses/{response_id}/input_items", response_id=response_id), + page=AsyncCursorPage[ResponseItem], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "include": include, + "limit": limit, + "order": order, + }, + input_item_list_params.InputItemListParams, + ), + security={"bearer_auth": True}, + ), + model=cast(Any, ResponseItem), # Union types cannot be passed in as arguments in the type system + ) + + +class InputItemsWithRawResponse: + def __init__(self, input_items: InputItems) -> None: + self._input_items = input_items + + self.list = _legacy_response.to_raw_response_wrapper( + input_items.list, + ) + + +class AsyncInputItemsWithRawResponse: + def __init__(self, input_items: AsyncInputItems) -> None: + self._input_items = input_items + + self.list = _legacy_response.async_to_raw_response_wrapper( + input_items.list, + ) + + +class InputItemsWithStreamingResponse: + def __init__(self, input_items: InputItems) -> None: + self._input_items = input_items + + self.list = to_streamed_response_wrapper( + input_items.list, + ) + + +class AsyncInputItemsWithStreamingResponse: + def __init__(self, input_items: AsyncInputItems) -> None: + self._input_items = input_items + + self.list = async_to_streamed_response_wrapper( + input_items.list, + ) diff --git a/src/openai/resources/responses/input_tokens.py b/src/openai/resources/responses/input_tokens.py new file mode 100644 index 0000000000..3306dfcd65 --- /dev/null +++ b/src/openai/resources/responses/input_tokens.py @@ -0,0 +1,331 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal + +import httpx + +from ... import _legacy_response +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options +from ...types.responses import input_token_count_params +from ...types.responses.tool_param import ToolParam +from ...types.shared_params.reasoning import Reasoning +from ...types.responses.response_input_item_param import ResponseInputItemParam +from ...types.responses.input_token_count_response import InputTokenCountResponse + +__all__ = ["InputTokens", "AsyncInputTokens"] + + +class InputTokens(SyncAPIResource): + @cached_property + def with_raw_response(self) -> InputTokensWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return InputTokensWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> InputTokensWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return InputTokensWithStreamingResponse(self) + + def count( + self, + *, + conversation: Optional[input_token_count_params.Conversation] | Omit = omit, + input: Union[str, Iterable[ResponseInputItemParam], None] | Omit = omit, + instructions: Optional[str] | Omit = omit, + model: Optional[str] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + personality: Union[str, Literal["friendly", "pragmatic"]] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + text: Optional[input_token_count_params.Text] | Omit = omit, + tool_choice: Optional[input_token_count_params.ToolChoice] | Omit = omit, + tools: Optional[Iterable[ToolParam]] | Omit = omit, + truncation: Literal["auto", "disabled"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> InputTokenCountResponse: + """ + Returns input token counts of the request. + + Returns an object with `object` set to `response.input_tokens` and an + `input_tokens` count. + + Args: + conversation: The conversation that this response belongs to. Items from this conversation are + prepended to `input_items` for this response request. Input items and output + items from this response are automatically added to this conversation after this + response completes. + + input: Text, image, or file inputs to the model, used to generate a response + + instructions: A system (or developer) message inserted into the model's context. When used + along with `previous_response_id`, the instructions from a previous response + will not be carried over to the next response. This makes it simple to swap out + system (or developer) messages in new responses. + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + parallel_tool_calls: Whether to allow the model to run tool calls in parallel. + + personality: A model-owned style preset to apply to this request. Omit this parameter to use + the model's default style. Supported values may expand over time. Values must be + at most 64 characters. + + previous_response_id: The unique ID of the previous response to the model. Use this to create + multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + + reasoning: **gpt-5 and o-series models only** Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + + text: Configuration options for a text response from the model. Can be plain text or + structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + + tool_choice: Controls which tool the model should use, if any. + + tools: An array of tools the model may call while generating a response. You can + specify which tool to use by setting the `tool_choice` parameter. + + truncation: The truncation strategy to use for the model response. - `auto`: If the input to + this Response exceeds the model's context window size, the model will truncate + the response to fit the context window by dropping items from the beginning of + the conversation. - `disabled` (default): If the input size will exceed the + context window size for a model, the request will fail with a 400 error. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/responses/input_tokens", + body=maybe_transform( + { + "conversation": conversation, + "input": input, + "instructions": instructions, + "model": model, + "parallel_tool_calls": parallel_tool_calls, + "personality": personality, + "previous_response_id": previous_response_id, + "reasoning": reasoning, + "text": text, + "tool_choice": tool_choice, + "tools": tools, + "truncation": truncation, + }, + input_token_count_params.InputTokenCountParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=InputTokenCountResponse, + ) + + +class AsyncInputTokens(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncInputTokensWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncInputTokensWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncInputTokensWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncInputTokensWithStreamingResponse(self) + + async def count( + self, + *, + conversation: Optional[input_token_count_params.Conversation] | Omit = omit, + input: Union[str, Iterable[ResponseInputItemParam], None] | Omit = omit, + instructions: Optional[str] | Omit = omit, + model: Optional[str] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + personality: Union[str, Literal["friendly", "pragmatic"]] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + text: Optional[input_token_count_params.Text] | Omit = omit, + tool_choice: Optional[input_token_count_params.ToolChoice] | Omit = omit, + tools: Optional[Iterable[ToolParam]] | Omit = omit, + truncation: Literal["auto", "disabled"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> InputTokenCountResponse: + """ + Returns input token counts of the request. + + Returns an object with `object` set to `response.input_tokens` and an + `input_tokens` count. + + Args: + conversation: The conversation that this response belongs to. Items from this conversation are + prepended to `input_items` for this response request. Input items and output + items from this response are automatically added to this conversation after this + response completes. + + input: Text, image, or file inputs to the model, used to generate a response + + instructions: A system (or developer) message inserted into the model's context. When used + along with `previous_response_id`, the instructions from a previous response + will not be carried over to the next response. This makes it simple to swap out + system (or developer) messages in new responses. + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + parallel_tool_calls: Whether to allow the model to run tool calls in parallel. + + personality: A model-owned style preset to apply to this request. Omit this parameter to use + the model's default style. Supported values may expand over time. Values must be + at most 64 characters. + + previous_response_id: The unique ID of the previous response to the model. Use this to create + multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + + reasoning: **gpt-5 and o-series models only** Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + + text: Configuration options for a text response from the model. Can be plain text or + structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + + tool_choice: Controls which tool the model should use, if any. + + tools: An array of tools the model may call while generating a response. You can + specify which tool to use by setting the `tool_choice` parameter. + + truncation: The truncation strategy to use for the model response. - `auto`: If the input to + this Response exceeds the model's context window size, the model will truncate + the response to fit the context window by dropping items from the beginning of + the conversation. - `disabled` (default): If the input size will exceed the + context window size for a model, the request will fail with a 400 error. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/responses/input_tokens", + body=await async_maybe_transform( + { + "conversation": conversation, + "input": input, + "instructions": instructions, + "model": model, + "parallel_tool_calls": parallel_tool_calls, + "personality": personality, + "previous_response_id": previous_response_id, + "reasoning": reasoning, + "text": text, + "tool_choice": tool_choice, + "tools": tools, + "truncation": truncation, + }, + input_token_count_params.InputTokenCountParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=InputTokenCountResponse, + ) + + +class InputTokensWithRawResponse: + def __init__(self, input_tokens: InputTokens) -> None: + self._input_tokens = input_tokens + + self.count = _legacy_response.to_raw_response_wrapper( + input_tokens.count, + ) + + +class AsyncInputTokensWithRawResponse: + def __init__(self, input_tokens: AsyncInputTokens) -> None: + self._input_tokens = input_tokens + + self.count = _legacy_response.async_to_raw_response_wrapper( + input_tokens.count, + ) + + +class InputTokensWithStreamingResponse: + def __init__(self, input_tokens: InputTokens) -> None: + self._input_tokens = input_tokens + + self.count = to_streamed_response_wrapper( + input_tokens.count, + ) + + +class AsyncInputTokensWithStreamingResponse: + def __init__(self, input_tokens: AsyncInputTokens) -> None: + self._input_tokens = input_tokens + + self.count = async_to_streamed_response_wrapper( + input_tokens.count, + ) diff --git a/src/openai/resources/responses/responses.py b/src/openai/resources/responses/responses.py new file mode 100644 index 0000000000..5019d7e831 --- /dev/null +++ b/src/openai/resources/responses/responses.py @@ -0,0 +1,4824 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import json +import time +import random +import logging +from copy import copy +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + List, + Type, + Union, + Callable, + Iterable, + Iterator, + Optional, + Awaitable, + AsyncIterator, + cast, +) +from functools import partial +from typing_extensions import Literal, overload + +import httpx +from pydantic import BaseModel + +from ... import _legacy_response +from ..._types import NOT_GIVEN, Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform +from ..._compat import cached_property +from ..._models import construct_type_unchecked +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .input_items import ( + InputItems, + AsyncInputItems, + InputItemsWithRawResponse, + AsyncInputItemsWithRawResponse, + InputItemsWithStreamingResponse, + AsyncInputItemsWithStreamingResponse, +) +from ..._streaming import Stream, AsyncStream +from ...lib._tools import PydanticFunctionTool, ResponsesPydanticFunctionTool +from .input_tokens import ( + InputTokens, + AsyncInputTokens, + InputTokensWithRawResponse, + AsyncInputTokensWithRawResponse, + InputTokensWithStreamingResponse, + AsyncInputTokensWithStreamingResponse, +) +from ..._exceptions import OpenAIError, WebSocketConnectionClosedError +from ..._send_queue import SendQueue +from ..._base_client import _merge_mappings, make_request_options +from ..._event_handler import EventHandlerRegistry +from ...types.responses import ( + response_create_params, + response_compact_params, + response_retrieve_params, + responses_client_event_param, +) +from ...lib._parsing._responses import ( + TextFormatT, + parse_response, + type_to_text_format_param as _type_to_text_format_param, +) +from ...types.responses.response import Response +from ...types.responses.tool_param import ToolParam, ParseableToolParam +from ...types.shared_params.metadata import Metadata +from ...types.websocket_reconnection import ReconnectingEvent, ReconnectingOverrides, is_recoverable_close +from ...types.shared_params.reasoning import Reasoning +from ...types.responses.parsed_response import ParsedResponse +from ...lib.streaming.responses._responses import ResponseStreamManager, AsyncResponseStreamManager +from ...types.responses.compacted_response import CompactedResponse +from ...types.websocket_connection_options import WebSocketConnectionOptions +from ...types.responses.response_includable import ResponseIncludable +from ...types.shared_params.responses_model import ResponsesModel +from ...types.responses.response_error_event import ResponseErrorEvent +from ...types.responses.response_input_param import ResponseInputParam +from ...types.responses.response_prompt_param import ResponsePromptParam +from ...types.responses.response_stream_event import ResponseStreamEvent +from ...types.responses.responses_client_event import ResponsesClientEvent +from ...types.responses.responses_server_event import ResponsesServerEvent +from ...types.responses.response_input_item_param import ResponseInputItemParam +from ...types.responses.response_text_config_param import ResponseTextConfigParam +from ...types.responses.responses_client_event_param import ResponsesClientEventParam + +if TYPE_CHECKING: + from websockets.sync.client import ClientConnection as WebSocketConnection + from websockets.asyncio.client import ClientConnection as AsyncWebSocketConnection + + from ..._client import OpenAI, AsyncOpenAI + +__all__ = ["Responses", "AsyncResponses"] + +log: logging.Logger = logging.getLogger(__name__) + + +class Responses(SyncAPIResource): + @cached_property + def input_items(self) -> InputItems: + return InputItems(self._client) + + @cached_property + def input_tokens(self) -> InputTokens: + return InputTokens(self._client) + + @cached_property + def with_raw_response(self) -> ResponsesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ResponsesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ResponsesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ResponsesWithStreamingResponse(self) + + @overload + def create( + self, + *, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + tools: Iterable[ToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response: + """Creates a model response. + + Provide + [text](https://platform.openai.com/docs/guides/text) or + [image](https://platform.openai.com/docs/guides/images) inputs to generate + [text](https://platform.openai.com/docs/guides/text) or + [JSON](https://platform.openai.com/docs/guides/structured-outputs) outputs. Have + the model call your own + [custom code](https://platform.openai.com/docs/guides/function-calling) or use + built-in [tools](https://platform.openai.com/docs/guides/tools) like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search) to use + your own data as input for the model's response. + + Args: + background: Whether to run the model response in the background. + [Learn more](https://platform.openai.com/docs/guides/background). + + context_management: Context management configuration for this request. + + conversation: The conversation that this response belongs to. Items from this conversation are + prepended to `input_items` for this response request. Input items and output + items from this response are automatically added to this conversation after this + response completes. + + include: Specify additional output data to include in the model response. Currently + supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + + input: Text, image, or file inputs to the model, used to generate a response. + + Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Image inputs](https://platform.openai.com/docs/guides/images) + - [File inputs](https://platform.openai.com/docs/guides/pdf-files) + - [Conversation state](https://platform.openai.com/docs/guides/conversation-state) + - [Function calling](https://platform.openai.com/docs/guides/function-calling) + + instructions: A system (or developer) message inserted into the model's context. + + When using along with `previous_response_id`, the instructions from a previous + response will not be carried over to the next response. This makes it simple to + swap out system (or developer) messages in new responses. + + max_output_tokens: An upper bound for the number of tokens that can be generated for a response, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tool_calls: The maximum number of total calls to built-in tools that can be processed in a + response. This maximum number applies across all built-in tool calls, not per + individual tool. Any further attempts to call a tool by the model will be + ignored. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + moderation: Configuration for running moderation on the input and output of this response. + + parallel_tool_calls: Whether to allow the model to run tool calls in parallel. + + previous_response_id: The unique ID of the previous response to the model. Use this to create + multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + + prompt: Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning: **gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + store: Whether to store the generated model response for later retrieval via API. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + stream_options: Options for streaming responses. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + text: Configuration options for a text response from the model. Can be plain text or + structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + + tool_choice: How the model should select which tool (or tools) to use when generating a + response. See the `tools` parameter to see how to specify which tools the model + can call. + + tools: An array of tools the model may call while generating a response. You can + specify which tool to use by setting the `tool_choice` parameter. + + We support the following categories of tools: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **MCP Tools**: Integrations with third-party systems via custom MCP servers or + predefined connectors such as Google Drive and SharePoint. Learn more about + [MCP Tools](https://platform.openai.com/docs/guides/tools-connectors-mcp). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code with strongly typed arguments and outputs. + Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + You can also use custom tools to call your own code. + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + truncation: The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. + - `disabled` (default): If the input size will exceed the context window size + for a model, the request will fail with a 400 error. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def create( + self, + *, + stream: Literal[True], + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + tools: Iterable[ToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Stream[ResponseStreamEvent]: + """Creates a model response. + + Provide + [text](https://platform.openai.com/docs/guides/text) or + [image](https://platform.openai.com/docs/guides/images) inputs to generate + [text](https://platform.openai.com/docs/guides/text) or + [JSON](https://platform.openai.com/docs/guides/structured-outputs) outputs. Have + the model call your own + [custom code](https://platform.openai.com/docs/guides/function-calling) or use + built-in [tools](https://platform.openai.com/docs/guides/tools) like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search) to use + your own data as input for the model's response. + + Args: + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + background: Whether to run the model response in the background. + [Learn more](https://platform.openai.com/docs/guides/background). + + context_management: Context management configuration for this request. + + conversation: The conversation that this response belongs to. Items from this conversation are + prepended to `input_items` for this response request. Input items and output + items from this response are automatically added to this conversation after this + response completes. + + include: Specify additional output data to include in the model response. Currently + supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + + input: Text, image, or file inputs to the model, used to generate a response. + + Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Image inputs](https://platform.openai.com/docs/guides/images) + - [File inputs](https://platform.openai.com/docs/guides/pdf-files) + - [Conversation state](https://platform.openai.com/docs/guides/conversation-state) + - [Function calling](https://platform.openai.com/docs/guides/function-calling) + + instructions: A system (or developer) message inserted into the model's context. + + When using along with `previous_response_id`, the instructions from a previous + response will not be carried over to the next response. This makes it simple to + swap out system (or developer) messages in new responses. + + max_output_tokens: An upper bound for the number of tokens that can be generated for a response, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tool_calls: The maximum number of total calls to built-in tools that can be processed in a + response. This maximum number applies across all built-in tool calls, not per + individual tool. Any further attempts to call a tool by the model will be + ignored. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + moderation: Configuration for running moderation on the input and output of this response. + + parallel_tool_calls: Whether to allow the model to run tool calls in parallel. + + previous_response_id: The unique ID of the previous response to the model. Use this to create + multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + + prompt: Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning: **gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + store: Whether to store the generated model response for later retrieval via API. + + stream_options: Options for streaming responses. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + text: Configuration options for a text response from the model. Can be plain text or + structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + + tool_choice: How the model should select which tool (or tools) to use when generating a + response. See the `tools` parameter to see how to specify which tools the model + can call. + + tools: An array of tools the model may call while generating a response. You can + specify which tool to use by setting the `tool_choice` parameter. + + We support the following categories of tools: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **MCP Tools**: Integrations with third-party systems via custom MCP servers or + predefined connectors such as Google Drive and SharePoint. Learn more about + [MCP Tools](https://platform.openai.com/docs/guides/tools-connectors-mcp). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code with strongly typed arguments and outputs. + Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + You can also use custom tools to call your own code. + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + truncation: The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. + - `disabled` (default): If the input size will exceed the context window size + for a model, the request will fail with a 400 error. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def create( + self, + *, + stream: bool, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + tools: Iterable[ToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response | Stream[ResponseStreamEvent]: + """Creates a model response. + + Provide + [text](https://platform.openai.com/docs/guides/text) or + [image](https://platform.openai.com/docs/guides/images) inputs to generate + [text](https://platform.openai.com/docs/guides/text) or + [JSON](https://platform.openai.com/docs/guides/structured-outputs) outputs. Have + the model call your own + [custom code](https://platform.openai.com/docs/guides/function-calling) or use + built-in [tools](https://platform.openai.com/docs/guides/tools) like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search) to use + your own data as input for the model's response. + + Args: + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + background: Whether to run the model response in the background. + [Learn more](https://platform.openai.com/docs/guides/background). + + context_management: Context management configuration for this request. + + conversation: The conversation that this response belongs to. Items from this conversation are + prepended to `input_items` for this response request. Input items and output + items from this response are automatically added to this conversation after this + response completes. + + include: Specify additional output data to include in the model response. Currently + supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + + input: Text, image, or file inputs to the model, used to generate a response. + + Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Image inputs](https://platform.openai.com/docs/guides/images) + - [File inputs](https://platform.openai.com/docs/guides/pdf-files) + - [Conversation state](https://platform.openai.com/docs/guides/conversation-state) + - [Function calling](https://platform.openai.com/docs/guides/function-calling) + + instructions: A system (or developer) message inserted into the model's context. + + When using along with `previous_response_id`, the instructions from a previous + response will not be carried over to the next response. This makes it simple to + swap out system (or developer) messages in new responses. + + max_output_tokens: An upper bound for the number of tokens that can be generated for a response, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tool_calls: The maximum number of total calls to built-in tools that can be processed in a + response. This maximum number applies across all built-in tool calls, not per + individual tool. Any further attempts to call a tool by the model will be + ignored. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + moderation: Configuration for running moderation on the input and output of this response. + + parallel_tool_calls: Whether to allow the model to run tool calls in parallel. + + previous_response_id: The unique ID of the previous response to the model. Use this to create + multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + + prompt: Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning: **gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + store: Whether to store the generated model response for later retrieval via API. + + stream_options: Options for streaming responses. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + text: Configuration options for a text response from the model. Can be plain text or + structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + + tool_choice: How the model should select which tool (or tools) to use when generating a + response. See the `tools` parameter to see how to specify which tools the model + can call. + + tools: An array of tools the model may call while generating a response. You can + specify which tool to use by setting the `tool_choice` parameter. + + We support the following categories of tools: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **MCP Tools**: Integrations with third-party systems via custom MCP servers or + predefined connectors such as Google Drive and SharePoint. Learn more about + [MCP Tools](https://platform.openai.com/docs/guides/tools-connectors-mcp). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code with strongly typed arguments and outputs. + Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + You can also use custom tools to call your own code. + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + truncation: The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. + - `disabled` (default): If the input size will exceed the context window size + for a model, the request will fail with a 400 error. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + def create( + self, + *, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + tools: Iterable[ToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response | Stream[ResponseStreamEvent]: + return self._post( + "/responses", + body=maybe_transform( + { + "background": background, + "context_management": context_management, + "conversation": conversation, + "include": include, + "input": input, + "instructions": instructions, + "max_output_tokens": max_output_tokens, + "max_tool_calls": max_tool_calls, + "metadata": metadata, + "model": model, + "moderation": moderation, + "parallel_tool_calls": parallel_tool_calls, + "previous_response_id": previous_response_id, + "prompt": prompt, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning": reasoning, + "safety_identifier": safety_identifier, + "service_tier": service_tier, + "store": store, + "stream": stream, + "stream_options": stream_options, + "temperature": temperature, + "text": text, + "tool_choice": tool_choice, + "tools": tools, + "top_logprobs": top_logprobs, + "top_p": top_p, + "truncation": truncation, + "user": user, + }, + response_create_params.ResponseCreateParamsStreaming + if stream + else response_create_params.ResponseCreateParamsNonStreaming, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Response, + stream=stream or False, + stream_cls=Stream[ResponseStreamEvent], + ) + + @overload + def stream( + self, + *, + response_id: str, + text_format: type[TextFormatT] | Omit = omit, + starting_after: int | Omit = omit, + tools: Iterable[ParseableToolParam] | Omit = omit, + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ResponseStreamManager[TextFormatT]: ... + + @overload + def stream( + self, + *, + input: Union[str, ResponseInputParam], + model: ResponsesModel, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + text_format: type[TextFormatT] | Omit = omit, + tools: Iterable[ParseableToolParam] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ResponseStreamManager[TextFormatT]: ... + + def stream( + self, + *, + response_id: str | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + model: ResponsesModel | Omit = omit, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + text_format: type[TextFormatT] | Omit = omit, + tools: Iterable[ParseableToolParam] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ResponseStreamManager[TextFormatT]: + new_response_args = { + "input": input, + "model": model, + "context_management": context_management, + "conversation": conversation, + "include": include, + "instructions": instructions, + "max_output_tokens": max_output_tokens, + "max_tool_calls": max_tool_calls, + "metadata": metadata, + "moderation": moderation, + "parallel_tool_calls": parallel_tool_calls, + "previous_response_id": previous_response_id, + "prompt": prompt, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning": reasoning, + "safety_identifier": safety_identifier, + "service_tier": service_tier, + "store": store, + "stream_options": stream_options, + "temperature": temperature, + "text": text, + "tool_choice": tool_choice, + "top_logprobs": top_logprobs, + "top_p": top_p, + "truncation": truncation, + "user": user, + "background": background, + } + new_response_args_names = [k for k, v in new_response_args.items() if is_given(v)] + + if (is_given(response_id) or is_given(starting_after)) and len(new_response_args_names) > 0: + raise ValueError( + "Cannot provide both response_id/starting_after can't be provided together with " + + ", ".join(new_response_args_names) + ) + tools = _make_tools(tools) + if len(new_response_args_names) > 0: + if not is_given(input): + raise ValueError("input must be provided when creating a new response") + + if not is_given(model): + raise ValueError("model must be provided when creating a new response") + + if is_given(text_format): + if not text: + text = {} + + if "format" in text: + raise TypeError("Cannot mix and match text.format with text_format") + + text = copy(text) + text["format"] = _type_to_text_format_param(text_format) + + api_request: partial[Stream[ResponseStreamEvent]] = partial( + self.create, + input=input, + model=model, + tools=tools, + context_management=context_management, + conversation=conversation, + include=include, + instructions=instructions, + max_output_tokens=max_output_tokens, + max_tool_calls=max_tool_calls, + metadata=metadata, + moderation=moderation, + parallel_tool_calls=parallel_tool_calls, + previous_response_id=previous_response_id, + prompt=prompt, + prompt_cache_key=prompt_cache_key, + prompt_cache_retention=prompt_cache_retention, + store=store, + stream_options=stream_options, + stream=True, + temperature=temperature, + text=text, + tool_choice=tool_choice, + reasoning=reasoning, + safety_identifier=safety_identifier, + service_tier=service_tier, + top_logprobs=top_logprobs, + top_p=top_p, + truncation=truncation, + user=user, + background=background, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + return ResponseStreamManager(api_request, text_format=text_format, input_tools=tools, starting_after=None) + else: + if not is_given(response_id): + raise ValueError("id must be provided when streaming an existing response") + + return ResponseStreamManager( + lambda: self.retrieve( + response_id=response_id, + stream=True, + include=include or [], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + starting_after=omit, + timeout=timeout, + ), + text_format=text_format, + input_tools=tools, + starting_after=starting_after if is_given(starting_after) else None, + ) + + def parse( + self, + *, + text_format: type[TextFormatT] | Omit = omit, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + tools: Iterable[ParseableToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ParsedResponse[TextFormatT]: + if is_given(text_format): + if not text: + text = {} + + if "format" in text: + raise TypeError("Cannot mix and match text.format with text_format") + text = copy(text) + text["format"] = _type_to_text_format_param(text_format) + + tools = _make_tools(tools) + + def parser(raw_response: Response) -> ParsedResponse[TextFormatT]: + return parse_response( + input_tools=tools, + text_format=text_format, + response=raw_response, + ) + + return self._post( + "/responses", + body=maybe_transform( + { + "background": background, + "context_management": context_management, + "conversation": conversation, + "include": include, + "input": input, + "instructions": instructions, + "max_output_tokens": max_output_tokens, + "max_tool_calls": max_tool_calls, + "metadata": metadata, + "model": model, + "moderation": moderation, + "parallel_tool_calls": parallel_tool_calls, + "previous_response_id": previous_response_id, + "prompt": prompt, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning": reasoning, + "safety_identifier": safety_identifier, + "service_tier": service_tier, + "store": store, + "stream": stream, + "stream_options": stream_options, + "temperature": temperature, + "text": text, + "tool_choice": tool_choice, + "tools": tools, + "top_logprobs": top_logprobs, + "top_p": top_p, + "truncation": truncation, + "user": user, + "verbosity": verbosity, + }, + response_create_params.ResponseCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + post_parser=parser, + security={"bearer_auth": True}, + ), + # we turn the `Response` instance into a `ParsedResponse` + # in the `parser` function above + cast_to=cast(Type[ParsedResponse[TextFormatT]], Response), + ) + + @overload + def retrieve( + self, + response_id: str, + *, + include: List[ResponseIncludable] | Omit = omit, + include_obfuscation: bool | Omit = omit, + starting_after: int | Omit = omit, + stream: Literal[False] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response: ... + + @overload + def retrieve( + self, + response_id: str, + *, + stream: Literal[True], + include: List[ResponseIncludable] | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Stream[ResponseStreamEvent]: ... + + @overload + def retrieve( + self, + response_id: str, + *, + stream: bool, + include: List[ResponseIncludable] | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Response | Stream[ResponseStreamEvent]: ... + + @overload + def retrieve( + self, + response_id: str, + *, + stream: bool = False, + include: List[ResponseIncludable] | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Response | Stream[ResponseStreamEvent]: + """ + Retrieves a model response with the given ID. + + Args: + include: Additional fields to include in the response. See the `include` parameter for + Response creation above for more information. + + include_obfuscation: When true, stream obfuscation will be enabled. Stream obfuscation adds random + characters to an `obfuscation` field on streaming delta events to normalize + payload sizes as a mitigation to certain side-channel attacks. These obfuscation + fields are included by default, but add a small amount of overhead to the data + stream. You can set `include_obfuscation` to false to optimize for bandwidth if + you trust the network links between your application and the OpenAI API. + + starting_after: The sequence number of the event after which to start streaming. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def retrieve( + self, + response_id: str, + *, + stream: Literal[True], + include: List[ResponseIncludable] | Omit = omit, + include_obfuscation: bool | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Stream[ResponseStreamEvent]: + """ + Retrieves a model response with the given ID. + + Args: + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + include: Additional fields to include in the response. See the `include` parameter for + Response creation above for more information. + + include_obfuscation: When true, stream obfuscation will be enabled. Stream obfuscation adds random + characters to an `obfuscation` field on streaming delta events to normalize + payload sizes as a mitigation to certain side-channel attacks. These obfuscation + fields are included by default, but add a small amount of overhead to the data + stream. You can set `include_obfuscation` to false to optimize for bandwidth if + you trust the network links between your application and the OpenAI API. + + starting_after: The sequence number of the event after which to start streaming. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def retrieve( + self, + response_id: str, + *, + stream: bool, + include: List[ResponseIncludable] | Omit = omit, + include_obfuscation: bool | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response | Stream[ResponseStreamEvent]: + """ + Retrieves a model response with the given ID. + + Args: + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + include: Additional fields to include in the response. See the `include` parameter for + Response creation above for more information. + + include_obfuscation: When true, stream obfuscation will be enabled. Stream obfuscation adds random + characters to an `obfuscation` field on streaming delta events to normalize + payload sizes as a mitigation to certain side-channel attacks. These obfuscation + fields are included by default, but add a small amount of overhead to the data + stream. You can set `include_obfuscation` to false to optimize for bandwidth if + you trust the network links between your application and the OpenAI API. + + starting_after: The sequence number of the event after which to start streaming. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + def retrieve( + self, + response_id: str, + *, + include: List[ResponseIncludable] | Omit = omit, + include_obfuscation: bool | Omit = omit, + starting_after: int | Omit = omit, + stream: Literal[False] | Literal[True] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response | Stream[ResponseStreamEvent]: + if not response_id: + raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") + return self._get( + path_template("/responses/{response_id}", response_id=response_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "include": include, + "include_obfuscation": include_obfuscation, + "starting_after": starting_after, + "stream": stream, + }, + response_retrieve_params.ResponseRetrieveParams, + ), + security={"bearer_auth": True}, + ), + cast_to=Response, + stream=stream or False, + stream_cls=Stream[ResponseStreamEvent], + ) + + def delete( + self, + response_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Deletes a model response with the given ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not response_id: + raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + path_template("/responses/{response_id}", response_id=response_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + def cancel( + self, + response_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response: + """Cancels a model response with the given ID. + + Only responses created with the + `background` parameter set to `true` can be cancelled. + [Learn more](https://platform.openai.com/docs/guides/background). + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not response_id: + raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") + return self._post( + path_template("/responses/{response_id}/cancel", response_id=response_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Response, + ) + + def compact( + self, + *, + model: Union[ + Literal[ + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gpt-5.4-mini-2026-03-17", + "gpt-5.4-nano-2026-03-17", + "gpt-5.3-chat-latest", + "gpt-5.2", + "gpt-5.2-2025-12-11", + "gpt-5.2-chat-latest", + "gpt-5.2-pro", + "gpt-5.2-pro-2025-12-11", + "gpt-5.1", + "gpt-5.1-2025-11-13", + "gpt-5.1-codex", + "gpt-5.1-mini", + "gpt-5.1-chat-latest", + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-5-chat-latest", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o4-mini", + "o4-mini-2025-04-16", + "o3", + "o3-2025-04-16", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "o1-preview", + "o1-preview-2024-09-12", + "o1-mini", + "o1-mini-2024-09-12", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-audio-preview", + "gpt-4o-audio-preview-2024-10-01", + "gpt-4o-audio-preview-2024-12-17", + "gpt-4o-audio-preview-2025-06-03", + "gpt-4o-mini-audio-preview", + "gpt-4o-mini-audio-preview-2024-12-17", + "gpt-4o-search-preview", + "gpt-4o-mini-search-preview", + "gpt-4o-search-preview-2025-03-11", + "gpt-4o-mini-search-preview-2025-03-11", + "chatgpt-4o-latest", + "codex-mini-latest", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0301", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613", + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max", + ], + str, + None, + ], + input: Union[str, Iterable[ResponseInputItemParam], None] | Omit = omit, + instructions: Optional[str] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt_cache_key: Optional[str] | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "priority"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CompactedResponse: + """Compact a conversation. + + Returns a compacted response object. + + Learn when and how to compact long-running conversations in the + [conversation state guide](https://platform.openai.com/docs/guides/conversation-state#managing-the-context-window). + For ZDR-compatible compaction details, see + [Compaction (advanced)](https://platform.openai.com/docs/guides/conversation-state#compaction-advanced). + + Args: + model: Model ID used to generate the response, like `gpt-5` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + input: Text, image, or file inputs to the model, used to generate a response + + instructions: A system (or developer) message inserted into the model's context. When used + along with `previous_response_id`, the instructions from a previous response + will not be carried over to the next response. This makes it simple to swap out + system (or developer) messages in new responses. + + previous_response_id: The unique ID of the previous response to the model. Use this to create + multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + + prompt_cache_key: A key to use when reading from or writing to the prompt cache. + + prompt_cache_retention: How long to retain a prompt cache entry created by this request. + + service_tier: The service tier to use for this request. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/responses/compact", + body=maybe_transform( + { + "model": model, + "input": input, + "instructions": instructions, + "previous_response_id": previous_response_id, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "service_tier": service_tier, + }, + response_compact_params.ResponseCompactParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=CompactedResponse, + ) + + def connect( + self, + extra_query: Query = {}, + extra_headers: Headers = {}, + websocket_connection_options: WebSocketConnectionOptions = {}, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + max_queue_size: int = 1_048_576, + ) -> ResponsesConnectionManager: + """Connect to a persistent Responses API WebSocket. + + Send `response.create` events and receive response stream events over the socket. + """ + return ResponsesConnectionManager( + client=self._client, + extra_query=extra_query, + extra_headers=extra_headers, + websocket_connection_options=websocket_connection_options, + on_reconnecting=on_reconnecting, + max_retries=max_retries, + initial_delay=initial_delay, + max_delay=max_delay, + max_queue_size=max_queue_size, + ) + + +class AsyncResponses(AsyncAPIResource): + @cached_property + def input_items(self) -> AsyncInputItems: + return AsyncInputItems(self._client) + + @cached_property + def input_tokens(self) -> AsyncInputTokens: + return AsyncInputTokens(self._client) + + @cached_property + def with_raw_response(self) -> AsyncResponsesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncResponsesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncResponsesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncResponsesWithStreamingResponse(self) + + @overload + async def create( + self, + *, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + tools: Iterable[ToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response: + """Creates a model response. + + Provide + [text](https://platform.openai.com/docs/guides/text) or + [image](https://platform.openai.com/docs/guides/images) inputs to generate + [text](https://platform.openai.com/docs/guides/text) or + [JSON](https://platform.openai.com/docs/guides/structured-outputs) outputs. Have + the model call your own + [custom code](https://platform.openai.com/docs/guides/function-calling) or use + built-in [tools](https://platform.openai.com/docs/guides/tools) like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search) to use + your own data as input for the model's response. + + Args: + background: Whether to run the model response in the background. + [Learn more](https://platform.openai.com/docs/guides/background). + + context_management: Context management configuration for this request. + + conversation: The conversation that this response belongs to. Items from this conversation are + prepended to `input_items` for this response request. Input items and output + items from this response are automatically added to this conversation after this + response completes. + + include: Specify additional output data to include in the model response. Currently + supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + + input: Text, image, or file inputs to the model, used to generate a response. + + Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Image inputs](https://platform.openai.com/docs/guides/images) + - [File inputs](https://platform.openai.com/docs/guides/pdf-files) + - [Conversation state](https://platform.openai.com/docs/guides/conversation-state) + - [Function calling](https://platform.openai.com/docs/guides/function-calling) + + instructions: A system (or developer) message inserted into the model's context. + + When using along with `previous_response_id`, the instructions from a previous + response will not be carried over to the next response. This makes it simple to + swap out system (or developer) messages in new responses. + + max_output_tokens: An upper bound for the number of tokens that can be generated for a response, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tool_calls: The maximum number of total calls to built-in tools that can be processed in a + response. This maximum number applies across all built-in tool calls, not per + individual tool. Any further attempts to call a tool by the model will be + ignored. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + moderation: Configuration for running moderation on the input and output of this response. + + parallel_tool_calls: Whether to allow the model to run tool calls in parallel. + + previous_response_id: The unique ID of the previous response to the model. Use this to create + multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + + prompt: Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning: **gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + store: Whether to store the generated model response for later retrieval via API. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + stream_options: Options for streaming responses. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + text: Configuration options for a text response from the model. Can be plain text or + structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + + tool_choice: How the model should select which tool (or tools) to use when generating a + response. See the `tools` parameter to see how to specify which tools the model + can call. + + tools: An array of tools the model may call while generating a response. You can + specify which tool to use by setting the `tool_choice` parameter. + + We support the following categories of tools: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **MCP Tools**: Integrations with third-party systems via custom MCP servers or + predefined connectors such as Google Drive and SharePoint. Learn more about + [MCP Tools](https://platform.openai.com/docs/guides/tools-connectors-mcp). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code with strongly typed arguments and outputs. + Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + You can also use custom tools to call your own code. + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + truncation: The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. + - `disabled` (default): If the input size will exceed the context window size + for a model, the request will fail with a 400 error. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def create( + self, + *, + stream: Literal[True], + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + tools: Iterable[ToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncStream[ResponseStreamEvent]: + """Creates a model response. + + Provide + [text](https://platform.openai.com/docs/guides/text) or + [image](https://platform.openai.com/docs/guides/images) inputs to generate + [text](https://platform.openai.com/docs/guides/text) or + [JSON](https://platform.openai.com/docs/guides/structured-outputs) outputs. Have + the model call your own + [custom code](https://platform.openai.com/docs/guides/function-calling) or use + built-in [tools](https://platform.openai.com/docs/guides/tools) like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search) to use + your own data as input for the model's response. + + Args: + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + background: Whether to run the model response in the background. + [Learn more](https://platform.openai.com/docs/guides/background). + + context_management: Context management configuration for this request. + + conversation: The conversation that this response belongs to. Items from this conversation are + prepended to `input_items` for this response request. Input items and output + items from this response are automatically added to this conversation after this + response completes. + + include: Specify additional output data to include in the model response. Currently + supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + + input: Text, image, or file inputs to the model, used to generate a response. + + Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Image inputs](https://platform.openai.com/docs/guides/images) + - [File inputs](https://platform.openai.com/docs/guides/pdf-files) + - [Conversation state](https://platform.openai.com/docs/guides/conversation-state) + - [Function calling](https://platform.openai.com/docs/guides/function-calling) + + instructions: A system (or developer) message inserted into the model's context. + + When using along with `previous_response_id`, the instructions from a previous + response will not be carried over to the next response. This makes it simple to + swap out system (or developer) messages in new responses. + + max_output_tokens: An upper bound for the number of tokens that can be generated for a response, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tool_calls: The maximum number of total calls to built-in tools that can be processed in a + response. This maximum number applies across all built-in tool calls, not per + individual tool. Any further attempts to call a tool by the model will be + ignored. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + moderation: Configuration for running moderation on the input and output of this response. + + parallel_tool_calls: Whether to allow the model to run tool calls in parallel. + + previous_response_id: The unique ID of the previous response to the model. Use this to create + multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + + prompt: Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning: **gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + store: Whether to store the generated model response for later retrieval via API. + + stream_options: Options for streaming responses. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + text: Configuration options for a text response from the model. Can be plain text or + structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + + tool_choice: How the model should select which tool (or tools) to use when generating a + response. See the `tools` parameter to see how to specify which tools the model + can call. + + tools: An array of tools the model may call while generating a response. You can + specify which tool to use by setting the `tool_choice` parameter. + + We support the following categories of tools: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **MCP Tools**: Integrations with third-party systems via custom MCP servers or + predefined connectors such as Google Drive and SharePoint. Learn more about + [MCP Tools](https://platform.openai.com/docs/guides/tools-connectors-mcp). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code with strongly typed arguments and outputs. + Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + You can also use custom tools to call your own code. + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + truncation: The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. + - `disabled` (default): If the input size will exceed the context window size + for a model, the request will fail with a 400 error. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def create( + self, + *, + stream: bool, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + tools: Iterable[ToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response | AsyncStream[ResponseStreamEvent]: + """Creates a model response. + + Provide + [text](https://platform.openai.com/docs/guides/text) or + [image](https://platform.openai.com/docs/guides/images) inputs to generate + [text](https://platform.openai.com/docs/guides/text) or + [JSON](https://platform.openai.com/docs/guides/structured-outputs) outputs. Have + the model call your own + [custom code](https://platform.openai.com/docs/guides/function-calling) or use + built-in [tools](https://platform.openai.com/docs/guides/tools) like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search) to use + your own data as input for the model's response. + + Args: + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + background: Whether to run the model response in the background. + [Learn more](https://platform.openai.com/docs/guides/background). + + context_management: Context management configuration for this request. + + conversation: The conversation that this response belongs to. Items from this conversation are + prepended to `input_items` for this response request. Input items and output + items from this response are automatically added to this conversation after this + response completes. + + include: Specify additional output data to include in the model response. Currently + supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + + input: Text, image, or file inputs to the model, used to generate a response. + + Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Image inputs](https://platform.openai.com/docs/guides/images) + - [File inputs](https://platform.openai.com/docs/guides/pdf-files) + - [Conversation state](https://platform.openai.com/docs/guides/conversation-state) + - [Function calling](https://platform.openai.com/docs/guides/function-calling) + + instructions: A system (or developer) message inserted into the model's context. + + When using along with `previous_response_id`, the instructions from a previous + response will not be carried over to the next response. This makes it simple to + swap out system (or developer) messages in new responses. + + max_output_tokens: An upper bound for the number of tokens that can be generated for a response, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + + max_tool_calls: The maximum number of total calls to built-in tools that can be processed in a + response. This maximum number applies across all built-in tool calls, not per + individual tool. Any further attempts to call a tool by the model will be + ignored. + + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + + model: Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + moderation: Configuration for running moderation on the input and output of this response. + + parallel_tool_calls: Whether to allow the model to run tool calls in parallel. + + previous_response_id: The unique ID of the previous response to the model. Use this to create + multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + + prompt: Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + + reasoning: **gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + service_tier: Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + + store: Whether to store the generated model response for later retrieval via API. + + stream_options: Options for streaming responses. Only set this when you set `stream: true`. + + temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will + make the output more random, while lower values like 0.2 will make it more + focused and deterministic. We generally recommend altering this or `top_p` but + not both. + + text: Configuration options for a text response from the model. Can be plain text or + structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + + tool_choice: How the model should select which tool (or tools) to use when generating a + response. See the `tools` parameter to see how to specify which tools the model + can call. + + tools: An array of tools the model may call while generating a response. You can + specify which tool to use by setting the `tool_choice` parameter. + + We support the following categories of tools: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **MCP Tools**: Integrations with third-party systems via custom MCP servers or + predefined connectors such as Google Drive and SharePoint. Learn more about + [MCP Tools](https://platform.openai.com/docs/guides/tools-connectors-mcp). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code with strongly typed arguments and outputs. + Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + You can also use custom tools to call your own code. + + top_logprobs: An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + + top_p: An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + + truncation: The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. + - `disabled` (default): If the input size will exceed the context window size + for a model, the request will fail with a 400 error. + + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + async def create( + self, + *, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + tools: Iterable[ToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response | AsyncStream[ResponseStreamEvent]: + return await self._post( + "/responses", + body=await async_maybe_transform( + { + "background": background, + "context_management": context_management, + "conversation": conversation, + "include": include, + "input": input, + "instructions": instructions, + "max_output_tokens": max_output_tokens, + "max_tool_calls": max_tool_calls, + "metadata": metadata, + "model": model, + "moderation": moderation, + "parallel_tool_calls": parallel_tool_calls, + "previous_response_id": previous_response_id, + "prompt": prompt, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning": reasoning, + "safety_identifier": safety_identifier, + "service_tier": service_tier, + "store": store, + "stream": stream, + "stream_options": stream_options, + "temperature": temperature, + "text": text, + "tool_choice": tool_choice, + "tools": tools, + "top_logprobs": top_logprobs, + "top_p": top_p, + "truncation": truncation, + "user": user, + }, + response_create_params.ResponseCreateParamsStreaming + if stream + else response_create_params.ResponseCreateParamsNonStreaming, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Response, + stream=stream or False, + stream_cls=AsyncStream[ResponseStreamEvent], + ) + + @overload + def stream( + self, + *, + response_id: str, + text_format: type[TextFormatT] | Omit = omit, + starting_after: int | Omit = omit, + tools: Iterable[ParseableToolParam] | Omit = omit, + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncResponseStreamManager[TextFormatT]: ... + + @overload + def stream( + self, + *, + input: Union[str, ResponseInputParam], + model: ResponsesModel, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + text_format: type[TextFormatT] | Omit = omit, + tools: Iterable[ParseableToolParam] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncResponseStreamManager[TextFormatT]: ... + + def stream( + self, + *, + response_id: str | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + model: ResponsesModel | Omit = omit, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + text_format: type[TextFormatT] | Omit = omit, + tools: Iterable[ParseableToolParam] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncResponseStreamManager[TextFormatT]: + new_response_args = { + "input": input, + "model": model, + "context_management": context_management, + "conversation": conversation, + "include": include, + "instructions": instructions, + "max_output_tokens": max_output_tokens, + "max_tool_calls": max_tool_calls, + "metadata": metadata, + "moderation": moderation, + "parallel_tool_calls": parallel_tool_calls, + "previous_response_id": previous_response_id, + "prompt": prompt, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning": reasoning, + "safety_identifier": safety_identifier, + "service_tier": service_tier, + "store": store, + "stream_options": stream_options, + "temperature": temperature, + "text": text, + "tool_choice": tool_choice, + "top_logprobs": top_logprobs, + "top_p": top_p, + "truncation": truncation, + "user": user, + "background": background, + } + new_response_args_names = [k for k, v in new_response_args.items() if is_given(v)] + + if (is_given(response_id) or is_given(starting_after)) and len(new_response_args_names) > 0: + raise ValueError( + "Cannot provide both response_id/starting_after can't be provided together with " + + ", ".join(new_response_args_names) + ) + + tools = _make_tools(tools) + if len(new_response_args_names) > 0: + if isinstance(input, NotGiven): + raise ValueError("input must be provided when creating a new response") + + if not is_given(model): + raise ValueError("model must be provided when creating a new response") + + if is_given(text_format): + if not text: + text = {} + + if "format" in text: + raise TypeError("Cannot mix and match text.format with text_format") + text = copy(text) + text["format"] = _type_to_text_format_param(text_format) + + api_request = self.create( + input=input, + model=model, + stream=True, + tools=tools, + context_management=context_management, + conversation=conversation, + include=include, + instructions=instructions, + max_output_tokens=max_output_tokens, + max_tool_calls=max_tool_calls, + metadata=metadata, + moderation=moderation, + parallel_tool_calls=parallel_tool_calls, + previous_response_id=previous_response_id, + prompt=prompt, + prompt_cache_key=prompt_cache_key, + prompt_cache_retention=prompt_cache_retention, + store=store, + stream_options=stream_options, + temperature=temperature, + text=text, + tool_choice=tool_choice, + reasoning=reasoning, + safety_identifier=safety_identifier, + service_tier=service_tier, + top_logprobs=top_logprobs, + top_p=top_p, + truncation=truncation, + user=user, + background=background, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + return AsyncResponseStreamManager( + api_request, + text_format=text_format, + input_tools=tools, + starting_after=None, + ) + else: + if isinstance(response_id, Omit): + raise ValueError("response_id must be provided when streaming an existing response") + + api_request = self.retrieve( + response_id, + stream=True, + include=include or [], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return AsyncResponseStreamManager( + api_request, + text_format=text_format, + input_tools=tools, + starting_after=starting_after if is_given(starting_after) else None, + ) + + async def parse( + self, + *, + text_format: type[TextFormatT] | Omit = omit, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[response_create_params.ContextManagement]] | Omit = omit, + conversation: Optional[response_create_params.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[response_create_params.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + stream_options: Optional[response_create_params.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: response_create_params.ToolChoice | Omit = omit, + tools: Iterable[ParseableToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ParsedResponse[TextFormatT]: + if is_given(text_format): + if not text: + text = {} + + if "format" in text: + raise TypeError("Cannot mix and match text.format with text_format") + text = copy(text) + text["format"] = _type_to_text_format_param(text_format) + + tools = _make_tools(tools) + + def parser(raw_response: Response) -> ParsedResponse[TextFormatT]: + return parse_response( + input_tools=tools, + text_format=text_format, + response=raw_response, + ) + + return await self._post( + "/responses", + body=maybe_transform( + { + "background": background, + "context_management": context_management, + "conversation": conversation, + "include": include, + "input": input, + "instructions": instructions, + "max_output_tokens": max_output_tokens, + "max_tool_calls": max_tool_calls, + "metadata": metadata, + "model": model, + "moderation": moderation, + "parallel_tool_calls": parallel_tool_calls, + "previous_response_id": previous_response_id, + "prompt": prompt, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning": reasoning, + "safety_identifier": safety_identifier, + "service_tier": service_tier, + "store": store, + "stream": stream, + "stream_options": stream_options, + "temperature": temperature, + "text": text, + "tool_choice": tool_choice, + "tools": tools, + "top_logprobs": top_logprobs, + "top_p": top_p, + "truncation": truncation, + "user": user, + "verbosity": verbosity, + }, + response_create_params.ResponseCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + post_parser=parser, + security={"bearer_auth": True}, + ), + # we turn the `Response` instance into a `ParsedResponse` + # in the `parser` function above + cast_to=cast(Type[ParsedResponse[TextFormatT]], Response), + ) + + @overload + async def retrieve( + self, + response_id: str, + *, + include: List[ResponseIncludable] | Omit = omit, + include_obfuscation: bool | Omit = omit, + starting_after: int | Omit = omit, + stream: Literal[False] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response: ... + + @overload + async def retrieve( + self, + response_id: str, + *, + stream: Literal[True], + include: List[ResponseIncludable] | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncStream[ResponseStreamEvent]: ... + + @overload + async def retrieve( + self, + response_id: str, + *, + stream: bool, + include: List[ResponseIncludable] | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Response | AsyncStream[ResponseStreamEvent]: ... + + @overload + async def retrieve( + self, + response_id: str, + *, + stream: bool = False, + include: List[ResponseIncludable] | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Response | AsyncStream[ResponseStreamEvent]: + """ + Retrieves a model response with the given ID. + + Args: + include: Additional fields to include in the response. See the `include` parameter for + Response creation above for more information. + + include_obfuscation: When true, stream obfuscation will be enabled. Stream obfuscation adds random + characters to an `obfuscation` field on streaming delta events to normalize + payload sizes as a mitigation to certain side-channel attacks. These obfuscation + fields are included by default, but add a small amount of overhead to the data + stream. You can set `include_obfuscation` to false to optimize for bandwidth if + you trust the network links between your application and the OpenAI API. + + starting_after: The sequence number of the event after which to start streaming. + + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def retrieve( + self, + response_id: str, + *, + stream: Literal[True], + include: List[ResponseIncludable] | Omit = omit, + include_obfuscation: bool | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncStream[ResponseStreamEvent]: + """ + Retrieves a model response with the given ID. + + Args: + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + include: Additional fields to include in the response. See the `include` parameter for + Response creation above for more information. + + include_obfuscation: When true, stream obfuscation will be enabled. Stream obfuscation adds random + characters to an `obfuscation` field on streaming delta events to normalize + payload sizes as a mitigation to certain side-channel attacks. These obfuscation + fields are included by default, but add a small amount of overhead to the data + stream. You can set `include_obfuscation` to false to optimize for bandwidth if + you trust the network links between your application and the OpenAI API. + + starting_after: The sequence number of the event after which to start streaming. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def retrieve( + self, + response_id: str, + *, + stream: bool, + include: List[ResponseIncludable] | Omit = omit, + include_obfuscation: bool | Omit = omit, + starting_after: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response | AsyncStream[ResponseStreamEvent]: + """ + Retrieves a model response with the given ID. + + Args: + stream: If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + + include: Additional fields to include in the response. See the `include` parameter for + Response creation above for more information. + + include_obfuscation: When true, stream obfuscation will be enabled. Stream obfuscation adds random + characters to an `obfuscation` field on streaming delta events to normalize + payload sizes as a mitigation to certain side-channel attacks. These obfuscation + fields are included by default, but add a small amount of overhead to the data + stream. You can set `include_obfuscation` to false to optimize for bandwidth if + you trust the network links between your application and the OpenAI API. + + starting_after: The sequence number of the event after which to start streaming. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + async def retrieve( + self, + response_id: str, + *, + include: List[ResponseIncludable] | Omit = omit, + include_obfuscation: bool | Omit = omit, + starting_after: int | Omit = omit, + stream: Literal[False] | Literal[True] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response | AsyncStream[ResponseStreamEvent]: + if not response_id: + raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") + return await self._get( + path_template("/responses/{response_id}", response_id=response_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "include": include, + "include_obfuscation": include_obfuscation, + "starting_after": starting_after, + "stream": stream, + }, + response_retrieve_params.ResponseRetrieveParams, + ), + security={"bearer_auth": True}, + ), + cast_to=Response, + stream=stream or False, + stream_cls=AsyncStream[ResponseStreamEvent], + ) + + async def delete( + self, + response_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Deletes a model response with the given ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not response_id: + raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + path_template("/responses/{response_id}", response_id=response_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=NoneType, + ) + + async def cancel( + self, + response_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Response: + """Cancels a model response with the given ID. + + Only responses created with the + `background` parameter set to `true` can be cancelled. + [Learn more](https://platform.openai.com/docs/guides/background). + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not response_id: + raise ValueError(f"Expected a non-empty value for `response_id` but received {response_id!r}") + return await self._post( + path_template("/responses/{response_id}/cancel", response_id=response_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Response, + ) + + async def compact( + self, + *, + model: Union[ + Literal[ + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gpt-5.4-mini-2026-03-17", + "gpt-5.4-nano-2026-03-17", + "gpt-5.3-chat-latest", + "gpt-5.2", + "gpt-5.2-2025-12-11", + "gpt-5.2-chat-latest", + "gpt-5.2-pro", + "gpt-5.2-pro-2025-12-11", + "gpt-5.1", + "gpt-5.1-2025-11-13", + "gpt-5.1-codex", + "gpt-5.1-mini", + "gpt-5.1-chat-latest", + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-5-chat-latest", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o4-mini", + "o4-mini-2025-04-16", + "o3", + "o3-2025-04-16", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "o1-preview", + "o1-preview-2024-09-12", + "o1-mini", + "o1-mini-2024-09-12", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-audio-preview", + "gpt-4o-audio-preview-2024-10-01", + "gpt-4o-audio-preview-2024-12-17", + "gpt-4o-audio-preview-2025-06-03", + "gpt-4o-mini-audio-preview", + "gpt-4o-mini-audio-preview-2024-12-17", + "gpt-4o-search-preview", + "gpt-4o-mini-search-preview", + "gpt-4o-search-preview-2025-03-11", + "gpt-4o-mini-search-preview-2025-03-11", + "chatgpt-4o-latest", + "codex-mini-latest", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0301", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613", + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max", + ], + str, + None, + ], + input: Union[str, Iterable[ResponseInputItemParam], None] | Omit = omit, + instructions: Optional[str] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt_cache_key: Optional[str] | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "priority"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CompactedResponse: + """Compact a conversation. + + Returns a compacted response object. + + Learn when and how to compact long-running conversations in the + [conversation state guide](https://platform.openai.com/docs/guides/conversation-state#managing-the-context-window). + For ZDR-compatible compaction details, see + [Compaction (advanced)](https://platform.openai.com/docs/guides/conversation-state#compaction-advanced). + + Args: + model: Model ID used to generate the response, like `gpt-5` or `o3`. OpenAI offers a + wide range of models with different capabilities, performance characteristics, + and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + + input: Text, image, or file inputs to the model, used to generate a response + + instructions: A system (or developer) message inserted into the model's context. When used + along with `previous_response_id`, the instructions from a previous response + will not be carried over to the next response. This makes it simple to swap out + system (or developer) messages in new responses. + + previous_response_id: The unique ID of the previous response to the model. Use this to create + multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + + prompt_cache_key: A key to use when reading from or writing to the prompt cache. + + prompt_cache_retention: How long to retain a prompt cache entry created by this request. + + service_tier: The service tier to use for this request. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/responses/compact", + body=await async_maybe_transform( + { + "model": model, + "input": input, + "instructions": instructions, + "previous_response_id": previous_response_id, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "service_tier": service_tier, + }, + response_compact_params.ResponseCompactParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=CompactedResponse, + ) + + def connect( + self, + extra_query: Query = {}, + extra_headers: Headers = {}, + websocket_connection_options: WebSocketConnectionOptions = {}, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + max_queue_size: int = 1_048_576, + ) -> AsyncResponsesConnectionManager: + """Connect to a persistent Responses API WebSocket. + + Send `response.create` events and receive response stream events over the socket. + """ + return AsyncResponsesConnectionManager( + client=self._client, + extra_query=extra_query, + extra_headers=extra_headers, + websocket_connection_options=websocket_connection_options, + on_reconnecting=on_reconnecting, + max_retries=max_retries, + initial_delay=initial_delay, + max_delay=max_delay, + max_queue_size=max_queue_size, + ) + + +class ResponsesWithRawResponse: + def __init__(self, responses: Responses) -> None: + self._responses = responses + + self.create = _legacy_response.to_raw_response_wrapper( + responses.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + responses.retrieve, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + responses.delete, + ) + self.cancel = _legacy_response.to_raw_response_wrapper( + responses.cancel, + ) + self.compact = _legacy_response.to_raw_response_wrapper( + responses.compact, + ) + self.parse = _legacy_response.to_raw_response_wrapper( + responses.parse, + ) + + @cached_property + def input_items(self) -> InputItemsWithRawResponse: + return InputItemsWithRawResponse(self._responses.input_items) + + @cached_property + def input_tokens(self) -> InputTokensWithRawResponse: + return InputTokensWithRawResponse(self._responses.input_tokens) + + +class AsyncResponsesWithRawResponse: + def __init__(self, responses: AsyncResponses) -> None: + self._responses = responses + + self.create = _legacy_response.async_to_raw_response_wrapper( + responses.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + responses.retrieve, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + responses.delete, + ) + self.cancel = _legacy_response.async_to_raw_response_wrapper( + responses.cancel, + ) + self.compact = _legacy_response.async_to_raw_response_wrapper( + responses.compact, + ) + self.parse = _legacy_response.async_to_raw_response_wrapper( + responses.parse, + ) + + @cached_property + def input_items(self) -> AsyncInputItemsWithRawResponse: + return AsyncInputItemsWithRawResponse(self._responses.input_items) + + @cached_property + def input_tokens(self) -> AsyncInputTokensWithRawResponse: + return AsyncInputTokensWithRawResponse(self._responses.input_tokens) + + +class ResponsesWithStreamingResponse: + def __init__(self, responses: Responses) -> None: + self._responses = responses + + self.create = to_streamed_response_wrapper( + responses.create, + ) + self.retrieve = to_streamed_response_wrapper( + responses.retrieve, + ) + self.delete = to_streamed_response_wrapper( + responses.delete, + ) + self.cancel = to_streamed_response_wrapper( + responses.cancel, + ) + self.compact = to_streamed_response_wrapper( + responses.compact, + ) + + @cached_property + def input_items(self) -> InputItemsWithStreamingResponse: + return InputItemsWithStreamingResponse(self._responses.input_items) + + @cached_property + def input_tokens(self) -> InputTokensWithStreamingResponse: + return InputTokensWithStreamingResponse(self._responses.input_tokens) + + +class AsyncResponsesWithStreamingResponse: + def __init__(self, responses: AsyncResponses) -> None: + self._responses = responses + + self.create = async_to_streamed_response_wrapper( + responses.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + responses.retrieve, + ) + self.delete = async_to_streamed_response_wrapper( + responses.delete, + ) + self.cancel = async_to_streamed_response_wrapper( + responses.cancel, + ) + self.compact = async_to_streamed_response_wrapper( + responses.compact, + ) + + @cached_property + def input_items(self) -> AsyncInputItemsWithStreamingResponse: + return AsyncInputItemsWithStreamingResponse(self._responses.input_items) + + @cached_property + def input_tokens(self) -> AsyncInputTokensWithStreamingResponse: + return AsyncInputTokensWithStreamingResponse(self._responses.input_tokens) + + +def _make_tools(tools: Iterable[ParseableToolParam] | Omit) -> List[ToolParam] | Omit: + if not is_given(tools): + return omit + + converted_tools: List[ToolParam] = [] + for tool in tools: + if tool["type"] != "function": + converted_tools.append(tool) + continue + + if "function" not in tool: + # standard Responses API case + converted_tools.append(tool) + continue + + function = cast(Any, tool)["function"] # pyright: ignore[reportUnnecessaryCast] + if not isinstance(function, PydanticFunctionTool): + raise Exception( + "Expected Chat Completions function tool shape to be created using `openai.pydantic_function_tool()`" + ) + + assert "parameters" in function + new_tool = ResponsesPydanticFunctionTool( + { + "type": "function", + "name": function["name"], + "description": function.get("description"), + "parameters": function["parameters"], + "strict": function.get("strict") or False, + }, + function.model, + ) + + converted_tools.append(new_tool.cast()) + + return converted_tools + + +class AsyncResponsesConnection: + """Represents a live WebSocket connection to the Responses API""" + + response: AsyncResponsesResponseResource + + _connection: AsyncWebSocketConnection + + def __init__( + self, + connection: AsyncWebSocketConnection, + *, + make_ws: Callable[[Query, Headers], Awaitable[AsyncWebSocketConnection]] | None = None, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + extra_query: Query = {}, + extra_headers: Headers = {}, + send_queue: SendQueue | None = None, + ) -> None: + self._connection = connection + self._make_ws = make_ws + self._on_reconnecting = on_reconnecting + self._max_retries = max_retries + self._initial_delay = initial_delay + self._max_delay = max_delay + self._extra_query = extra_query + self._extra_headers = extra_headers + self._intentionally_closed = False + self._is_reconnecting = False + self._send_queue = send_queue or SendQueue() + self._event_handler_registry = EventHandlerRegistry(use_lock=False) + + self.response = AsyncResponsesResponseResource(self) + + async def __aiter__(self) -> AsyncIterator[ResponsesServerEvent]: + """ + An infinite-iterator that will continue to yield events until + the connection is closed. + """ + from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError + + while True: + try: + yield await self.recv() + except ConnectionClosedOK: + return + except ConnectionClosedError as exc: + if not await self._reconnect(exc): + unsent = self._send_queue.drain() + if unsent: + raise WebSocketConnectionClosedError( + "WebSocket connection closed with unsent messages", + unsent_messages=unsent, + ) from exc + raise + + async def recv(self) -> ResponsesServerEvent: + """ + Receive the next message from the connection and parses it into a `ResponsesServerEvent` object. + + Canceling this method is safe. There's no risk of losing data. + """ + return self.parse_event(await self.recv_bytes()) + + async def recv_bytes(self) -> bytes: + """Receive the next message from the connection as raw bytes. + + Canceling this method is safe. There's no risk of losing data. + + If you want to parse the message into a `ResponsesServerEvent` object like `.recv()` does, + then you can call `.parse_event(data)`. + """ + message = await self._connection.recv(decode=False) + log.debug(f"Received WebSocket message: %s", message) + return message + + async def send(self, event: ResponsesClientEvent | ResponsesClientEventParam) -> None: + data = ( + event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + if isinstance(event, BaseModel) + else json.dumps(await async_maybe_transform(event, ResponsesClientEventParam)) + ) + if self._is_reconnecting: + self._send_queue.enqueue(data) + return + try: + await self._connection.send(data) + except Exception: + self._send_queue.enqueue(data) + raise + + async def send_raw(self, data: bytes | str) -> None: + if self._is_reconnecting: + raw = data if isinstance(data, str) else data.decode("utf-8") + self._send_queue.enqueue(raw) + return + await self._connection.send(data) + + async def close(self, *, code: int = 1000, reason: str = "") -> None: + self._intentionally_closed = True + await self._connection.close(code=code, reason=reason) + + def parse_event(self, data: str | bytes) -> ResponsesServerEvent: + """ + Converts a raw `str` or `bytes` message into a `ResponsesServerEvent` object. + + This is helpful if you're using `.recv_bytes()`. + """ + return cast( + ResponsesServerEvent, + construct_type_unchecked(value=json.loads(data), type_=cast(Any, ResponsesServerEvent)), + ) + + async def _reconnect(self, exc: Exception) -> bool: + """Attempt to reconnect after a connection failure. + + Returns ``True`` if a new connection was established, ``False`` if the + caller should re-raise the original exception. + """ + import asyncio + + if self._on_reconnecting is None or self._make_ws is None: + return False + + from websockets.exceptions import ConnectionClosedError + + close_code = 1006 + if isinstance(exc, ConnectionClosedError) and exc.rcvd is not None: + close_code = exc.rcvd.code + + if not is_recoverable_close(close_code): + return False + + self._is_reconnecting = True + + for attempt in range(1, self._max_retries + 1): + base_delay = min(self._initial_delay * (2 ** (attempt - 1)), self._max_delay) + jitter = 0.75 + random.random() * 0.25 + delay = base_delay * jitter + + event = ReconnectingEvent( + attempt=attempt, + max_attempts=self._max_retries, + delay=delay, + close_code=close_code, + extra_query=self._extra_query, + extra_headers=self._extra_headers, + ) + + try: + result = self._on_reconnecting(event) + except Exception: + self._is_reconnecting = False + return False + + if result is not None and result.get("abort"): + self._is_reconnecting = False + return False + + if result is not None: + if "extra_query" in result: + self._extra_query = result["extra_query"] + if "extra_headers" in result: + self._extra_headers = result["extra_headers"] + + log.info( + "Reconnecting to WebSocket API (attempt %d/%d) after %.1fs delay", + attempt, + self._max_retries, + delay, + ) + await asyncio.sleep(delay) + + if self._intentionally_closed: + self._is_reconnecting = False + return False + + try: + self._connection = await self._make_ws(self._extra_query, self._extra_headers) + log.info("Reconnected to WebSocket API") + self._is_reconnecting = False + await self._flush_send_queue() + return True + except Exception: + pass + + self._is_reconnecting = False + return False + + async def _flush_send_queue(self) -> None: + """Send all queued messages over the current connection.""" + + async def _send(data: str) -> None: + await self._connection.send(data) + + try: + await self._send_queue.flush_async(_send) + except Exception: + log.warning("Failed to flush send queue after reconnect", exc_info=True) + + def on( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[AsyncResponsesConnection, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Adds the handler to the end of the handlers list for the given event type. + + No checks are made to see if the handler has already been added. Multiple calls + passing the same combination of event type and handler will result in the handler + being added, and called, multiple times. + + Can be used as a method (returns ``self`` for chaining):: + + connection.on("response.audio.delta", my_handler) + + Or as a decorator:: + + @connection.on("response.audio.delta") + async def my_handler(event): ... + """ + if handler is not None: + self._event_handler_registry.add(event_type, handler) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self._event_handler_registry.add(event_type, fn) + return fn + + return decorator + + def off(self, event_type: str, handler: Callable[..., Any]) -> AsyncResponsesConnection: + """Remove a previously registered event handler.""" + self._event_handler_registry.remove(event_type, handler) + return self + + def once( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[AsyncResponsesConnection, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register a one-time event handler. + + Automatically removed after first invocation. + """ + if handler is not None: + self._event_handler_registry.add(event_type, handler, once=True) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self._event_handler_registry.add(event_type, fn, once=True) + return fn + + return decorator + + async def dispatch_events(self) -> None: + """Run the event loop, dispatching received events to registered handlers. + + Blocks until the connection is closed. This is the push-based + alternative to iterating with ``async for event in connection``. + + If an ``"error"`` event arrives and no handler is registered for + ``"error"`` or ``"event"``, an ``OpenAIError`` is raised. + """ + import asyncio + + async for event in self: + event_type = event.type + specific = self._event_handler_registry.get_handlers(event_type) + generic = self._event_handler_registry.get_handlers("event") + + if event_type == "error" and not specific and not generic: + if isinstance(event, ResponseErrorEvent): + raise OpenAIError(f"WebSocket error: {event}") + + for handler in specific: + result = handler(event) + if asyncio.iscoroutine(result): + await result + + for handler in generic: + result = handler(event) + if asyncio.iscoroutine(result): + await result + + +class AsyncResponsesConnectionManager: + """ + Context manager over a `AsyncResponsesConnection` that is returned by `responses.connect()` + + This context manager ensures that the connection will be closed when it exits. + + --- + + Note that if your application doesn't work well with the context manager approach then you + can call the `.enter()` method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = await client.responses.connect(...).enter() + # ... + await connection.close() + ``` + """ + + def __init__( + self, + *, + client: AsyncOpenAI, + extra_query: Query, + extra_headers: Headers, + websocket_connection_options: WebSocketConnectionOptions, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + max_queue_size: int = 1_048_576, + ) -> None: + self.__client = client + self.__connection: AsyncResponsesConnection | None = None + self.__extra_query = extra_query + self.__extra_headers = extra_headers + self.__websocket_connection_options = websocket_connection_options + self.__on_reconnecting = on_reconnecting + self.__max_retries = max_retries + self.__initial_delay = initial_delay + self.__max_delay = max_delay + self.__send_queue = SendQueue(max_bytes=max_queue_size) + self.__event_handler_registry = EventHandlerRegistry(use_lock=False) + + def send(self, event: ResponsesClientEvent | ResponsesClientEventParam) -> None: + """Queue a message to be sent when the connection is established. + + This can be called before entering the context manager. Queued messages + are automatically sent once the WebSocket connection opens. + """ + data = ( + event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + if isinstance(event, BaseModel) + else json.dumps(event) + ) + self.__send_queue.enqueue(data) + + def on( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[AsyncResponsesConnectionManager, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register an event handler before the connection is established. + + Handlers are transferred to the connection on enter. Supports the + same method and decorator forms as ``AsyncResponsesConnection.on``. + """ + if handler is not None: + self.__event_handler_registry.add(event_type, handler) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self.__event_handler_registry.add(event_type, fn) + return fn + + return decorator + + def off(self, event_type: str, handler: Callable[..., Any]) -> AsyncResponsesConnectionManager: + """Remove a previously registered event handler.""" + self.__event_handler_registry.remove(event_type, handler) + return self + + def once( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[AsyncResponsesConnectionManager, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register a one-time event handler before the connection is established.""" + if handler is not None: + self.__event_handler_registry.add(event_type, handler, once=True) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self.__event_handler_registry.add(event_type, fn, once=True) + return fn + + return decorator + + async def __aenter__(self) -> AsyncResponsesConnection: + """ + If your application doesn't work well with the context manager approach then you + can call this method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = await client.responses.connect(...).enter() + # ... + await connection.close() + ``` + """ + ws = await self._connect_ws(self.__extra_query, self.__extra_headers) + + self.__connection = AsyncResponsesConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) + + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + await self.__connection._flush_send_queue() + + return self.__connection + + enter = __aenter__ + + async def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> AsyncWebSocketConnection: + try: + from websockets.asyncio.client import connect + except ImportError as exc: + raise OpenAIError("You need to install `openai[realtime]` to use this method") from exc + + url = self._prepare_url().copy_with( + params={ + **self.__client.base_url.params, + **extra_query, + }, + ) + log.debug("Connecting to %s", url) + if self.__websocket_connection_options: + log.debug("Connection options: %s", self.__websocket_connection_options) + + return await connect( + str(url), + user_agent_header=self.__client.user_agent, + additional_headers=_merge_mappings( + { + **self.__client.auth_headers, + }, + extra_headers, + ), + **self.__websocket_connection_options, + ) + + def _prepare_url(self) -> httpx.URL: + if self.__client.websocket_base_url is not None: + base_url = httpx.URL(self.__client.websocket_base_url) + else: + scheme = self.__client._base_url.scheme + ws_scheme = "ws" if scheme == "http" else "wss" + base_url = self.__client._base_url.copy_with(scheme=ws_scheme) + + merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/responses" + return base_url.copy_with(raw_path=merge_raw_path) + + async def __aexit__( + self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None + ) -> None: + if self.__connection is not None: + await self.__connection.close() + + +class ResponsesConnection: + """Represents a live WebSocket connection to the Responses API""" + + response: ResponsesResponseResource + + _connection: WebSocketConnection + + def __init__( + self, + connection: WebSocketConnection, + *, + make_ws: Callable[[Query, Headers], WebSocketConnection] | None = None, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + extra_query: Query = {}, + extra_headers: Headers = {}, + send_queue: SendQueue | None = None, + ) -> None: + self._connection = connection + self._make_ws = make_ws + self._on_reconnecting = on_reconnecting + self._max_retries = max_retries + self._initial_delay = initial_delay + self._max_delay = max_delay + self._extra_query = extra_query + self._extra_headers = extra_headers + self._intentionally_closed = False + self._is_reconnecting = False + self._send_queue = send_queue or SendQueue() + self._event_handler_registry = EventHandlerRegistry(use_lock=True) + + self.response = ResponsesResponseResource(self) + + def __iter__(self) -> Iterator[ResponsesServerEvent]: + """ + An infinite-iterator that will continue to yield events until + the connection is closed. + """ + from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError + + while True: + try: + yield self.recv() + except ConnectionClosedOK: + return + except ConnectionClosedError as exc: + if not self._reconnect(exc): + unsent = self._send_queue.drain() + if unsent: + raise WebSocketConnectionClosedError( + "WebSocket connection closed with unsent messages", + unsent_messages=unsent, + ) from exc + raise + + def recv(self) -> ResponsesServerEvent: + """ + Receive the next message from the connection and parses it into a `ResponsesServerEvent` object. + + Canceling this method is safe. There's no risk of losing data. + """ + return self.parse_event(self.recv_bytes()) + + def recv_bytes(self) -> bytes: + """Receive the next message from the connection as raw bytes. + + Canceling this method is safe. There's no risk of losing data. + + If you want to parse the message into a `ResponsesServerEvent` object like `.recv()` does, + then you can call `.parse_event(data)`. + """ + message = self._connection.recv(decode=False) + log.debug(f"Received WebSocket message: %s", message) + return message + + def send(self, event: ResponsesClientEvent | ResponsesClientEventParam) -> None: + data = ( + event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + if isinstance(event, BaseModel) + else json.dumps(maybe_transform(event, ResponsesClientEventParam)) + ) + if self._is_reconnecting: + self._send_queue.enqueue(data) + return + try: + self._connection.send(data) + except Exception: + self._send_queue.enqueue(data) + raise + + def send_raw(self, data: bytes | str) -> None: + if self._is_reconnecting: + raw = data if isinstance(data, str) else data.decode("utf-8") + self._send_queue.enqueue(raw) + return + self._connection.send(data) + + def close(self, *, code: int = 1000, reason: str = "") -> None: + self._intentionally_closed = True + self._connection.close(code=code, reason=reason) + + def parse_event(self, data: str | bytes) -> ResponsesServerEvent: + """ + Converts a raw `str` or `bytes` message into a `ResponsesServerEvent` object. + + This is helpful if you're using `.recv_bytes()`. + """ + return cast( + ResponsesServerEvent, + construct_type_unchecked(value=json.loads(data), type_=cast(Any, ResponsesServerEvent)), + ) + + def _reconnect(self, exc: Exception) -> bool: + """Attempt to reconnect after a connection failure. + + Returns ``True`` if a new connection was established, ``False`` if the + caller should re-raise the original exception. + """ + if self._on_reconnecting is None or self._make_ws is None: + return False + + from websockets.exceptions import ConnectionClosedError + + close_code = 1006 + if isinstance(exc, ConnectionClosedError) and exc.rcvd is not None: + close_code = exc.rcvd.code + + if not is_recoverable_close(close_code): + return False + + self._is_reconnecting = True + + for attempt in range(1, self._max_retries + 1): + base_delay = min(self._initial_delay * (2 ** (attempt - 1)), self._max_delay) + jitter = 0.75 + random.random() * 0.25 + delay = base_delay * jitter + + event = ReconnectingEvent( + attempt=attempt, + max_attempts=self._max_retries, + delay=delay, + close_code=close_code, + extra_query=self._extra_query, + extra_headers=self._extra_headers, + ) + + try: + result = self._on_reconnecting(event) + except Exception: + self._is_reconnecting = False + return False + + if result is not None and result.get("abort"): + self._is_reconnecting = False + return False + + if result is not None: + if "extra_query" in result: + self._extra_query = result["extra_query"] + if "extra_headers" in result: + self._extra_headers = result["extra_headers"] + + log.info( + "Reconnecting to WebSocket API (attempt %d/%d) after %.1fs delay", + attempt, + self._max_retries, + delay, + ) + time.sleep(delay) + + if self._intentionally_closed: + self._is_reconnecting = False + return False + + try: + self._connection = self._make_ws(self._extra_query, self._extra_headers) + log.info("Reconnected to WebSocket API") + self._is_reconnecting = False + self._flush_send_queue() + return True + except Exception: + pass + + self._is_reconnecting = False + return False + + def _flush_send_queue(self) -> None: + """Send all queued messages over the current connection.""" + try: + self._send_queue.flush_sync(lambda data: self._connection.send(data)) + except Exception: + log.warning("Failed to flush send queue after reconnect", exc_info=True) + + def on( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[ResponsesConnection, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Adds the handler to the end of the handlers list for the given event type. + + No checks are made to see if the handler has already been added. Multiple calls + passing the same combination of event type and handler will result in the handler + being added, and called, multiple times. + + Can be used as a method (returns ``self`` for chaining):: + + connection.on("response.audio.delta", my_handler) + + Or as a decorator:: + + @connection.on("response.audio.delta") + def my_handler(event): ... + """ + if handler is not None: + self._event_handler_registry.add(event_type, handler) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self._event_handler_registry.add(event_type, fn) + return fn + + return decorator + + def off(self, event_type: str, handler: Callable[..., Any]) -> ResponsesConnection: + """Remove a previously registered event handler.""" + self._event_handler_registry.remove(event_type, handler) + return self + + def once( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[ResponsesConnection, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register a one-time event handler. + + Automatically removed after first invocation. + """ + if handler is not None: + self._event_handler_registry.add(event_type, handler, once=True) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self._event_handler_registry.add(event_type, fn, once=True) + return fn + + return decorator + + def dispatch_events(self) -> None: + """Run the event loop, dispatching received events to registered handlers. + + Blocks the current thread until the connection is closed. This is the push-based + alternative to iterating with ``for event in connection``. + + If an ``"error"`` event arrives and no handler is registered for + ``"error"`` or ``"event"``, an ``OpenAIError`` is raised. + """ + for event in self: + event_type = event.type + specific = self._event_handler_registry.get_handlers(event_type) + generic = self._event_handler_registry.get_handlers("event") + + if event_type == "error" and not specific and not generic: + if isinstance(event, ResponseErrorEvent): + raise OpenAIError(f"WebSocket error: {event}") + + for handler in specific: + handler(event) + + for handler in generic: + handler(event) + + +class ResponsesConnectionManager: + """ + Context manager over a `ResponsesConnection` that is returned by `responses.connect()` + + This context manager ensures that the connection will be closed when it exits. + + --- + + Note that if your application doesn't work well with the context manager approach then you + can call the `.enter()` method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = client.responses.connect(...).enter() + # ... + connection.close() + ``` + """ + + def __init__( + self, + *, + client: OpenAI, + extra_query: Query, + extra_headers: Headers, + websocket_connection_options: WebSocketConnectionOptions, + on_reconnecting: Callable[[ReconnectingEvent], ReconnectingOverrides | None] | None = None, + max_retries: int = 5, + initial_delay: float = 0.5, + max_delay: float = 8.0, + max_queue_size: int = 1_048_576, + ) -> None: + self.__client = client + self.__connection: ResponsesConnection | None = None + self.__extra_query = extra_query + self.__extra_headers = extra_headers + self.__websocket_connection_options = websocket_connection_options + self.__on_reconnecting = on_reconnecting + self.__max_retries = max_retries + self.__initial_delay = initial_delay + self.__max_delay = max_delay + self.__send_queue = SendQueue(max_bytes=max_queue_size) + self.__event_handler_registry = EventHandlerRegistry(use_lock=True) + + def send(self, event: ResponsesClientEvent | ResponsesClientEventParam) -> None: + """Queue a message to be sent when the connection is established. + + This can be called before entering the context manager. Queued messages + are automatically sent once the WebSocket connection opens. + """ + data = ( + event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + if isinstance(event, BaseModel) + else json.dumps(event) + ) + self.__send_queue.enqueue(data) + + def on( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[ResponsesConnectionManager, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register an event handler before the connection is established. + + Handlers are transferred to the connection on enter. Supports the + same method and decorator forms as ``ResponsesConnection.on``. + """ + if handler is not None: + self.__event_handler_registry.add(event_type, handler) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self.__event_handler_registry.add(event_type, fn) + return fn + + return decorator + + def off(self, event_type: str, handler: Callable[..., Any]) -> ResponsesConnectionManager: + """Remove a previously registered event handler.""" + self.__event_handler_registry.remove(event_type, handler) + return self + + def once( + self, event_type: str, handler: Callable[..., Any] | None = None + ) -> Union[ResponsesConnectionManager, Callable[[Callable[..., Any]], Callable[..., Any]]]: + """Register a one-time event handler before the connection is established.""" + if handler is not None: + self.__event_handler_registry.add(event_type, handler, once=True) + return self + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self.__event_handler_registry.add(event_type, fn, once=True) + return fn + + return decorator + + def __enter__(self) -> ResponsesConnection: + """ + If your application doesn't work well with the context manager approach then you + can call this method directly to initiate a connection. + + **Warning**: You must remember to close the connection with `.close()`. + + ```py + connection = client.responses.connect(...).enter() + # ... + connection.close() + ``` + """ + ws = self._connect_ws(self.__extra_query, self.__extra_headers) + + self.__connection = ResponsesConnection( + ws, + make_ws=self._connect_ws if self.__on_reconnecting is not None else None, + on_reconnecting=self.__on_reconnecting, + max_retries=self.__max_retries, + initial_delay=self.__initial_delay, + max_delay=self.__max_delay, + extra_query=self.__extra_query, + extra_headers=self.__extra_headers, + send_queue=self.__send_queue, + ) + + self.__event_handler_registry.merge_into(self.__connection._event_handler_registry) + self.__connection._flush_send_queue() + + return self.__connection + + enter = __enter__ + + def _connect_ws(self, extra_query: Query, extra_headers: Headers) -> WebSocketConnection: + try: + from websockets.sync.client import connect + except ImportError as exc: + raise OpenAIError("You need to install `openai[realtime]` to use this method") from exc + + url = self._prepare_url().copy_with( + params={ + **self.__client.base_url.params, + **extra_query, + }, + ) + log.debug("Connecting to %s", url) + if self.__websocket_connection_options: + log.debug("Connection options: %s", self.__websocket_connection_options) + + return connect( + str(url), + user_agent_header=self.__client.user_agent, + additional_headers=_merge_mappings( + { + **self.__client.auth_headers, + }, + extra_headers, + ), + **self.__websocket_connection_options, + ) + + def _prepare_url(self) -> httpx.URL: + if self.__client.websocket_base_url is not None: + base_url = httpx.URL(self.__client.websocket_base_url) + else: + scheme = self.__client._base_url.scheme + ws_scheme = "ws" if scheme == "http" else "wss" + base_url = self.__client._base_url.copy_with(scheme=ws_scheme) + + merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/responses" + return base_url.copy_with(raw_path=merge_raw_path) + + def __exit__( + self, exc_type: type[BaseException] | None, exc: BaseException | None, exc_tb: TracebackType | None + ) -> None: + if self.__connection is not None: + self.__connection.close() + + +class BaseResponsesConnectionResource: + def __init__(self, connection: ResponsesConnection) -> None: + self._connection = connection + + +class ResponsesResponseResource(BaseResponsesConnectionResource): + def create( + self, + *, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[responses_client_event_param.ContextManagement]] | Omit = omit, + conversation: Optional[responses_client_event_param.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[responses_client_event_param.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[bool] | Omit = omit, + stream_options: Optional[responses_client_event_param.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: responses_client_event_param.ToolChoice | Omit = omit, + tools: Iterable[ToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + ) -> None: + self._connection.send( + cast( + ResponsesClientEventParam, + strip_not_given( + { + "type": "response.create", + "background": background, + "context_management": context_management, + "conversation": conversation, + "include": include, + "input": input, + "instructions": instructions, + "max_output_tokens": max_output_tokens, + "max_tool_calls": max_tool_calls, + "metadata": metadata, + "model": model, + "moderation": moderation, + "parallel_tool_calls": parallel_tool_calls, + "previous_response_id": previous_response_id, + "prompt": prompt, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning": reasoning, + "safety_identifier": safety_identifier, + "service_tier": service_tier, + "store": store, + "stream": stream, + "stream_options": stream_options, + "temperature": temperature, + "text": text, + "tool_choice": tool_choice, + "tools": tools, + "top_logprobs": top_logprobs, + "top_p": top_p, + "truncation": truncation, + "user": user, + } + ), + ) + ) + + +class BaseAsyncResponsesConnectionResource: + def __init__(self, connection: AsyncResponsesConnection) -> None: + self._connection = connection + + +class AsyncResponsesResponseResource(BaseAsyncResponsesConnectionResource): + async def create( + self, + *, + background: Optional[bool] | Omit = omit, + context_management: Optional[Iterable[responses_client_event_param.ContextManagement]] | Omit = omit, + conversation: Optional[responses_client_event_param.Conversation] | Omit = omit, + include: Optional[List[ResponseIncludable]] | Omit = omit, + input: Union[str, ResponseInputParam] | Omit = omit, + instructions: Optional[str] | Omit = omit, + max_output_tokens: Optional[int] | Omit = omit, + max_tool_calls: Optional[int] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + model: ResponsesModel | Omit = omit, + moderation: Optional[responses_client_event_param.Moderation] | Omit = omit, + parallel_tool_calls: Optional[bool] | Omit = omit, + previous_response_id: Optional[str] | Omit = omit, + prompt: Optional[ResponsePromptParam] | Omit = omit, + prompt_cache_key: str | Omit = omit, + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] | Omit = omit, + reasoning: Optional[Reasoning] | Omit = omit, + safety_identifier: str | Omit = omit, + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] | Omit = omit, + store: Optional[bool] | Omit = omit, + stream: Optional[bool] | Omit = omit, + stream_options: Optional[responses_client_event_param.StreamOptions] | Omit = omit, + temperature: Optional[float] | Omit = omit, + text: ResponseTextConfigParam | Omit = omit, + tool_choice: responses_client_event_param.ToolChoice | Omit = omit, + tools: Iterable[ToolParam] | Omit = omit, + top_logprobs: Optional[int] | Omit = omit, + top_p: Optional[float] | Omit = omit, + truncation: Optional[Literal["auto", "disabled"]] | Omit = omit, + user: str | Omit = omit, + ) -> None: + await self._connection.send( + cast( + ResponsesClientEventParam, + strip_not_given( + { + "type": "response.create", + "background": background, + "context_management": context_management, + "conversation": conversation, + "include": include, + "input": input, + "instructions": instructions, + "max_output_tokens": max_output_tokens, + "max_tool_calls": max_tool_calls, + "metadata": metadata, + "model": model, + "moderation": moderation, + "parallel_tool_calls": parallel_tool_calls, + "previous_response_id": previous_response_id, + "prompt": prompt, + "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "reasoning": reasoning, + "safety_identifier": safety_identifier, + "service_tier": service_tier, + "store": store, + "stream": stream, + "stream_options": stream_options, + "temperature": temperature, + "text": text, + "tool_choice": tool_choice, + "tools": tools, + "top_logprobs": top_logprobs, + "top_p": top_p, + "truncation": truncation, + "user": user, + } + ), + ) + ) diff --git a/src/openai/resources/skills/__init__.py b/src/openai/resources/skills/__init__.py new file mode 100644 index 0000000000..07f4d6729d --- /dev/null +++ b/src/openai/resources/skills/__init__.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .skills import ( + Skills, + AsyncSkills, + SkillsWithRawResponse, + AsyncSkillsWithRawResponse, + SkillsWithStreamingResponse, + AsyncSkillsWithStreamingResponse, +) +from .content import ( + Content, + AsyncContent, + ContentWithRawResponse, + AsyncContentWithRawResponse, + ContentWithStreamingResponse, + AsyncContentWithStreamingResponse, +) +from .versions import ( + Versions, + AsyncVersions, + VersionsWithRawResponse, + AsyncVersionsWithRawResponse, + VersionsWithStreamingResponse, + AsyncVersionsWithStreamingResponse, +) + +__all__ = [ + "Content", + "AsyncContent", + "ContentWithRawResponse", + "AsyncContentWithRawResponse", + "ContentWithStreamingResponse", + "AsyncContentWithStreamingResponse", + "Versions", + "AsyncVersions", + "VersionsWithRawResponse", + "AsyncVersionsWithRawResponse", + "VersionsWithStreamingResponse", + "AsyncVersionsWithStreamingResponse", + "Skills", + "AsyncSkills", + "SkillsWithRawResponse", + "AsyncSkillsWithRawResponse", + "SkillsWithStreamingResponse", + "AsyncSkillsWithStreamingResponse", +] diff --git a/src/openai/resources/skills/content.py b/src/openai/resources/skills/content.py new file mode 100644 index 0000000000..d0639a5a52 --- /dev/null +++ b/src/openai/resources/skills/content.py @@ -0,0 +1,176 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ... import _legacy_response +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import path_template +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, + to_custom_streamed_response_wrapper, + async_to_custom_streamed_response_wrapper, +) +from ..._base_client import make_request_options + +__all__ = ["Content", "AsyncContent"] + + +class Content(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ContentWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ContentWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ContentWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ContentWithStreamingResponse(self) + + def retrieve( + self, + skill_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + """ + Download a skill zip bundle by its ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + extra_headers = {"Accept": "application/binary", **(extra_headers or {})} + return self._get( + path_template("/skills/{skill_id}/content", skill_id=skill_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + +class AsyncContent(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncContentWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncContentWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncContentWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncContentWithStreamingResponse(self) + + async def retrieve( + self, + skill_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + """ + Download a skill zip bundle by its ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + extra_headers = {"Accept": "application/binary", **(extra_headers or {})} + return await self._get( + path_template("/skills/{skill_id}/content", skill_id=skill_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + +class ContentWithRawResponse: + def __init__(self, content: Content) -> None: + self._content = content + + self.retrieve = _legacy_response.to_raw_response_wrapper( + content.retrieve, + ) + + +class AsyncContentWithRawResponse: + def __init__(self, content: AsyncContent) -> None: + self._content = content + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + content.retrieve, + ) + + +class ContentWithStreamingResponse: + def __init__(self, content: Content) -> None: + self._content = content + + self.retrieve = to_custom_streamed_response_wrapper( + content.retrieve, + StreamedBinaryAPIResponse, + ) + + +class AsyncContentWithStreamingResponse: + def __init__(self, content: AsyncContent) -> None: + self._content = content + + self.retrieve = async_to_custom_streamed_response_wrapper( + content.retrieve, + AsyncStreamedBinaryAPIResponse, + ) diff --git a/src/openai/resources/skills/skills.py b/src/openai/resources/skills/skills.py new file mode 100644 index 0000000000..aeea43ca3e --- /dev/null +++ b/src/openai/resources/skills/skills.py @@ -0,0 +1,653 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Mapping, cast +from typing_extensions import Literal + +import httpx + +from ... import _legacy_response +from ...types import skill_list_params, skill_create_params, skill_update_params +from .content import ( + Content, + AsyncContent, + ContentWithRawResponse, + AsyncContentWithRawResponse, + ContentWithStreamingResponse, + AsyncContentWithStreamingResponse, +) +from ..._files import deepcopy_with_paths +from ..._types import ( + Body, + Omit, + Query, + Headers, + NotGiven, + FileTypes, + SequenceNotStr, + omit, + not_given, +) +from ..._utils import extract_files, path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncCursorPage, AsyncCursorPage +from ...types.skill import Skill +from ..._base_client import AsyncPaginator, make_request_options +from .versions.versions import ( + Versions, + AsyncVersions, + VersionsWithRawResponse, + AsyncVersionsWithRawResponse, + VersionsWithStreamingResponse, + AsyncVersionsWithStreamingResponse, +) +from ...types.deleted_skill import DeletedSkill + +__all__ = ["Skills", "AsyncSkills"] + + +class Skills(SyncAPIResource): + @cached_property + def content(self) -> Content: + return Content(self._client) + + @cached_property + def versions(self) -> Versions: + return Versions(self._client) + + @cached_property + def with_raw_response(self) -> SkillsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return SkillsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SkillsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return SkillsWithStreamingResponse(self) + + def create( + self, + *, + files: Union[SequenceNotStr[FileTypes], FileTypes] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Skill: + """ + Create a new skill. + + Args: + files: Skill files to upload (directory upload) or a single zip file. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + body = deepcopy_with_paths({"files": files}, [["files", ""], ["files"]]) + extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", ""], ["files"]]) + if extracted_files: + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( + "/skills", + body=maybe_transform(body, skill_create_params.SkillCreateParams), + files=extracted_files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Skill, + ) + + def retrieve( + self, + skill_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Skill: + """ + Get a skill by its ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + return self._get( + path_template("/skills/{skill_id}", skill_id=skill_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Skill, + ) + + def update( + self, + skill_id: str, + *, + default_version: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Skill: + """ + Update the default version pointer for a skill. + + Args: + default_version: The skill version number to set as default. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + return self._post( + path_template("/skills/{skill_id}", skill_id=skill_id), + body=maybe_transform({"default_version": default_version}, skill_update_params.SkillUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Skill, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[Skill]: + """ + List all skills for the current project. + + Args: + after: Identifier for the last item from the previous pagination request + + limit: Number of items to retrieve + + order: Sort order of results by timestamp. Use `asc` for ascending order or `desc` for + descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/skills", + page=SyncCursorPage[Skill], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + skill_list_params.SkillListParams, + ), + security={"bearer_auth": True}, + ), + model=Skill, + ) + + def delete( + self, + skill_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DeletedSkill: + """ + Delete a skill by its ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + return self._delete( + path_template("/skills/{skill_id}", skill_id=skill_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=DeletedSkill, + ) + + +class AsyncSkills(AsyncAPIResource): + @cached_property + def content(self) -> AsyncContent: + return AsyncContent(self._client) + + @cached_property + def versions(self) -> AsyncVersions: + return AsyncVersions(self._client) + + @cached_property + def with_raw_response(self) -> AsyncSkillsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncSkillsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSkillsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncSkillsWithStreamingResponse(self) + + async def create( + self, + *, + files: Union[SequenceNotStr[FileTypes], FileTypes] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Skill: + """ + Create a new skill. + + Args: + files: Skill files to upload (directory upload) or a single zip file. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + body = deepcopy_with_paths({"files": files}, [["files", ""], ["files"]]) + extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", ""], ["files"]]) + if extracted_files: + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + "/skills", + body=await async_maybe_transform(body, skill_create_params.SkillCreateParams), + files=extracted_files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Skill, + ) + + async def retrieve( + self, + skill_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Skill: + """ + Get a skill by its ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + return await self._get( + path_template("/skills/{skill_id}", skill_id=skill_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Skill, + ) + + async def update( + self, + skill_id: str, + *, + default_version: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Skill: + """ + Update the default version pointer for a skill. + + Args: + default_version: The skill version number to set as default. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + return await self._post( + path_template("/skills/{skill_id}", skill_id=skill_id), + body=await async_maybe_transform( + {"default_version": default_version}, skill_update_params.SkillUpdateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Skill, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Skill, AsyncCursorPage[Skill]]: + """ + List all skills for the current project. + + Args: + after: Identifier for the last item from the previous pagination request + + limit: Number of items to retrieve + + order: Sort order of results by timestamp. Use `asc` for ascending order or `desc` for + descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/skills", + page=AsyncCursorPage[Skill], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + skill_list_params.SkillListParams, + ), + security={"bearer_auth": True}, + ), + model=Skill, + ) + + async def delete( + self, + skill_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DeletedSkill: + """ + Delete a skill by its ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + return await self._delete( + path_template("/skills/{skill_id}", skill_id=skill_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=DeletedSkill, + ) + + +class SkillsWithRawResponse: + def __init__(self, skills: Skills) -> None: + self._skills = skills + + self.create = _legacy_response.to_raw_response_wrapper( + skills.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + skills.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + skills.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + skills.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + skills.delete, + ) + + @cached_property + def content(self) -> ContentWithRawResponse: + return ContentWithRawResponse(self._skills.content) + + @cached_property + def versions(self) -> VersionsWithRawResponse: + return VersionsWithRawResponse(self._skills.versions) + + +class AsyncSkillsWithRawResponse: + def __init__(self, skills: AsyncSkills) -> None: + self._skills = skills + + self.create = _legacy_response.async_to_raw_response_wrapper( + skills.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + skills.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + skills.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + skills.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + skills.delete, + ) + + @cached_property + def content(self) -> AsyncContentWithRawResponse: + return AsyncContentWithRawResponse(self._skills.content) + + @cached_property + def versions(self) -> AsyncVersionsWithRawResponse: + return AsyncVersionsWithRawResponse(self._skills.versions) + + +class SkillsWithStreamingResponse: + def __init__(self, skills: Skills) -> None: + self._skills = skills + + self.create = to_streamed_response_wrapper( + skills.create, + ) + self.retrieve = to_streamed_response_wrapper( + skills.retrieve, + ) + self.update = to_streamed_response_wrapper( + skills.update, + ) + self.list = to_streamed_response_wrapper( + skills.list, + ) + self.delete = to_streamed_response_wrapper( + skills.delete, + ) + + @cached_property + def content(self) -> ContentWithStreamingResponse: + return ContentWithStreamingResponse(self._skills.content) + + @cached_property + def versions(self) -> VersionsWithStreamingResponse: + return VersionsWithStreamingResponse(self._skills.versions) + + +class AsyncSkillsWithStreamingResponse: + def __init__(self, skills: AsyncSkills) -> None: + self._skills = skills + + self.create = async_to_streamed_response_wrapper( + skills.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + skills.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + skills.update, + ) + self.list = async_to_streamed_response_wrapper( + skills.list, + ) + self.delete = async_to_streamed_response_wrapper( + skills.delete, + ) + + @cached_property + def content(self) -> AsyncContentWithStreamingResponse: + return AsyncContentWithStreamingResponse(self._skills.content) + + @cached_property + def versions(self) -> AsyncVersionsWithStreamingResponse: + return AsyncVersionsWithStreamingResponse(self._skills.versions) diff --git a/src/openai/resources/skills/versions/__init__.py b/src/openai/resources/skills/versions/__init__.py new file mode 100644 index 0000000000..c9ad6fbbca --- /dev/null +++ b/src/openai/resources/skills/versions/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .content import ( + Content, + AsyncContent, + ContentWithRawResponse, + AsyncContentWithRawResponse, + ContentWithStreamingResponse, + AsyncContentWithStreamingResponse, +) +from .versions import ( + Versions, + AsyncVersions, + VersionsWithRawResponse, + AsyncVersionsWithRawResponse, + VersionsWithStreamingResponse, + AsyncVersionsWithStreamingResponse, +) + +__all__ = [ + "Content", + "AsyncContent", + "ContentWithRawResponse", + "AsyncContentWithRawResponse", + "ContentWithStreamingResponse", + "AsyncContentWithStreamingResponse", + "Versions", + "AsyncVersions", + "VersionsWithRawResponse", + "AsyncVersionsWithRawResponse", + "VersionsWithStreamingResponse", + "AsyncVersionsWithStreamingResponse", +] diff --git a/src/openai/resources/skills/versions/content.py b/src/openai/resources/skills/versions/content.py new file mode 100644 index 0000000000..173f94e4e1 --- /dev/null +++ b/src/openai/resources/skills/versions/content.py @@ -0,0 +1,186 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .... import _legacy_response +from ...._types import Body, Query, Headers, NotGiven, not_given +from ...._utils import path_template +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, + to_custom_streamed_response_wrapper, + async_to_custom_streamed_response_wrapper, +) +from ...._base_client import make_request_options + +__all__ = ["Content", "AsyncContent"] + + +class Content(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ContentWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ContentWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ContentWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ContentWithStreamingResponse(self) + + def retrieve( + self, + version: str, + *, + skill_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + """ + Download a skill version zip bundle. + + Args: + version: The skill version number. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + if not version: + raise ValueError(f"Expected a non-empty value for `version` but received {version!r}") + extra_headers = {"Accept": "application/binary", **(extra_headers or {})} + return self._get( + path_template("/skills/{skill_id}/versions/{version}/content", skill_id=skill_id, version=version), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + +class AsyncContent(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncContentWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncContentWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncContentWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncContentWithStreamingResponse(self) + + async def retrieve( + self, + version: str, + *, + skill_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + """ + Download a skill version zip bundle. + + Args: + version: The skill version number. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + if not version: + raise ValueError(f"Expected a non-empty value for `version` but received {version!r}") + extra_headers = {"Accept": "application/binary", **(extra_headers or {})} + return await self._get( + path_template("/skills/{skill_id}/versions/{version}/content", skill_id=skill_id, version=version), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + +class ContentWithRawResponse: + def __init__(self, content: Content) -> None: + self._content = content + + self.retrieve = _legacy_response.to_raw_response_wrapper( + content.retrieve, + ) + + +class AsyncContentWithRawResponse: + def __init__(self, content: AsyncContent) -> None: + self._content = content + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + content.retrieve, + ) + + +class ContentWithStreamingResponse: + def __init__(self, content: Content) -> None: + self._content = content + + self.retrieve = to_custom_streamed_response_wrapper( + content.retrieve, + StreamedBinaryAPIResponse, + ) + + +class AsyncContentWithStreamingResponse: + def __init__(self, content: AsyncContent) -> None: + self._content = content + + self.retrieve = async_to_custom_streamed_response_wrapper( + content.retrieve, + AsyncStreamedBinaryAPIResponse, + ) diff --git a/src/openai/resources/skills/versions/versions.py b/src/openai/resources/skills/versions/versions.py new file mode 100644 index 0000000000..d688d2917c --- /dev/null +++ b/src/openai/resources/skills/versions/versions.py @@ -0,0 +1,573 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Mapping, cast +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from .content import ( + Content, + AsyncContent, + ContentWithRawResponse, + AsyncContentWithRawResponse, + ContentWithStreamingResponse, + AsyncContentWithStreamingResponse, +) +from ...._files import deepcopy_with_paths +from ...._types import ( + Body, + Omit, + Query, + Headers, + NotGiven, + FileTypes, + SequenceNotStr, + omit, + not_given, +) +from ...._utils import extract_files, path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncCursorPage, AsyncCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.skills import version_list_params, version_create_params +from ....types.skills.skill_version import SkillVersion +from ....types.skills.deleted_skill_version import DeletedSkillVersion + +__all__ = ["Versions", "AsyncVersions"] + + +class Versions(SyncAPIResource): + @cached_property + def content(self) -> Content: + return Content(self._client) + + @cached_property + def with_raw_response(self) -> VersionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return VersionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VersionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return VersionsWithStreamingResponse(self) + + def create( + self, + skill_id: str, + *, + default: bool | Omit = omit, + files: Union[SequenceNotStr[FileTypes], FileTypes] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SkillVersion: + """ + Create a new immutable skill version. + + Args: + default: Whether to set this version as the default. + + files: Skill files to upload (directory upload) or a single zip file. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + body = deepcopy_with_paths( + { + "default": default, + "files": files, + }, + [["files", ""], ["files"]], + ) + extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", ""], ["files"]]) + if extracted_files: + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( + path_template("/skills/{skill_id}/versions", skill_id=skill_id), + body=maybe_transform(body, version_create_params.VersionCreateParams), + files=extracted_files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=SkillVersion, + ) + + def retrieve( + self, + version: str, + *, + skill_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SkillVersion: + """ + Get a specific skill version. + + Args: + version: The version number to retrieve. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + if not version: + raise ValueError(f"Expected a non-empty value for `version` but received {version!r}") + return self._get( + path_template("/skills/{skill_id}/versions/{version}", skill_id=skill_id, version=version), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=SkillVersion, + ) + + def list( + self, + skill_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[SkillVersion]: + """ + List skill versions for a skill. + + Args: + after: The skill version ID to start after. + + limit: Number of versions to retrieve. + + order: Sort order of results by version number. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + return self._get_api_list( + path_template("/skills/{skill_id}/versions", skill_id=skill_id), + page=SyncCursorPage[SkillVersion], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + version_list_params.VersionListParams, + ), + security={"bearer_auth": True}, + ), + model=SkillVersion, + ) + + def delete( + self, + version: str, + *, + skill_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DeletedSkillVersion: + """ + Delete a skill version. + + Args: + version: The skill version number. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + if not version: + raise ValueError(f"Expected a non-empty value for `version` but received {version!r}") + return self._delete( + path_template("/skills/{skill_id}/versions/{version}", skill_id=skill_id, version=version), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=DeletedSkillVersion, + ) + + +class AsyncVersions(AsyncAPIResource): + @cached_property + def content(self) -> AsyncContent: + return AsyncContent(self._client) + + @cached_property + def with_raw_response(self) -> AsyncVersionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncVersionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVersionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncVersionsWithStreamingResponse(self) + + async def create( + self, + skill_id: str, + *, + default: bool | Omit = omit, + files: Union[SequenceNotStr[FileTypes], FileTypes] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SkillVersion: + """ + Create a new immutable skill version. + + Args: + default: Whether to set this version as the default. + + files: Skill files to upload (directory upload) or a single zip file. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + body = deepcopy_with_paths( + { + "default": default, + "files": files, + }, + [["files", ""], ["files"]], + ) + extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", ""], ["files"]]) + if extracted_files: + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + path_template("/skills/{skill_id}/versions", skill_id=skill_id), + body=await async_maybe_transform(body, version_create_params.VersionCreateParams), + files=extracted_files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=SkillVersion, + ) + + async def retrieve( + self, + version: str, + *, + skill_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SkillVersion: + """ + Get a specific skill version. + + Args: + version: The version number to retrieve. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + if not version: + raise ValueError(f"Expected a non-empty value for `version` but received {version!r}") + return await self._get( + path_template("/skills/{skill_id}/versions/{version}", skill_id=skill_id, version=version), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=SkillVersion, + ) + + def list( + self, + skill_id: str, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[SkillVersion, AsyncCursorPage[SkillVersion]]: + """ + List skill versions for a skill. + + Args: + after: The skill version ID to start after. + + limit: Number of versions to retrieve. + + order: Sort order of results by version number. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + return self._get_api_list( + path_template("/skills/{skill_id}/versions", skill_id=skill_id), + page=AsyncCursorPage[SkillVersion], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + version_list_params.VersionListParams, + ), + security={"bearer_auth": True}, + ), + model=SkillVersion, + ) + + async def delete( + self, + version: str, + *, + skill_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DeletedSkillVersion: + """ + Delete a skill version. + + Args: + version: The skill version number. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not skill_id: + raise ValueError(f"Expected a non-empty value for `skill_id` but received {skill_id!r}") + if not version: + raise ValueError(f"Expected a non-empty value for `version` but received {version!r}") + return await self._delete( + path_template("/skills/{skill_id}/versions/{version}", skill_id=skill_id, version=version), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=DeletedSkillVersion, + ) + + +class VersionsWithRawResponse: + def __init__(self, versions: Versions) -> None: + self._versions = versions + + self.create = _legacy_response.to_raw_response_wrapper( + versions.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + versions.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + versions.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + versions.delete, + ) + + @cached_property + def content(self) -> ContentWithRawResponse: + return ContentWithRawResponse(self._versions.content) + + +class AsyncVersionsWithRawResponse: + def __init__(self, versions: AsyncVersions) -> None: + self._versions = versions + + self.create = _legacy_response.async_to_raw_response_wrapper( + versions.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + versions.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + versions.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + versions.delete, + ) + + @cached_property + def content(self) -> AsyncContentWithRawResponse: + return AsyncContentWithRawResponse(self._versions.content) + + +class VersionsWithStreamingResponse: + def __init__(self, versions: Versions) -> None: + self._versions = versions + + self.create = to_streamed_response_wrapper( + versions.create, + ) + self.retrieve = to_streamed_response_wrapper( + versions.retrieve, + ) + self.list = to_streamed_response_wrapper( + versions.list, + ) + self.delete = to_streamed_response_wrapper( + versions.delete, + ) + + @cached_property + def content(self) -> ContentWithStreamingResponse: + return ContentWithStreamingResponse(self._versions.content) + + +class AsyncVersionsWithStreamingResponse: + def __init__(self, versions: AsyncVersions) -> None: + self._versions = versions + + self.create = async_to_streamed_response_wrapper( + versions.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + versions.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + versions.list, + ) + self.delete = async_to_streamed_response_wrapper( + versions.delete, + ) + + @cached_property + def content(self) -> AsyncContentWithStreamingResponse: + return AsyncContentWithStreamingResponse(self._versions.content) diff --git a/src/openai/resources/uploads/parts.py b/src/openai/resources/uploads/parts.py index d46e5ea1bb..a0c1e15223 100644 --- a/src/openai/resources/uploads/parts.py +++ b/src/openai/resources/uploads/parts.py @@ -7,13 +7,9 @@ import httpx from ... import _legacy_response -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from ..._utils import ( - extract_files, - maybe_transform, - deepcopy_minimal, - async_maybe_transform, -) +from ..._files import deepcopy_with_paths +from ..._types import Body, Query, Headers, NotGiven, FileTypes, not_given +from ..._utils import extract_files, path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -25,10 +21,12 @@ class Parts(SyncAPIResource): + """Use Uploads to upload large files in multiple parts.""" + @cached_property def with_raw_response(self) -> PartsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -54,7 +52,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UploadPart: """ Adds a @@ -82,28 +80,34 @@ def create( """ if not upload_id: raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") - body = deepcopy_minimal({"data": data}) + body = deepcopy_with_paths({"data": data}, [["data"]]) files = extract_files(cast(Mapping[str, object], body), paths=[["data"]]) # It should be noted that the actual Content-Type header that will be # sent to the server will contain a `boundary` parameter, e.g. # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return self._post( - f"/uploads/{upload_id}/parts", + path_template("/uploads/{upload_id}/parts", upload_id=upload_id), body=maybe_transform(body, part_create_params.PartCreateParams), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=UploadPart, ) class AsyncParts(AsyncAPIResource): + """Use Uploads to upload large files in multiple parts.""" + @cached_property def with_raw_response(self) -> AsyncPartsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -129,7 +133,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UploadPart: """ Adds a @@ -157,18 +161,22 @@ async def create( """ if not upload_id: raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") - body = deepcopy_minimal({"data": data}) + body = deepcopy_with_paths({"data": data}, [["data"]]) files = extract_files(cast(Mapping[str, object], body), paths=[["data"]]) # It should be noted that the actual Content-Type header that will be # sent to the server will contain a `boundary` parameter, e.g. # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return await self._post( - f"/uploads/{upload_id}/parts", + path_template("/uploads/{upload_id}/parts", upload_id=upload_id), body=await async_maybe_transform(body, part_create_params.PartCreateParams), files=files, options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=UploadPart, ) diff --git a/src/openai/resources/uploads/uploads.py b/src/openai/resources/uploads/uploads.py index 96a531a8e4..6c8c347f97 100644 --- a/src/openai/resources/uploads/uploads.py +++ b/src/openai/resources/uploads/uploads.py @@ -6,7 +6,7 @@ import os import logging import builtins -from typing import List, overload +from typing import overload from pathlib import Path import anyio @@ -22,11 +22,8 @@ AsyncPartsWithStreamingResponse, ) from ...types import FilePurpose, upload_create_params, upload_complete_params -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -44,14 +41,17 @@ class Uploads(SyncAPIResource): + """Use Uploads to upload large files in multiple parts.""" + @cached_property def parts(self) -> Parts: + """Use Uploads to upload large files in multiple parts.""" return Parts(self._client) @cached_property def with_raw_response(self) -> UploadsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -76,7 +76,7 @@ def upload_file_chunked( purpose: FilePurpose, bytes: int | None = None, part_size: int | None = None, - md5: str | NotGiven = NOT_GIVEN, + md5: str | Omit = omit, ) -> Upload: """Splits a file into multiple 64MB parts and uploads them sequentially.""" @@ -90,7 +90,7 @@ def upload_file_chunked( mime_type: str, purpose: FilePurpose, part_size: int | None = None, - md5: str | NotGiven = NOT_GIVEN, + md5: str | Omit = omit, ) -> Upload: """Splits an in-memory file into multiple 64MB parts and uploads them sequentially.""" @@ -103,7 +103,7 @@ def upload_file_chunked( filename: str | None = None, bytes: int | None = None, part_size: int | None = None, - md5: str | NotGiven = NOT_GIVEN, + md5: str | Omit = omit, ) -> Upload: """Splits the given file into multiple parts and uploads them sequentially. @@ -160,9 +160,8 @@ def upload_file_chunked( part = self.parts.create(upload_id=upload.id, data=data) log.info("Uploaded part %s for upload %s", part.id, upload.id) part_ids.append(part.id) - except Exception: + finally: buf.close() - raise return self.complete(upload_id=upload.id, part_ids=part_ids, md5=md5) @@ -173,12 +172,13 @@ def create( filename: str, mime_type: str, purpose: FilePurpose, + expires_after: upload_create_params.ExpiresAfter | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Upload: """ Creates an intermediate @@ -193,15 +193,16 @@ def create( contains all the parts you uploaded. This File is usable in the rest of our platform as a regular File object. - For certain `purpose`s, the correct `mime_type` must be specified. Please refer - to documentation for the supported MIME types for your use case: - - - [Assistants](https://platform.openai.com/docs/assistants/tools/file-search/supported-files) + For certain `purpose` values, the correct `mime_type` must be specified. Please + refer to documentation for the + [supported MIME types for your use case](https://platform.openai.com/docs/assistants/tools/file-search#supported-files). For guidance on the proper filename extensions for each purpose, please follow the documentation on [creating a File](https://platform.openai.com/docs/api-reference/files/create). + Returns the Upload object with status `pending`. + Args: bytes: The number of bytes in the file you are uploading. @@ -217,6 +218,9 @@ def create( See the [documentation on File purposes](https://platform.openai.com/docs/api-reference/files/create#files-create-purpose). + expires_after: The expiration policy for a file. By default, files with `purpose=batch` expire + after 30 days and all other files are persisted until they are manually deleted. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -233,11 +237,16 @@ def create( "filename": filename, "mime_type": mime_type, "purpose": purpose, + "expires_after": expires_after, }, upload_create_params.UploadCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Upload, ) @@ -251,12 +260,14 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Upload: """Cancels the Upload. No Parts may be added after an Upload is cancelled. + Returns the Upload object with status `cancelled`. + Args: extra_headers: Send extra headers @@ -269,9 +280,13 @@ def cancel( if not upload_id: raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") return self._post( - f"/uploads/{upload_id}/cancel", + path_template("/uploads/{upload_id}/cancel", upload_id=upload_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Upload, ) @@ -280,14 +295,14 @@ def complete( self, upload_id: str, *, - part_ids: List[str], - md5: str | NotGiven = NOT_GIVEN, + part_ids: SequenceNotStr[str], + md5: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Upload: """ Completes the @@ -302,7 +317,9 @@ def complete( The number of bytes uploaded upon completion must match the number of bytes initially specified when creating the Upload object. No Parts may be added after - an Upload is completed. + an Upload is completed. Returns the Upload object with status `completed`, + including an additional `file` property containing the created usable File + object. Args: part_ids: The ordered list of Part IDs. @@ -321,7 +338,7 @@ def complete( if not upload_id: raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") return self._post( - f"/uploads/{upload_id}/complete", + path_template("/uploads/{upload_id}/complete", upload_id=upload_id), body=maybe_transform( { "part_ids": part_ids, @@ -330,21 +347,28 @@ def complete( upload_complete_params.UploadCompleteParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Upload, ) class AsyncUploads(AsyncAPIResource): + """Use Uploads to upload large files in multiple parts.""" + @cached_property def parts(self) -> AsyncParts: + """Use Uploads to upload large files in multiple parts.""" return AsyncParts(self._client) @cached_property def with_raw_response(self) -> AsyncUploadsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -369,7 +393,7 @@ async def upload_file_chunked( purpose: FilePurpose, bytes: int | None = None, part_size: int | None = None, - md5: str | NotGiven = NOT_GIVEN, + md5: str | Omit = omit, ) -> Upload: """Splits a file into multiple 64MB parts and uploads them sequentially.""" @@ -383,7 +407,7 @@ async def upload_file_chunked( mime_type: str, purpose: FilePurpose, part_size: int | None = None, - md5: str | NotGiven = NOT_GIVEN, + md5: str | Omit = omit, ) -> Upload: """Splits an in-memory file into multiple 64MB parts and uploads them sequentially.""" @@ -396,7 +420,7 @@ async def upload_file_chunked( filename: str | None = None, bytes: int | None = None, part_size: int | None = None, - md5: str | NotGiven = NOT_GIVEN, + md5: str | Omit = omit, ) -> Upload: """Splits the given file into multiple parts and uploads them sequentially. @@ -464,9 +488,8 @@ async def upload_file_chunked( part = await self.parts.create(upload_id=upload.id, data=data) log.info("Uploaded part %s for upload %s", part.id, upload.id) part_ids.append(part.id) - except Exception: + finally: buf.close() - raise return await self.complete(upload_id=upload.id, part_ids=part_ids, md5=md5) @@ -477,12 +500,13 @@ async def create( filename: str, mime_type: str, purpose: FilePurpose, + expires_after: upload_create_params.ExpiresAfter | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Upload: """ Creates an intermediate @@ -497,15 +521,16 @@ async def create( contains all the parts you uploaded. This File is usable in the rest of our platform as a regular File object. - For certain `purpose`s, the correct `mime_type` must be specified. Please refer - to documentation for the supported MIME types for your use case: - - - [Assistants](https://platform.openai.com/docs/assistants/tools/file-search/supported-files) + For certain `purpose` values, the correct `mime_type` must be specified. Please + refer to documentation for the + [supported MIME types for your use case](https://platform.openai.com/docs/assistants/tools/file-search#supported-files). For guidance on the proper filename extensions for each purpose, please follow the documentation on [creating a File](https://platform.openai.com/docs/api-reference/files/create). + Returns the Upload object with status `pending`. + Args: bytes: The number of bytes in the file you are uploading. @@ -521,6 +546,9 @@ async def create( See the [documentation on File purposes](https://platform.openai.com/docs/api-reference/files/create#files-create-purpose). + expires_after: The expiration policy for a file. By default, files with `purpose=batch` expire + after 30 days and all other files are persisted until they are manually deleted. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -537,11 +565,16 @@ async def create( "filename": filename, "mime_type": mime_type, "purpose": purpose, + "expires_after": expires_after, }, upload_create_params.UploadCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Upload, ) @@ -555,12 +588,14 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Upload: """Cancels the Upload. No Parts may be added after an Upload is cancelled. + Returns the Upload object with status `cancelled`. + Args: extra_headers: Send extra headers @@ -573,9 +608,13 @@ async def cancel( if not upload_id: raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") return await self._post( - f"/uploads/{upload_id}/cancel", + path_template("/uploads/{upload_id}/cancel", upload_id=upload_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Upload, ) @@ -584,14 +623,14 @@ async def complete( self, upload_id: str, *, - part_ids: List[str], - md5: str | NotGiven = NOT_GIVEN, + part_ids: SequenceNotStr[str], + md5: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Upload: """ Completes the @@ -606,7 +645,9 @@ async def complete( The number of bytes uploaded upon completion must match the number of bytes initially specified when creating the Upload object. No Parts may be added after - an Upload is completed. + an Upload is completed. Returns the Upload object with status `completed`, + including an additional `file` property containing the created usable File + object. Args: part_ids: The ordered list of Part IDs. @@ -625,7 +666,7 @@ async def complete( if not upload_id: raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") return await self._post( - f"/uploads/{upload_id}/complete", + path_template("/uploads/{upload_id}/complete", upload_id=upload_id), body=await async_maybe_transform( { "part_ids": part_ids, @@ -634,7 +675,11 @@ async def complete( upload_complete_params.UploadCompleteParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=Upload, ) @@ -656,6 +701,7 @@ def __init__(self, uploads: Uploads) -> None: @cached_property def parts(self) -> PartsWithRawResponse: + """Use Uploads to upload large files in multiple parts.""" return PartsWithRawResponse(self._uploads.parts) @@ -675,6 +721,7 @@ def __init__(self, uploads: AsyncUploads) -> None: @cached_property def parts(self) -> AsyncPartsWithRawResponse: + """Use Uploads to upload large files in multiple parts.""" return AsyncPartsWithRawResponse(self._uploads.parts) @@ -694,6 +741,7 @@ def __init__(self, uploads: Uploads) -> None: @cached_property def parts(self) -> PartsWithStreamingResponse: + """Use Uploads to upload large files in multiple parts.""" return PartsWithStreamingResponse(self._uploads.parts) @@ -713,4 +761,5 @@ def __init__(self, uploads: AsyncUploads) -> None: @cached_property def parts(self) -> AsyncPartsWithStreamingResponse: + """Use Uploads to upload large files in multiple parts.""" return AsyncPartsWithStreamingResponse(self._uploads.parts) diff --git a/src/openai/resources/beta/vector_stores/__init__.py b/src/openai/resources/vector_stores/__init__.py similarity index 100% rename from src/openai/resources/beta/vector_stores/__init__.py rename to src/openai/resources/vector_stores/__init__.py diff --git a/src/openai/resources/beta/vector_stores/file_batches.py b/src/openai/resources/vector_stores/file_batches.py similarity index 71% rename from src/openai/resources/beta/vector_stores/file_batches.py rename to src/openai/resources/vector_stores/file_batches.py index d1f9c872e4..4bde1a4aa6 100644 --- a/src/openai/resources/beta/vector_stores/file_batches.py +++ b/src/openai/resources/vector_stores/file_batches.py @@ -3,31 +3,27 @@ from __future__ import annotations import asyncio -from typing import List, Iterable -from typing_extensions import Literal +from typing import Dict, Iterable, Optional +from typing_extensions import Union, Literal from concurrent.futures import Future, ThreadPoolExecutor, as_completed import httpx import sniffio -from .... import _legacy_response -from ....types import FileObject -from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from ...._utils import ( - is_given, - maybe_transform, - async_maybe_transform, -) -from ...._compat import cached_property -from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ....pagination import SyncCursorPage, AsyncCursorPage -from ....types.beta import FileChunkingStrategyParam -from ...._base_client import AsyncPaginator, make_request_options -from ....types.beta.vector_stores import file_batch_create_params, file_batch_list_files_params -from ....types.beta.file_chunking_strategy_param import FileChunkingStrategyParam -from ....types.beta.vector_stores.vector_store_file import VectorStoreFile -from ....types.beta.vector_stores.vector_store_file_batch import VectorStoreFileBatch +from ... import _legacy_response +from ...types import FileChunkingStrategyParam +from ..._types import Body, Omit, Query, Headers, NotGiven, FileTypes, SequenceNotStr, omit, not_given +from ..._utils import is_given, path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncCursorPage, AsyncCursorPage +from ..._base_client import AsyncPaginator, make_request_options +from ...types.file_object import FileObject +from ...types.vector_stores import file_batch_create_params, file_batch_list_files_params +from ...types.file_chunking_strategy_param import FileChunkingStrategyParam +from ...types.vector_stores.vector_store_file import VectorStoreFile +from ...types.vector_stores.vector_store_file_batch import VectorStoreFileBatch __all__ = ["FileBatches", "AsyncFileBatches"] @@ -36,7 +32,7 @@ class FileBatches(SyncAPIResource): @cached_property def with_raw_response(self) -> FileBatchesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -56,26 +52,44 @@ def create( self, vector_store_id: str, *, - file_ids: List[str], - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + file_ids: SequenceNotStr[str] | Omit = omit, + files: Iterable[file_batch_create_params.File] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """ Create a vector store file batch. Args: - file_ids: A list of [File](https://platform.openai.com/docs/api-reference/files) IDs that - the vector store should use. Useful for tools like `file_search` that can access - files. + attributes: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters, booleans, or numbers. chunking_strategy: The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty. + file_ids: A list of [File](https://platform.openai.com/docs/api-reference/files) IDs that + the vector store should use. Useful for tools like `file_search` that can access + files. If `attributes` or `chunking_strategy` are provided, they will be applied + to all files in the batch. The maximum batch size is 2000 files. This endpoint + is recommended for multi-file ingestion and helps reduce per-vector-store write + request pressure. Mutually exclusive with `files`. + + files: A list of objects that each include a `file_id` plus optional `attributes` or + `chunking_strategy`. Use this when you need to override metadata for specific + files. The global `attributes` or `chunking_strategy` will be ignored and must + be specified for each file. The maximum batch size is 2000 files. This endpoint + is recommended for multi-file ingestion and helps reduce per-vector-store write + request pressure. Mutually exclusive with `file_ids`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -88,16 +102,22 @@ def create( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/vector_stores/{vector_store_id}/file_batches", + path_template("/vector_stores/{vector_store_id}/file_batches", vector_store_id=vector_store_id), body=maybe_transform( { - "file_ids": file_ids, + "attributes": attributes, "chunking_strategy": chunking_strategy, + "file_ids": file_ids, + "files": files, }, file_batch_create_params.FileBatchCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFileBatch, ) @@ -112,7 +132,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """ Retrieves a vector store file batch. @@ -132,9 +152,17 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get( - f"/vector_stores/{vector_store_id}/file_batches/{batch_id}", + path_template( + "/vector_stores/{vector_store_id}/file_batches/{batch_id}", + vector_store_id=vector_store_id, + batch_id=batch_id, + ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFileBatch, ) @@ -149,7 +177,7 @@ def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """Cancel a vector store file batch. @@ -171,9 +199,17 @@ def cancel( raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/vector_stores/{vector_store_id}/file_batches/{batch_id}/cancel", + path_template( + "/vector_stores/{vector_store_id}/file_batches/{batch_id}/cancel", + vector_store_id=vector_store_id, + batch_id=batch_id, + ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFileBatch, ) @@ -182,15 +218,29 @@ def create_and_poll( self, vector_store_id: str, *, - file_ids: List[str], - poll_interval_ms: int | NotGiven = NOT_GIVEN, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + file_ids: SequenceNotStr[str] | Omit = omit, + files: Iterable[file_batch_create_params.File] | Omit = omit, + poll_interval_ms: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """Create a vector store batch and poll until all files have been processed.""" batch = self.create( vector_store_id=vector_store_id, - file_ids=file_ids, + attributes=attributes, chunking_strategy=chunking_strategy, + file_ids=file_ids, + files=files, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, ) # TODO: don't poll unless necessary?? return self.poll( @@ -204,17 +254,17 @@ def list_files( batch_id: str, *, vector_store_id: str, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - filter: Literal["in_progress", "completed", "failed", "cancelled"] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + filter: Literal["in_progress", "completed", "failed", "cancelled"] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[VectorStoreFile]: """ Returns a list of vector store files in a batch. @@ -227,8 +277,8 @@ def list_files( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. filter: Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`. @@ -252,7 +302,11 @@ def list_files( raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get_api_list( - f"/vector_stores/{vector_store_id}/file_batches/{batch_id}/files", + path_template( + "/vector_stores/{vector_store_id}/file_batches/{batch_id}/files", + vector_store_id=vector_store_id, + batch_id=batch_id, + ), page=SyncCursorPage[VectorStoreFile], options=make_request_options( extra_headers=extra_headers, @@ -269,6 +323,7 @@ def list_files( }, file_batch_list_files_params.FileBatchListFilesParams, ), + security={"bearer_auth": True}, ), model=VectorStoreFile, ) @@ -278,7 +333,7 @@ def poll( batch_id: str, *, vector_store_id: str, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + poll_interval_ms: int | Omit = omit, ) -> VectorStoreFileBatch: """Wait for the given file batch to be processed. @@ -316,9 +371,9 @@ def upload_and_poll( *, files: Iterable[FileTypes], max_concurrency: int = 5, - file_ids: List[str] = [], - poll_interval_ms: int | NotGiven = NOT_GIVEN, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + file_ids: SequenceNotStr[str] = [], + poll_interval_ms: int | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFileBatch: """Uploads the given files concurrently and then creates a vector store file batch. @@ -365,7 +420,7 @@ class AsyncFileBatches(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncFileBatchesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -385,26 +440,44 @@ async def create( self, vector_store_id: str, *, - file_ids: List[str], - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + file_ids: SequenceNotStr[str] | Omit = omit, + files: Iterable[file_batch_create_params.File] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """ Create a vector store file batch. Args: - file_ids: A list of [File](https://platform.openai.com/docs/api-reference/files) IDs that - the vector store should use. Useful for tools like `file_search` that can access - files. + attributes: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters, booleans, or numbers. chunking_strategy: The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty. + file_ids: A list of [File](https://platform.openai.com/docs/api-reference/files) IDs that + the vector store should use. Useful for tools like `file_search` that can access + files. If `attributes` or `chunking_strategy` are provided, they will be applied + to all files in the batch. The maximum batch size is 2000 files. This endpoint + is recommended for multi-file ingestion and helps reduce per-vector-store write + request pressure. Mutually exclusive with `files`. + + files: A list of objects that each include a `file_id` plus optional `attributes` or + `chunking_strategy`. Use this when you need to override metadata for specific + files. The global `attributes` or `chunking_strategy` will be ignored and must + be specified for each file. The maximum batch size is 2000 files. This endpoint + is recommended for multi-file ingestion and helps reduce per-vector-store write + request pressure. Mutually exclusive with `file_ids`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -417,16 +490,22 @@ async def create( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/vector_stores/{vector_store_id}/file_batches", + path_template("/vector_stores/{vector_store_id}/file_batches", vector_store_id=vector_store_id), body=await async_maybe_transform( { - "file_ids": file_ids, + "attributes": attributes, "chunking_strategy": chunking_strategy, + "file_ids": file_ids, + "files": files, }, file_batch_create_params.FileBatchCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFileBatch, ) @@ -441,7 +520,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """ Retrieves a vector store file batch. @@ -461,9 +540,17 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._get( - f"/vector_stores/{vector_store_id}/file_batches/{batch_id}", + path_template( + "/vector_stores/{vector_store_id}/file_batches/{batch_id}", + vector_store_id=vector_store_id, + batch_id=batch_id, + ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFileBatch, ) @@ -478,7 +565,7 @@ async def cancel( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """Cancel a vector store file batch. @@ -500,9 +587,17 @@ async def cancel( raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/vector_stores/{vector_store_id}/file_batches/{batch_id}/cancel", + path_template( + "/vector_stores/{vector_store_id}/file_batches/{batch_id}/cancel", + vector_store_id=vector_store_id, + batch_id=batch_id, + ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFileBatch, ) @@ -511,15 +606,29 @@ async def create_and_poll( self, vector_store_id: str, *, - file_ids: List[str], - poll_interval_ms: int | NotGiven = NOT_GIVEN, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + file_ids: SequenceNotStr[str] | Omit = omit, + files: Iterable[file_batch_create_params.File] | Omit = omit, + poll_interval_ms: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileBatch: """Create a vector store batch and poll until all files have been processed.""" batch = await self.create( vector_store_id=vector_store_id, - file_ids=file_ids, + attributes=attributes, chunking_strategy=chunking_strategy, + file_ids=file_ids, + files=files, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, ) # TODO: don't poll unless necessary?? return await self.poll( @@ -533,17 +642,17 @@ def list_files( batch_id: str, *, vector_store_id: str, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - filter: Literal["in_progress", "completed", "failed", "cancelled"] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + filter: Literal["in_progress", "completed", "failed", "cancelled"] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[VectorStoreFile, AsyncCursorPage[VectorStoreFile]]: """ Returns a list of vector store files in a batch. @@ -556,8 +665,8 @@ def list_files( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. filter: Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`. @@ -581,7 +690,11 @@ def list_files( raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get_api_list( - f"/vector_stores/{vector_store_id}/file_batches/{batch_id}/files", + path_template( + "/vector_stores/{vector_store_id}/file_batches/{batch_id}/files", + vector_store_id=vector_store_id, + batch_id=batch_id, + ), page=AsyncCursorPage[VectorStoreFile], options=make_request_options( extra_headers=extra_headers, @@ -598,6 +711,7 @@ def list_files( }, file_batch_list_files_params.FileBatchListFilesParams, ), + security={"bearer_auth": True}, ), model=VectorStoreFile, ) @@ -607,7 +721,7 @@ async def poll( batch_id: str, *, vector_store_id: str, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + poll_interval_ms: int | Omit = omit, ) -> VectorStoreFileBatch: """Wait for the given file batch to be processed. @@ -645,9 +759,9 @@ async def upload_and_poll( *, files: Iterable[FileTypes], max_concurrency: int = 5, - file_ids: List[str] = [], - poll_interval_ms: int | NotGiven = NOT_GIVEN, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + file_ids: SequenceNotStr[str] = [], + poll_interval_ms: int | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFileBatch: """Uploads the given files concurrently and then creates a vector store file batch. diff --git a/src/openai/resources/beta/vector_stores/files.py b/src/openai/resources/vector_stores/files.py similarity index 57% rename from src/openai/resources/beta/vector_stores/files.py rename to src/openai/resources/vector_stores/files.py index fe43bb3488..b7e1ea9f92 100644 --- a/src/openai/resources/beta/vector_stores/files.py +++ b/src/openai/resources/vector_stores/files.py @@ -2,28 +2,25 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Union, Optional from typing_extensions import Literal, assert_never import httpx -from .... import _legacy_response -from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from ...._utils import ( - is_given, - maybe_transform, - async_maybe_transform, -) -from ...._compat import cached_property -from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ....pagination import SyncCursorPage, AsyncCursorPage -from ....types.beta import FileChunkingStrategyParam -from ...._base_client import AsyncPaginator, make_request_options -from ....types.beta.vector_stores import file_list_params, file_create_params -from ....types.beta.file_chunking_strategy_param import FileChunkingStrategyParam -from ....types.beta.vector_stores.vector_store_file import VectorStoreFile -from ....types.beta.vector_stores.vector_store_file_deleted import VectorStoreFileDeleted +from ... import _legacy_response +from ...types import FileChunkingStrategyParam +from ..._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given +from ..._utils import is_given, path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncPage, AsyncPage, SyncCursorPage, AsyncCursorPage +from ..._base_client import AsyncPaginator, make_request_options +from ...types.vector_stores import file_list_params, file_create_params, file_update_params +from ...types.file_chunking_strategy_param import FileChunkingStrategyParam +from ...types.vector_stores.vector_store_file import VectorStoreFile +from ...types.vector_stores.file_content_response import FileContentResponse +from ...types.vector_stores.vector_store_file_deleted import VectorStoreFileDeleted __all__ = ["Files", "AsyncFiles"] @@ -32,7 +29,7 @@ class Files(SyncAPIResource): @cached_property def with_raw_response(self) -> FilesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -53,13 +50,14 @@ def create( vector_store_id: str, *, file_id: str, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """ Create a vector store file by attaching a @@ -69,7 +67,15 @@ def create( Args: file_id: A [File](https://platform.openai.com/docs/api-reference/files) ID that the vector store should use. Useful for tools like `file_search` that can access - files. + files. For multi-file ingestion, we recommend + [`file_batches`](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/createBatch) + to minimize per-vector-store write requests. + + attributes: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters, booleans, or numbers. chunking_strategy: The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty. @@ -86,16 +92,21 @@ def create( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/vector_stores/{vector_store_id}/files", + path_template("/vector_stores/{vector_store_id}/files", vector_store_id=vector_store_id), body=maybe_transform( { "file_id": file_id, + "attributes": attributes, "chunking_strategy": chunking_strategy, }, file_create_params.FileCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFile, ) @@ -110,7 +121,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """ Retrieves a vector store file. @@ -130,9 +141,66 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get( - f"/vector_stores/{vector_store_id}/files/{file_id}", + path_template( + "/vector_stores/{vector_store_id}/files/{file_id}", vector_store_id=vector_store_id, file_id=file_id + ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=VectorStoreFile, + ) + + def update( + self, + file_id: str, + *, + vector_store_id: str, + attributes: Optional[Dict[str, Union[str, float, bool]]], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> VectorStoreFile: + """ + Update attributes on a vector store file. + + Args: + attributes: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters, booleans, or numbers. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not vector_store_id: + raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return self._post( + path_template( + "/vector_stores/{vector_store_id}/files/{file_id}", vector_store_id=vector_store_id, file_id=file_id + ), + body=maybe_transform({"attributes": attributes}, file_update_params.FileUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFile, ) @@ -141,17 +209,17 @@ def list( self, vector_store_id: str, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - filter: Literal["in_progress", "completed", "failed", "cancelled"] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + filter: Literal["in_progress", "completed", "failed", "cancelled"] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[VectorStoreFile]: """ Returns a list of vector store files. @@ -164,8 +232,8 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. filter: Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`. @@ -187,7 +255,7 @@ def list( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get_api_list( - f"/vector_stores/{vector_store_id}/files", + path_template("/vector_stores/{vector_store_id}/files", vector_store_id=vector_store_id), page=SyncCursorPage[VectorStoreFile], options=make_request_options( extra_headers=extra_headers, @@ -204,6 +272,7 @@ def list( }, file_list_params.FileListParams, ), + security={"bearer_auth": True}, ), model=VectorStoreFile, ) @@ -218,7 +287,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileDeleted: """Delete a vector store file. @@ -242,9 +311,15 @@ def delete( raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._delete( - f"/vector_stores/{vector_store_id}/files/{file_id}", + path_template( + "/vector_stores/{vector_store_id}/files/{file_id}", vector_store_id=vector_store_id, file_id=file_id + ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFileDeleted, ) @@ -254,11 +329,27 @@ def create_and_poll( file_id: str, *, vector_store_id: str, - poll_interval_ms: int | NotGiven = NOT_GIVEN, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + poll_interval_ms: int | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """Attach a file to the given vector store and wait for it to be processed.""" - self.create(vector_store_id=vector_store_id, file_id=file_id, chunking_strategy=chunking_strategy) + self.create( + vector_store_id=vector_store_id, + file_id=file_id, + chunking_strategy=chunking_strategy, + attributes=attributes, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) return self.poll( file_id, @@ -271,7 +362,7 @@ def poll( file_id: str, *, vector_store_id: str, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + poll_interval_ms: int | Omit = omit, ) -> VectorStoreFile: """Wait for the vector store file to finish processing. @@ -312,7 +403,7 @@ def upload( *, vector_store_id: str, file: FileTypes, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFile: """Upload a file to the `files` API and then attach it to the given vector store. @@ -327,8 +418,9 @@ def upload_and_poll( *, vector_store_id: str, file: FileTypes, - poll_interval_ms: int | NotGiven = NOT_GIVEN, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + poll_interval_ms: int | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFile: """Add a file to a vector store and poll until processing is complete.""" file_obj = self._client.files.create(file=file, purpose="assistants") @@ -337,6 +429,53 @@ def upload_and_poll( file_id=file_obj.id, chunking_strategy=chunking_strategy, poll_interval_ms=poll_interval_ms, + attributes=attributes, + ) + + def content( + self, + file_id: str, + *, + vector_store_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncPage[FileContentResponse]: + """ + Retrieve the parsed contents of a vector store file. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not vector_store_id: + raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return self._get_api_list( + path_template( + "/vector_stores/{vector_store_id}/files/{file_id}/content", + vector_store_id=vector_store_id, + file_id=file_id, + ), + page=SyncPage[FileContentResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + model=FileContentResponse, ) @@ -344,7 +483,7 @@ class AsyncFiles(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncFilesWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -365,13 +504,14 @@ async def create( vector_store_id: str, *, file_id: str, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """ Create a vector store file by attaching a @@ -381,7 +521,15 @@ async def create( Args: file_id: A [File](https://platform.openai.com/docs/api-reference/files) ID that the vector store should use. Useful for tools like `file_search` that can access - files. + files. For multi-file ingestion, we recommend + [`file_batches`](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/createBatch) + to minimize per-vector-store write requests. + + attributes: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters, booleans, or numbers. chunking_strategy: The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty. @@ -398,16 +546,21 @@ async def create( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/vector_stores/{vector_store_id}/files", + path_template("/vector_stores/{vector_store_id}/files", vector_store_id=vector_store_id), body=await async_maybe_transform( { "file_id": file_id, + "attributes": attributes, "chunking_strategy": chunking_strategy, }, file_create_params.FileCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFile, ) @@ -422,7 +575,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """ Retrieves a vector store file. @@ -442,9 +595,66 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._get( - f"/vector_stores/{vector_store_id}/files/{file_id}", + path_template( + "/vector_stores/{vector_store_id}/files/{file_id}", vector_store_id=vector_store_id, file_id=file_id + ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=VectorStoreFile, + ) + + async def update( + self, + file_id: str, + *, + vector_store_id: str, + attributes: Optional[Dict[str, Union[str, float, bool]]], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> VectorStoreFile: + """ + Update attributes on a vector store file. + + Args: + attributes: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters, booleans, or numbers. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not vector_store_id: + raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return await self._post( + path_template( + "/vector_stores/{vector_store_id}/files/{file_id}", vector_store_id=vector_store_id, file_id=file_id + ), + body=await async_maybe_transform({"attributes": attributes}, file_update_params.FileUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFile, ) @@ -453,17 +663,17 @@ def list( self, vector_store_id: str, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - filter: Literal["in_progress", "completed", "failed", "cancelled"] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + filter: Literal["in_progress", "completed", "failed", "cancelled"] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[VectorStoreFile, AsyncCursorPage[VectorStoreFile]]: """ Returns a list of vector store files. @@ -476,8 +686,8 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. filter: Filter by file status. One of `in_progress`, `completed`, `failed`, `cancelled`. @@ -499,7 +709,7 @@ def list( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get_api_list( - f"/vector_stores/{vector_store_id}/files", + path_template("/vector_stores/{vector_store_id}/files", vector_store_id=vector_store_id), page=AsyncCursorPage[VectorStoreFile], options=make_request_options( extra_headers=extra_headers, @@ -516,6 +726,7 @@ def list( }, file_list_params.FileListParams, ), + security={"bearer_auth": True}, ), model=VectorStoreFile, ) @@ -530,7 +741,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFileDeleted: """Delete a vector store file. @@ -554,9 +765,15 @@ async def delete( raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._delete( - f"/vector_stores/{vector_store_id}/files/{file_id}", + path_template( + "/vector_stores/{vector_store_id}/files/{file_id}", vector_store_id=vector_store_id, file_id=file_id + ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreFileDeleted, ) @@ -566,11 +783,27 @@ async def create_and_poll( file_id: str, *, vector_store_id: str, - poll_interval_ms: int | NotGiven = NOT_GIVEN, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + poll_interval_ms: int | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreFile: """Attach a file to the given vector store and wait for it to be processed.""" - await self.create(vector_store_id=vector_store_id, file_id=file_id, chunking_strategy=chunking_strategy) + await self.create( + vector_store_id=vector_store_id, + file_id=file_id, + chunking_strategy=chunking_strategy, + attributes=attributes, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) return await self.poll( file_id, @@ -583,7 +816,7 @@ async def poll( file_id: str, *, vector_store_id: str, - poll_interval_ms: int | NotGiven = NOT_GIVEN, + poll_interval_ms: int | Omit = omit, ) -> VectorStoreFile: """Wait for the vector store file to finish processing. @@ -624,7 +857,7 @@ async def upload( *, vector_store_id: str, file: FileTypes, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFile: """Upload a file to the `files` API and then attach it to the given vector store. @@ -641,8 +874,9 @@ async def upload_and_poll( *, vector_store_id: str, file: FileTypes, - poll_interval_ms: int | NotGiven = NOT_GIVEN, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, + attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit, + poll_interval_ms: int | Omit = omit, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, ) -> VectorStoreFile: """Add a file to a vector store and poll until processing is complete.""" file_obj = await self._client.files.create(file=file, purpose="assistants") @@ -651,6 +885,53 @@ async def upload_and_poll( file_id=file_obj.id, poll_interval_ms=poll_interval_ms, chunking_strategy=chunking_strategy, + attributes=attributes, + ) + + def content( + self, + file_id: str, + *, + vector_store_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[FileContentResponse, AsyncPage[FileContentResponse]]: + """ + Retrieve the parsed contents of a vector store file. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not vector_store_id: + raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return self._get_api_list( + path_template( + "/vector_stores/{vector_store_id}/files/{file_id}/content", + vector_store_id=vector_store_id, + file_id=file_id, + ), + page=AsyncPage[FileContentResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + model=FileContentResponse, ) @@ -664,12 +945,18 @@ def __init__(self, files: Files) -> None: self.retrieve = _legacy_response.to_raw_response_wrapper( files.retrieve, ) + self.update = _legacy_response.to_raw_response_wrapper( + files.update, + ) self.list = _legacy_response.to_raw_response_wrapper( files.list, ) self.delete = _legacy_response.to_raw_response_wrapper( files.delete, ) + self.content = _legacy_response.to_raw_response_wrapper( + files.content, + ) class AsyncFilesWithRawResponse: @@ -682,12 +969,18 @@ def __init__(self, files: AsyncFiles) -> None: self.retrieve = _legacy_response.async_to_raw_response_wrapper( files.retrieve, ) + self.update = _legacy_response.async_to_raw_response_wrapper( + files.update, + ) self.list = _legacy_response.async_to_raw_response_wrapper( files.list, ) self.delete = _legacy_response.async_to_raw_response_wrapper( files.delete, ) + self.content = _legacy_response.async_to_raw_response_wrapper( + files.content, + ) class FilesWithStreamingResponse: @@ -700,12 +993,18 @@ def __init__(self, files: Files) -> None: self.retrieve = to_streamed_response_wrapper( files.retrieve, ) + self.update = to_streamed_response_wrapper( + files.update, + ) self.list = to_streamed_response_wrapper( files.list, ) self.delete = to_streamed_response_wrapper( files.delete, ) + self.content = to_streamed_response_wrapper( + files.content, + ) class AsyncFilesWithStreamingResponse: @@ -718,9 +1017,15 @@ def __init__(self, files: AsyncFiles) -> None: self.retrieve = async_to_streamed_response_wrapper( files.retrieve, ) + self.update = async_to_streamed_response_wrapper( + files.update, + ) self.list = async_to_streamed_response_wrapper( files.list, ) self.delete = async_to_streamed_response_wrapper( files.delete, ) + self.content = async_to_streamed_response_wrapper( + files.content, + ) diff --git a/src/openai/resources/beta/vector_stores/vector_stores.py b/src/openai/resources/vector_stores/vector_stores.py similarity index 65% rename from src/openai/resources/beta/vector_stores/vector_stores.py rename to src/openai/resources/vector_stores/vector_stores.py index 06e26852b4..e4c5d1440c 100644 --- a/src/openai/resources/beta/vector_stores/vector_stores.py +++ b/src/openai/resources/vector_stores/vector_stores.py @@ -2,12 +2,12 @@ from __future__ import annotations -from typing import List, Optional +from typing import Union, Optional from typing_extensions import Literal import httpx -from .... import _legacy_response +from ... import _legacy_response from .files import ( Files, AsyncFiles, @@ -16,14 +16,19 @@ FilesWithStreamingResponse, AsyncFilesWithStreamingResponse, ) -from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ...._utils import ( - maybe_transform, - async_maybe_transform, +from ...types import ( + FileChunkingStrategyParam, + vector_store_list_params, + vector_store_create_params, + vector_store_search_params, + vector_store_update_params, ) -from ...._compat import cached_property -from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncPage, AsyncPage, SyncCursorPage, AsyncCursorPage from .file_batches import ( FileBatches, AsyncFileBatches, @@ -32,17 +37,12 @@ FileBatchesWithStreamingResponse, AsyncFileBatchesWithStreamingResponse, ) -from ....pagination import SyncCursorPage, AsyncCursorPage -from ....types.beta import ( - FileChunkingStrategyParam, - vector_store_list_params, - vector_store_create_params, - vector_store_update_params, -) -from ...._base_client import AsyncPaginator, make_request_options -from ....types.beta.vector_store import VectorStore -from ....types.beta.vector_store_deleted import VectorStoreDeleted -from ....types.beta.file_chunking_strategy_param import FileChunkingStrategyParam +from ..._base_client import AsyncPaginator, make_request_options +from ...types.vector_store import VectorStore +from ...types.vector_store_deleted import VectorStoreDeleted +from ...types.shared_params.metadata import Metadata +from ...types.file_chunking_strategy_param import FileChunkingStrategyParam +from ...types.vector_store_search_response import VectorStoreSearchResponse __all__ = ["VectorStores", "AsyncVectorStores"] @@ -59,7 +59,7 @@ def file_batches(self) -> FileBatches: @cached_property def with_raw_response(self) -> VectorStoresWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -78,17 +78,18 @@ def with_streaming_response(self) -> VectorStoresWithStreamingResponse: def create( self, *, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, - expires_after: vector_store_create_params.ExpiresAfter | NotGiven = NOT_GIVEN, - file_ids: List[str] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - name: str | NotGiven = NOT_GIVEN, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + description: str | Omit = omit, + expires_after: vector_store_create_params.ExpiresAfter | Omit = omit, + file_ids: SequenceNotStr[str] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + name: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Create a vector store. @@ -97,6 +98,9 @@ def create( chunking_strategy: The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty. + description: A description for the vector store. Can be used to describe the vector store's + purpose. + expires_after: The expiration policy for a vector store. file_ids: A list of [File](https://platform.openai.com/docs/api-reference/files) IDs that @@ -104,9 +108,11 @@ def create( files. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. name: The name of the vector store. @@ -124,6 +130,7 @@ def create( body=maybe_transform( { "chunking_strategy": chunking_strategy, + "description": description, "expires_after": expires_after, "file_ids": file_ids, "metadata": metadata, @@ -132,7 +139,11 @@ def create( vector_store_create_params.VectorStoreCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStore, ) @@ -146,7 +157,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Retrieves a vector store. @@ -164,9 +175,13 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._get( - f"/vector_stores/{vector_store_id}", + path_template("/vector_stores/{vector_store_id}", vector_store_id=vector_store_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStore, ) @@ -175,15 +190,15 @@ def update( self, vector_store_id: str, *, - expires_after: Optional[vector_store_update_params.ExpiresAfter] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - name: Optional[str] | NotGiven = NOT_GIVEN, + expires_after: Optional[vector_store_update_params.ExpiresAfter] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Modifies a vector store. @@ -192,9 +207,11 @@ def update( expires_after: The expiration policy for a vector store. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. name: The name of the vector store. @@ -210,7 +227,7 @@ def update( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._post( - f"/vector_stores/{vector_store_id}", + path_template("/vector_stores/{vector_store_id}", vector_store_id=vector_store_id), body=maybe_transform( { "expires_after": expires_after, @@ -220,7 +237,11 @@ def update( vector_store_update_params.VectorStoreUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStore, ) @@ -228,16 +249,16 @@ def update( def list( self, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[VectorStore]: """Returns a list of vector stores. @@ -251,8 +272,8 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20. @@ -286,6 +307,7 @@ def list( }, vector_store_list_params.VectorStoreListParams, ), + security={"bearer_auth": True}, ), model=VectorStore, ) @@ -299,7 +321,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreDeleted: """ Delete a vector store. @@ -317,13 +339,84 @@ def delete( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return self._delete( - f"/vector_stores/{vector_store_id}", + path_template("/vector_stores/{vector_store_id}", vector_store_id=vector_store_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreDeleted, ) + def search( + self, + vector_store_id: str, + *, + query: Union[str, SequenceNotStr[str]], + filters: vector_store_search_params.Filters | Omit = omit, + max_num_results: int | Omit = omit, + ranking_options: vector_store_search_params.RankingOptions | Omit = omit, + rewrite_query: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncPage[VectorStoreSearchResponse]: + """ + Search a vector store for relevant chunks based on a query and file attributes + filter. + + Args: + query: A query string for a search + + filters: A filter to apply based on file attributes. + + max_num_results: The maximum number of results to return. This number should be between 1 and 50 + inclusive. + + ranking_options: Ranking options for search. + + rewrite_query: Whether to rewrite the natural language query for vector search. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not vector_store_id: + raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return self._get_api_list( + path_template("/vector_stores/{vector_store_id}/search", vector_store_id=vector_store_id), + page=SyncPage[VectorStoreSearchResponse], + body=maybe_transform( + { + "query": query, + "filters": filters, + "max_num_results": max_num_results, + "ranking_options": ranking_options, + "rewrite_query": rewrite_query, + }, + vector_store_search_params.VectorStoreSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + model=VectorStoreSearchResponse, + method="post", + ) + class AsyncVectorStores(AsyncAPIResource): @cached_property @@ -337,7 +430,7 @@ def file_batches(self) -> AsyncFileBatches: @cached_property def with_raw_response(self) -> AsyncVectorStoresWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers @@ -356,17 +449,18 @@ def with_streaming_response(self) -> AsyncVectorStoresWithStreamingResponse: async def create( self, *, - chunking_strategy: FileChunkingStrategyParam | NotGiven = NOT_GIVEN, - expires_after: vector_store_create_params.ExpiresAfter | NotGiven = NOT_GIVEN, - file_ids: List[str] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - name: str | NotGiven = NOT_GIVEN, + chunking_strategy: FileChunkingStrategyParam | Omit = omit, + description: str | Omit = omit, + expires_after: vector_store_create_params.ExpiresAfter | Omit = omit, + file_ids: SequenceNotStr[str] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + name: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Create a vector store. @@ -375,6 +469,9 @@ async def create( chunking_strategy: The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty. + description: A description for the vector store. Can be used to describe the vector store's + purpose. + expires_after: The expiration policy for a vector store. file_ids: A list of [File](https://platform.openai.com/docs/api-reference/files) IDs that @@ -382,9 +479,11 @@ async def create( files. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. name: The name of the vector store. @@ -402,6 +501,7 @@ async def create( body=await async_maybe_transform( { "chunking_strategy": chunking_strategy, + "description": description, "expires_after": expires_after, "file_ids": file_ids, "metadata": metadata, @@ -410,7 +510,11 @@ async def create( vector_store_create_params.VectorStoreCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStore, ) @@ -424,7 +528,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Retrieves a vector store. @@ -442,9 +546,13 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._get( - f"/vector_stores/{vector_store_id}", + path_template("/vector_stores/{vector_store_id}", vector_store_id=vector_store_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStore, ) @@ -453,15 +561,15 @@ async def update( self, vector_store_id: str, *, - expires_after: Optional[vector_store_update_params.ExpiresAfter] | NotGiven = NOT_GIVEN, - metadata: Optional[object] | NotGiven = NOT_GIVEN, - name: Optional[str] | NotGiven = NOT_GIVEN, + expires_after: Optional[vector_store_update_params.ExpiresAfter] | Omit = omit, + metadata: Optional[Metadata] | Omit = omit, + name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStore: """ Modifies a vector store. @@ -470,9 +578,11 @@ async def update( expires_after: The expiration policy for a vector store. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful - for storing additional information about the object in a structured format. Keys - can be a maximum of 64 characters long and values can be a maxium of 512 - characters long. + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. name: The name of the vector store. @@ -488,7 +598,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._post( - f"/vector_stores/{vector_store_id}", + path_template("/vector_stores/{vector_store_id}", vector_store_id=vector_store_id), body=await async_maybe_transform( { "expires_after": expires_after, @@ -498,7 +608,11 @@ async def update( vector_store_update_params.VectorStoreUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStore, ) @@ -506,16 +620,16 @@ async def update( def list( self, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[VectorStore, AsyncCursorPage[VectorStore]]: """Returns a list of vector stores. @@ -529,8 +643,8 @@ def list( before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, - ending with obj_foo, your subsequent call can include before=obj_foo in order to - fetch the previous page of the list. + starting with obj_foo, your subsequent call can include before=obj_foo in order + to fetch the previous page of the list. limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20. @@ -564,6 +678,7 @@ def list( }, vector_store_list_params.VectorStoreListParams, ), + security={"bearer_auth": True}, ), model=VectorStore, ) @@ -577,7 +692,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> VectorStoreDeleted: """ Delete a vector store. @@ -595,13 +710,84 @@ async def delete( raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} return await self._delete( - f"/vector_stores/{vector_store_id}", + path_template("/vector_stores/{vector_store_id}", vector_store_id=vector_store_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, ), cast_to=VectorStoreDeleted, ) + def search( + self, + vector_store_id: str, + *, + query: Union[str, SequenceNotStr[str]], + filters: vector_store_search_params.Filters | Omit = omit, + max_num_results: int | Omit = omit, + ranking_options: vector_store_search_params.RankingOptions | Omit = omit, + rewrite_query: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[VectorStoreSearchResponse, AsyncPage[VectorStoreSearchResponse]]: + """ + Search a vector store for relevant chunks based on a query and file attributes + filter. + + Args: + query: A query string for a search + + filters: A filter to apply based on file attributes. + + max_num_results: The maximum number of results to return. This number should be between 1 and 50 + inclusive. + + ranking_options: Ranking options for search. + + rewrite_query: Whether to rewrite the natural language query for vector search. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not vector_store_id: + raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") + extra_headers = {"OpenAI-Beta": "assistants=v2", **(extra_headers or {})} + return self._get_api_list( + path_template("/vector_stores/{vector_store_id}/search", vector_store_id=vector_store_id), + page=AsyncPage[VectorStoreSearchResponse], + body=maybe_transform( + { + "query": query, + "filters": filters, + "max_num_results": max_num_results, + "ranking_options": ranking_options, + "rewrite_query": rewrite_query, + }, + vector_store_search_params.VectorStoreSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + model=VectorStoreSearchResponse, + method="post", + ) + class VectorStoresWithRawResponse: def __init__(self, vector_stores: VectorStores) -> None: @@ -622,6 +808,9 @@ def __init__(self, vector_stores: VectorStores) -> None: self.delete = _legacy_response.to_raw_response_wrapper( vector_stores.delete, ) + self.search = _legacy_response.to_raw_response_wrapper( + vector_stores.search, + ) @cached_property def files(self) -> FilesWithRawResponse: @@ -651,6 +840,9 @@ def __init__(self, vector_stores: AsyncVectorStores) -> None: self.delete = _legacy_response.async_to_raw_response_wrapper( vector_stores.delete, ) + self.search = _legacy_response.async_to_raw_response_wrapper( + vector_stores.search, + ) @cached_property def files(self) -> AsyncFilesWithRawResponse: @@ -680,6 +872,9 @@ def __init__(self, vector_stores: VectorStores) -> None: self.delete = to_streamed_response_wrapper( vector_stores.delete, ) + self.search = to_streamed_response_wrapper( + vector_stores.search, + ) @cached_property def files(self) -> FilesWithStreamingResponse: @@ -709,6 +904,9 @@ def __init__(self, vector_stores: AsyncVectorStores) -> None: self.delete = async_to_streamed_response_wrapper( vector_stores.delete, ) + self.search = async_to_streamed_response_wrapper( + vector_stores.search, + ) @cached_property def files(self) -> AsyncFilesWithStreamingResponse: diff --git a/src/openai/resources/videos.py b/src/openai/resources/videos.py new file mode 100644 index 0000000000..e9bad80afe --- /dev/null +++ b/src/openai/resources/videos.py @@ -0,0 +1,1352 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import TYPE_CHECKING, Mapping, cast +from typing_extensions import Literal, assert_never + +import httpx + +from .. import _legacy_response +from ..types import ( + VideoSize, + VideoSeconds, + video_edit_params, + video_list_params, + video_remix_params, + video_create_params, + video_extend_params, + video_create_character_params, + video_download_content_params, +) +from .._files import deepcopy_with_paths +from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given +from .._utils import extract_files, path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, + to_streamed_response_wrapper, + async_to_streamed_response_wrapper, + to_custom_streamed_response_wrapper, + async_to_custom_streamed_response_wrapper, +) +from ..pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from ..types.video import Video +from .._base_client import AsyncPaginator, make_request_options +from .._utils._utils import is_given +from ..types.video_size import VideoSize +from ..types.video_seconds import VideoSeconds +from ..types.video_model_param import VideoModelParam +from ..types.video_delete_response import VideoDeleteResponse +from ..types.video_get_character_response import VideoGetCharacterResponse +from ..types.video_create_character_response import VideoCreateCharacterResponse + +__all__ = ["Videos", "AsyncVideos"] + + +class Videos(SyncAPIResource): + @cached_property + def with_raw_response(self) -> VideosWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return VideosWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VideosWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return VideosWithStreamingResponse(self) + + def create( + self, + *, + prompt: str, + input_reference: video_create_params.InputReference | Omit = omit, + model: VideoModelParam | Omit = omit, + seconds: VideoSeconds | Omit = omit, + size: VideoSize | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """ + Create a new video generation job from a prompt and optional reference assets. + + Args: + prompt: Text prompt that describes the video to generate. + + input_reference: Optional reference asset upload or reference object that guides generation. + + model: The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults + to `sora-2`. + + seconds: Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds. + + size: Output resolution formatted as width x height (allowed values: 720x1280, + 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + body = deepcopy_with_paths( + { + "prompt": prompt, + "input_reference": input_reference, + "model": model, + "seconds": seconds, + "size": size, + }, + [["input_reference"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["input_reference"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( + "/videos", + body=maybe_transform(body, video_create_params.VideoCreateParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Video, + ) + + def create_and_poll( + self, + *, + prompt: str, + input_reference: video_create_params.InputReference | Omit = omit, + model: VideoModelParam | Omit = omit, + seconds: VideoSeconds | Omit = omit, + size: VideoSize | Omit = omit, + poll_interval_ms: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """Create a video and wait for it to be processed.""" + video = self.create( + model=model, + prompt=prompt, + input_reference=input_reference, + seconds=seconds, + size=size, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + return self.poll( + video.id, + poll_interval_ms=poll_interval_ms, + ) + + def poll( + self, + video_id: str, + *, + poll_interval_ms: int | Omit = omit, + ) -> Video: + """Wait for the vector store file to finish processing. + + Note: this will return even if the file failed to process, you need to check + file.last_error and file.status to handle these cases + """ + headers: dict[str, str] = {"X-Stainless-Poll-Helper": "true"} + if is_given(poll_interval_ms): + headers["X-Stainless-Custom-Poll-Interval"] = str(poll_interval_ms) + + while True: + response = self.with_raw_response.retrieve( + video_id, + extra_headers=headers, + ) + + video = response.parse() + if video.status == "in_progress" or video.status == "queued": + if not is_given(poll_interval_ms): + from_header = response.headers.get("openai-poll-after-ms") + if from_header is not None: + poll_interval_ms = int(from_header) + else: + poll_interval_ms = 1000 + + self._sleep(poll_interval_ms / 1000) + elif video.status == "completed" or video.status == "failed": + return video + else: + if TYPE_CHECKING: # type: ignore[unreachable] + assert_never(video.status) + else: + return video + + def retrieve( + self, + video_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """ + Fetch the latest metadata for a generated video. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not video_id: + raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}") + return self._get( + path_template("/videos/{video_id}", video_id=video_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Video, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncConversationCursorPage[Video]: + """ + List recently generated videos for the current project. + + Args: + after: Identifier for the last item from the previous pagination request + + limit: Number of items to retrieve + + order: Sort order of results by timestamp. Use `asc` for ascending order or `desc` for + descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/videos", + page=SyncConversationCursorPage[Video], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + video_list_params.VideoListParams, + ), + security={"bearer_auth": True}, + ), + model=Video, + ) + + def delete( + self, + video_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> VideoDeleteResponse: + """ + Permanently delete a completed or failed video and its stored assets. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not video_id: + raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}") + return self._delete( + path_template("/videos/{video_id}", video_id=video_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=VideoDeleteResponse, + ) + + def create_character( + self, + *, + name: str, + video: FileTypes, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> VideoCreateCharacterResponse: + """ + Create a character from an uploaded video. + + Args: + name: Display name for this API character. + + video: Video file used to create a character. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + body = deepcopy_with_paths( + { + "name": name, + "video": video, + }, + [["video"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["video"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( + "/videos/characters", + body=maybe_transform(body, video_create_character_params.VideoCreateCharacterParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=VideoCreateCharacterResponse, + ) + + def download_content( + self, + video_id: str, + *, + variant: Literal["video", "thumbnail", "spritesheet"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + """ + Download the generated video bytes or a derived preview asset. + + Streams the rendered video content for the specified video job. + + Args: + variant: Which downloadable asset to return. Defaults to the MP4 video. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not video_id: + raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}") + extra_headers = {"Accept": "application/binary", **(extra_headers or {})} + return self._get( + path_template("/videos/{video_id}/content", video_id=video_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"variant": variant}, video_download_content_params.VideoDownloadContentParams), + security={"bearer_auth": True}, + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + def edit( + self, + *, + prompt: str, + video: video_edit_params.Video, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """ + Create a new video generation job by editing a source video or existing + generated video. + + Args: + prompt: Text prompt that describes how to edit the source video. + + video: Reference to the completed video to edit. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + body = deepcopy_with_paths( + { + "prompt": prompt, + "video": video, + }, + [["video"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["video"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( + "/videos/edits", + body=maybe_transform(body, video_edit_params.VideoEditParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Video, + ) + + def extend( + self, + *, + prompt: str, + seconds: VideoSeconds, + video: video_extend_params.Video, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """ + Create an extension of a completed video. + + Args: + prompt: Updated text prompt that directs the extension generation. + + seconds: Length of the newly generated extension segment in seconds (allowed values: 4, + 8, 12, 16, 20). + + video: Reference to the completed video to extend. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + body = deepcopy_with_paths( + { + "prompt": prompt, + "seconds": seconds, + "video": video, + }, + [["video"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["video"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( + "/videos/extensions", + body=maybe_transform(body, video_extend_params.VideoExtendParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Video, + ) + + def get_character( + self, + character_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> VideoGetCharacterResponse: + """ + Fetch a character. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not character_id: + raise ValueError(f"Expected a non-empty value for `character_id` but received {character_id!r}") + return self._get( + path_template("/videos/characters/{character_id}", character_id=character_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=VideoGetCharacterResponse, + ) + + def remix( + self, + video_id: str, + *, + prompt: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """ + Create a remix of a completed video using a refreshed prompt. + + Args: + prompt: Updated text prompt that directs the remix generation. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not video_id: + raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}") + return self._post( + path_template("/videos/{video_id}/remix", video_id=video_id), + body=maybe_transform({"prompt": prompt}, video_remix_params.VideoRemixParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Video, + ) + + +class AsyncVideos(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncVideosWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncVideosWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVideosWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncVideosWithStreamingResponse(self) + + async def create( + self, + *, + prompt: str, + input_reference: video_create_params.InputReference | Omit = omit, + model: VideoModelParam | Omit = omit, + seconds: VideoSeconds | Omit = omit, + size: VideoSize | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """ + Create a new video generation job from a prompt and optional reference assets. + + Args: + prompt: Text prompt that describes the video to generate. + + input_reference: Optional reference asset upload or reference object that guides generation. + + model: The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults + to `sora-2`. + + seconds: Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds. + + size: Output resolution formatted as width x height (allowed values: 720x1280, + 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + body = deepcopy_with_paths( + { + "prompt": prompt, + "input_reference": input_reference, + "model": model, + "seconds": seconds, + "size": size, + }, + [["input_reference"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["input_reference"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + "/videos", + body=await async_maybe_transform(body, video_create_params.VideoCreateParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Video, + ) + + async def create_and_poll( + self, + *, + prompt: str, + input_reference: video_create_params.InputReference | Omit = omit, + model: VideoModelParam | Omit = omit, + seconds: VideoSeconds | Omit = omit, + size: VideoSize | Omit = omit, + poll_interval_ms: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """Create a video and wait for it to be processed.""" + video = await self.create( + model=model, + prompt=prompt, + input_reference=input_reference, + seconds=seconds, + size=size, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + return await self.poll( + video.id, + poll_interval_ms=poll_interval_ms, + ) + + async def poll( + self, + video_id: str, + *, + poll_interval_ms: int | Omit = omit, + ) -> Video: + """Wait for the vector store file to finish processing. + + Note: this will return even if the file failed to process, you need to check + file.last_error and file.status to handle these cases + """ + headers: dict[str, str] = {"X-Stainless-Poll-Helper": "true"} + if is_given(poll_interval_ms): + headers["X-Stainless-Custom-Poll-Interval"] = str(poll_interval_ms) + + while True: + response = await self.with_raw_response.retrieve( + video_id, + extra_headers=headers, + ) + + video = response.parse() + if video.status == "in_progress" or video.status == "queued": + if not is_given(poll_interval_ms): + from_header = response.headers.get("openai-poll-after-ms") + if from_header is not None: + poll_interval_ms = int(from_header) + else: + poll_interval_ms = 1000 + + await self._sleep(poll_interval_ms / 1000) + elif video.status == "completed" or video.status == "failed": + return video + else: + if TYPE_CHECKING: # type: ignore[unreachable] + assert_never(video.status) + else: + return video + + async def retrieve( + self, + video_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """ + Fetch the latest metadata for a generated video. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not video_id: + raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}") + return await self._get( + path_template("/videos/{video_id}", video_id=video_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Video, + ) + + def list( + self, + *, + after: str | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Video, AsyncConversationCursorPage[Video]]: + """ + List recently generated videos for the current project. + + Args: + after: Identifier for the last item from the previous pagination request + + limit: Number of items to retrieve + + order: Sort order of results by timestamp. Use `asc` for ascending order or `desc` for + descending order. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/videos", + page=AsyncConversationCursorPage[Video], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + }, + video_list_params.VideoListParams, + ), + security={"bearer_auth": True}, + ), + model=Video, + ) + + async def delete( + self, + video_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> VideoDeleteResponse: + """ + Permanently delete a completed or failed video and its stored assets. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not video_id: + raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}") + return await self._delete( + path_template("/videos/{video_id}", video_id=video_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=VideoDeleteResponse, + ) + + async def create_character( + self, + *, + name: str, + video: FileTypes, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> VideoCreateCharacterResponse: + """ + Create a character from an uploaded video. + + Args: + name: Display name for this API character. + + video: Video file used to create a character. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + body = deepcopy_with_paths( + { + "name": name, + "video": video, + }, + [["video"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["video"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + "/videos/characters", + body=await async_maybe_transform(body, video_create_character_params.VideoCreateCharacterParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=VideoCreateCharacterResponse, + ) + + async def download_content( + self, + video_id: str, + *, + variant: Literal["video", "thumbnail", "spritesheet"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> _legacy_response.HttpxBinaryResponseContent: + """ + Download the generated video bytes or a derived preview asset. + + Streams the rendered video content for the specified video job. + + Args: + variant: Which downloadable asset to return. Defaults to the MP4 video. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not video_id: + raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}") + extra_headers = {"Accept": "application/binary", **(extra_headers or {})} + return await self._get( + path_template("/videos/{video_id}/content", video_id=video_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"variant": variant}, video_download_content_params.VideoDownloadContentParams + ), + security={"bearer_auth": True}, + ), + cast_to=_legacy_response.HttpxBinaryResponseContent, + ) + + async def edit( + self, + *, + prompt: str, + video: video_edit_params.Video, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """ + Create a new video generation job by editing a source video or existing + generated video. + + Args: + prompt: Text prompt that describes how to edit the source video. + + video: Reference to the completed video to edit. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + body = deepcopy_with_paths( + { + "prompt": prompt, + "video": video, + }, + [["video"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["video"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + "/videos/edits", + body=await async_maybe_transform(body, video_edit_params.VideoEditParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Video, + ) + + async def extend( + self, + *, + prompt: str, + seconds: VideoSeconds, + video: video_extend_params.Video, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """ + Create an extension of a completed video. + + Args: + prompt: Updated text prompt that directs the extension generation. + + seconds: Length of the newly generated extension segment in seconds (allowed values: 4, + 8, 12, 16, 20). + + video: Reference to the completed video to extend. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + body = deepcopy_with_paths( + { + "prompt": prompt, + "seconds": seconds, + "video": video, + }, + [["video"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["video"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + "/videos/extensions", + body=await async_maybe_transform(body, video_extend_params.VideoExtendParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Video, + ) + + async def get_character( + self, + character_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> VideoGetCharacterResponse: + """ + Fetch a character. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not character_id: + raise ValueError(f"Expected a non-empty value for `character_id` but received {character_id!r}") + return await self._get( + path_template("/videos/characters/{character_id}", character_id=character_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=VideoGetCharacterResponse, + ) + + async def remix( + self, + video_id: str, + *, + prompt: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Video: + """ + Create a remix of a completed video using a refreshed prompt. + + Args: + prompt: Updated text prompt that directs the remix generation. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not video_id: + raise ValueError(f"Expected a non-empty value for `video_id` but received {video_id!r}") + return await self._post( + path_template("/videos/{video_id}/remix", video_id=video_id), + body=await async_maybe_transform({"prompt": prompt}, video_remix_params.VideoRemixParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"bearer_auth": True}, + ), + cast_to=Video, + ) + + +class VideosWithRawResponse: + def __init__(self, videos: Videos) -> None: + self._videos = videos + + self.create = _legacy_response.to_raw_response_wrapper( + videos.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + videos.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + videos.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + videos.delete, + ) + self.create_character = _legacy_response.to_raw_response_wrapper( + videos.create_character, + ) + self.download_content = _legacy_response.to_raw_response_wrapper( + videos.download_content, + ) + self.edit = _legacy_response.to_raw_response_wrapper( + videos.edit, + ) + self.extend = _legacy_response.to_raw_response_wrapper( + videos.extend, + ) + self.get_character = _legacy_response.to_raw_response_wrapper( + videos.get_character, + ) + self.remix = _legacy_response.to_raw_response_wrapper( + videos.remix, + ) + + +class AsyncVideosWithRawResponse: + def __init__(self, videos: AsyncVideos) -> None: + self._videos = videos + + self.create = _legacy_response.async_to_raw_response_wrapper( + videos.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + videos.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + videos.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + videos.delete, + ) + self.create_character = _legacy_response.async_to_raw_response_wrapper( + videos.create_character, + ) + self.download_content = _legacy_response.async_to_raw_response_wrapper( + videos.download_content, + ) + self.edit = _legacy_response.async_to_raw_response_wrapper( + videos.edit, + ) + self.extend = _legacy_response.async_to_raw_response_wrapper( + videos.extend, + ) + self.get_character = _legacy_response.async_to_raw_response_wrapper( + videos.get_character, + ) + self.remix = _legacy_response.async_to_raw_response_wrapper( + videos.remix, + ) + + +class VideosWithStreamingResponse: + def __init__(self, videos: Videos) -> None: + self._videos = videos + + self.create = to_streamed_response_wrapper( + videos.create, + ) + self.retrieve = to_streamed_response_wrapper( + videos.retrieve, + ) + self.list = to_streamed_response_wrapper( + videos.list, + ) + self.delete = to_streamed_response_wrapper( + videos.delete, + ) + self.create_character = to_streamed_response_wrapper( + videos.create_character, + ) + self.download_content = to_custom_streamed_response_wrapper( + videos.download_content, + StreamedBinaryAPIResponse, + ) + self.edit = to_streamed_response_wrapper( + videos.edit, + ) + self.extend = to_streamed_response_wrapper( + videos.extend, + ) + self.get_character = to_streamed_response_wrapper( + videos.get_character, + ) + self.remix = to_streamed_response_wrapper( + videos.remix, + ) + + +class AsyncVideosWithStreamingResponse: + def __init__(self, videos: AsyncVideos) -> None: + self._videos = videos + + self.create = async_to_streamed_response_wrapper( + videos.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + videos.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + videos.list, + ) + self.delete = async_to_streamed_response_wrapper( + videos.delete, + ) + self.create_character = async_to_streamed_response_wrapper( + videos.create_character, + ) + self.download_content = async_to_custom_streamed_response_wrapper( + videos.download_content, + AsyncStreamedBinaryAPIResponse, + ) + self.edit = async_to_streamed_response_wrapper( + videos.edit, + ) + self.extend = async_to_streamed_response_wrapper( + videos.extend, + ) + self.get_character = async_to_streamed_response_wrapper( + videos.get_character, + ) + self.remix = async_to_streamed_response_wrapper( + videos.remix, + ) diff --git a/src/openai/resources/webhooks/__init__.py b/src/openai/resources/webhooks/__init__.py new file mode 100644 index 0000000000..66449ee7e3 --- /dev/null +++ b/src/openai/resources/webhooks/__init__.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .webhooks import Webhooks as _Webhooks, AsyncWebhooks as _AsyncWebhooks + + +class Webhooks(_Webhooks): + pass + + +class AsyncWebhooks(_AsyncWebhooks): + pass + + +__all__ = ["Webhooks", "AsyncWebhooks"] diff --git a/src/openai/resources/webhooks/api.md b/src/openai/resources/webhooks/api.md new file mode 100644 index 0000000000..8e3c312eb0 --- /dev/null +++ b/src/openai/resources/webhooks/api.md @@ -0,0 +1,24 @@ +# Webhooks + +Types: + +```python +from openai.types.webhooks import ( + BatchCancelledWebhookEvent, + BatchCompletedWebhookEvent, + BatchExpiredWebhookEvent, + BatchFailedWebhookEvent, + EvalRunCanceledWebhookEvent, + EvalRunFailedWebhookEvent, + EvalRunSucceededWebhookEvent, + FineTuningJobCancelledWebhookEvent, + FineTuningJobFailedWebhookEvent, + FineTuningJobSucceededWebhookEvent, + RealtimeCallIncomingWebhookEvent, + ResponseCancelledWebhookEvent, + ResponseCompletedWebhookEvent, + ResponseFailedWebhookEvent, + ResponseIncompleteWebhookEvent, + UnwrapWebhookEvent, +) +``` diff --git a/src/openai/resources/webhooks/webhooks.py b/src/openai/resources/webhooks/webhooks.py new file mode 100644 index 0000000000..8d99568aad --- /dev/null +++ b/src/openai/resources/webhooks/webhooks.py @@ -0,0 +1,210 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import hmac +import json +import time +import base64 +import hashlib +from typing import cast + +from ..._types import HeadersLike +from ..._utils import get_required_header +from ..._models import construct_type +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._exceptions import InvalidWebhookSignatureError +from ...types.webhooks.unwrap_webhook_event import UnwrapWebhookEvent + +__all__ = ["Webhooks", "AsyncWebhooks"] + + +class Webhooks(SyncAPIResource): + def unwrap( + self, + payload: str | bytes, + headers: HeadersLike, + *, + secret: str | None = None, + ) -> UnwrapWebhookEvent: + """Validates that the given payload was sent by OpenAI and parses the payload.""" + if secret is None: + secret = self._client.webhook_secret + + self.verify_signature(payload=payload, headers=headers, secret=secret) + + return cast( + UnwrapWebhookEvent, + construct_type( + type_=UnwrapWebhookEvent, + value=json.loads(payload), + ), + ) + + def verify_signature( + self, + payload: str | bytes, + headers: HeadersLike, + *, + secret: str | None = None, + tolerance: int = 300, + ) -> None: + """Validates whether or not the webhook payload was sent by OpenAI. + + Args: + payload: The webhook payload + headers: The webhook headers + secret: The webhook secret (optional, will use client secret if not provided) + tolerance: Maximum age of the webhook in seconds (default: 300 = 5 minutes) + """ + if secret is None: + secret = self._client.webhook_secret + + if secret is None: + raise ValueError( + "The webhook secret must either be set using the env var, OPENAI_WEBHOOK_SECRET, " + "on the client class, OpenAI(webhook_secret='123'), or passed to this function" + ) + + signature_header = get_required_header(headers, "webhook-signature") + timestamp = get_required_header(headers, "webhook-timestamp") + webhook_id = get_required_header(headers, "webhook-id") + + # Validate timestamp to prevent replay attacks + try: + timestamp_seconds = int(timestamp) + except ValueError: + raise InvalidWebhookSignatureError("Invalid webhook timestamp format") from None + + now = int(time.time()) + + if now - timestamp_seconds > tolerance: + raise InvalidWebhookSignatureError("Webhook timestamp is too old") from None + + if timestamp_seconds > now + tolerance: + raise InvalidWebhookSignatureError("Webhook timestamp is too new") from None + + # Extract signatures from v1, format + # The signature header can have multiple values, separated by spaces. + # Each value is in the format v1,. We should accept if any match. + signatures: list[str] = [] + for part in signature_header.split(): + if part.startswith("v1,"): + signatures.append(part[3:]) + else: + signatures.append(part) + + # Decode the secret if it starts with whsec_ + if secret.startswith("whsec_"): + decoded_secret = base64.b64decode(secret[6:]) + else: + decoded_secret = secret.encode() + + body = payload.decode("utf-8") if isinstance(payload, bytes) else payload + + # Prepare the signed payload (OpenAI uses webhookId.timestamp.payload format) + signed_payload = f"{webhook_id}.{timestamp}.{body}" + expected_signature = base64.b64encode( + hmac.new(decoded_secret, signed_payload.encode(), hashlib.sha256).digest() + ).decode() + + # Accept if any signature matches + if not any(hmac.compare_digest(expected_signature, sig) for sig in signatures): + raise InvalidWebhookSignatureError( + "The given webhook signature does not match the expected signature" + ) from None + + +class AsyncWebhooks(AsyncAPIResource): + def unwrap( + self, + payload: str | bytes, + headers: HeadersLike, + *, + secret: str | None = None, + ) -> UnwrapWebhookEvent: + """Validates that the given payload was sent by OpenAI and parses the payload.""" + if secret is None: + secret = self._client.webhook_secret + + self.verify_signature(payload=payload, headers=headers, secret=secret) + + body = payload.decode("utf-8") if isinstance(payload, bytes) else payload + return cast( + UnwrapWebhookEvent, + construct_type( + type_=UnwrapWebhookEvent, + value=json.loads(body), + ), + ) + + def verify_signature( + self, + payload: str | bytes, + headers: HeadersLike, + *, + secret: str | None = None, + tolerance: int = 300, + ) -> None: + """Validates whether or not the webhook payload was sent by OpenAI. + + Args: + payload: The webhook payload + headers: The webhook headers + secret: The webhook secret (optional, will use client secret if not provided) + tolerance: Maximum age of the webhook in seconds (default: 300 = 5 minutes) + """ + if secret is None: + secret = self._client.webhook_secret + + if secret is None: + raise ValueError( + "The webhook secret must either be set using the env var, OPENAI_WEBHOOK_SECRET, " + "on the client class, OpenAI(webhook_secret='123'), or passed to this function" + ) from None + + signature_header = get_required_header(headers, "webhook-signature") + timestamp = get_required_header(headers, "webhook-timestamp") + webhook_id = get_required_header(headers, "webhook-id") + + # Validate timestamp to prevent replay attacks + try: + timestamp_seconds = int(timestamp) + except ValueError: + raise InvalidWebhookSignatureError("Invalid webhook timestamp format") from None + + now = int(time.time()) + + if now - timestamp_seconds > tolerance: + raise InvalidWebhookSignatureError("Webhook timestamp is too old") from None + + if timestamp_seconds > now + tolerance: + raise InvalidWebhookSignatureError("Webhook timestamp is too new") from None + + # Extract signatures from v1, format + # The signature header can have multiple values, separated by spaces. + # Each value is in the format v1,. We should accept if any match. + signatures: list[str] = [] + for part in signature_header.split(): + if part.startswith("v1,"): + signatures.append(part[3:]) + else: + signatures.append(part) + + # Decode the secret if it starts with whsec_ + if secret.startswith("whsec_"): + decoded_secret = base64.b64decode(secret[6:]) + else: + decoded_secret = secret.encode() + + body = payload.decode("utf-8") if isinstance(payload, bytes) else payload + + # Prepare the signed payload (OpenAI uses webhookId.timestamp.payload format) + signed_payload = f"{webhook_id}.{timestamp}.{body}" + expected_signature = base64.b64encode( + hmac.new(decoded_secret, signed_payload.encode(), hashlib.sha256).digest() + ).decode() + + # Accept if any signature matches + if not any(hmac.compare_digest(expected_signature, sig) for sig in signatures): + raise InvalidWebhookSignatureError("The given webhook signature does not match the expected signature") diff --git a/src/openai/types/__init__.py b/src/openai/types/__init__.py index ad9284fbd5..dc2c8a348c 100644 --- a/src/openai/types/__init__.py +++ b/src/openai/types/__init__.py @@ -5,43 +5,132 @@ from .batch import Batch as Batch from .image import Image as Image from .model import Model as Model +from .skill import Skill as Skill +from .video import Video as Video from .shared import ( + Metadata as Metadata, + AllModels as AllModels, + ChatModel as ChatModel, + Reasoning as Reasoning, ErrorObject as ErrorObject, + CompoundFilter as CompoundFilter, + OAuthErrorCode as OAuthErrorCode, + ResponsesModel as ResponsesModel, + ReasoningEffort as ReasoningEffort, + ComparisonFilter as ComparisonFilter, FunctionDefinition as FunctionDefinition, FunctionParameters as FunctionParameters, ResponseFormatText as ResponseFormatText, + CustomToolInputFormat as CustomToolInputFormat, ResponseFormatJSONObject as ResponseFormatJSONObject, ResponseFormatJSONSchema as ResponseFormatJSONSchema, + ResponseFormatTextPython as ResponseFormatTextPython, + ResponseFormatTextGrammar as ResponseFormatTextGrammar, ) from .upload import Upload as Upload from .embedding import Embedding as Embedding from .chat_model import ChatModel as ChatModel from .completion import Completion as Completion from .moderation import Moderation as Moderation +from .skill_list import SkillList as SkillList +from .video_size import VideoSize as VideoSize from .audio_model import AudioModel as AudioModel from .batch_error import BatchError as BatchError +from .batch_usage import BatchUsage as BatchUsage from .file_object import FileObject as FileObject from .image_model import ImageModel as ImageModel +from .video_model import VideoModel as VideoModel from .file_content import FileContent as FileContent from .file_deleted import FileDeleted as FileDeleted from .file_purpose import FilePurpose as FilePurpose +from .vector_store import VectorStore as VectorStore +from .deleted_skill import DeletedSkill as DeletedSkill from .model_deleted import ModelDeleted as ModelDeleted +from .video_seconds import VideoSeconds as VideoSeconds +from .embedding_model import EmbeddingModel as EmbeddingModel from .images_response import ImagesResponse as ImagesResponse from .completion_usage import CompletionUsage as CompletionUsage +from .eval_list_params import EvalListParams as EvalListParams from .file_list_params import FileListParams as FileListParams from .moderation_model import ModerationModel as ModerationModel from .batch_list_params import BatchListParams as BatchListParams from .completion_choice import CompletionChoice as CompletionChoice from .image_edit_params import ImageEditParams as ImageEditParams +from .skill_list_params import SkillListParams as SkillListParams +from .video_edit_params import VideoEditParams as VideoEditParams +from .video_list_params import VideoListParams as VideoListParams +from .video_model_param import VideoModelParam as VideoModelParam +from .eval_create_params import EvalCreateParams as EvalCreateParams +from .eval_list_response import EvalListResponse as EvalListResponse +from .eval_update_params import EvalUpdateParams as EvalUpdateParams from .file_create_params import FileCreateParams as FileCreateParams +from .video_create_error import VideoCreateError as VideoCreateError +from .video_remix_params import VideoRemixParams as VideoRemixParams from .batch_create_params import BatchCreateParams as BatchCreateParams +from .skill_create_params import SkillCreateParams as SkillCreateParams +from .skill_update_params import SkillUpdateParams as SkillUpdateParams +from .video_create_params import VideoCreateParams as VideoCreateParams +from .video_extend_params import VideoExtendParams as VideoExtendParams from .batch_request_counts import BatchRequestCounts as BatchRequestCounts +from .eval_create_response import EvalCreateResponse as EvalCreateResponse +from .eval_delete_response import EvalDeleteResponse as EvalDeleteResponse +from .eval_update_response import EvalUpdateResponse as EvalUpdateResponse from .upload_create_params import UploadCreateParams as UploadCreateParams +from .vector_store_deleted import VectorStoreDeleted as VectorStoreDeleted +from .audio_response_format import AudioResponseFormat as AudioResponseFormat +from .container_list_params import ContainerListParams as ContainerListParams from .image_generate_params import ImageGenerateParams as ImageGenerateParams +from .video_delete_response import VideoDeleteResponse as VideoDeleteResponse +from .eval_retrieve_response import EvalRetrieveResponse as EvalRetrieveResponse +from .file_chunking_strategy import FileChunkingStrategy as FileChunkingStrategy +from .image_gen_stream_event import ImageGenStreamEvent as ImageGenStreamEvent from .upload_complete_params import UploadCompleteParams as UploadCompleteParams +from .websocket_reconnection import ( + ReconnectingEvent as ReconnectingEvent, + ReconnectingOverrides as ReconnectingOverrides, +) +from .container_create_params import ContainerCreateParams as ContainerCreateParams +from .container_list_response import ContainerListResponse as ContainerListResponse from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams +from .image_edit_stream_event import ImageEditStreamEvent as ImageEditStreamEvent from .completion_create_params import CompletionCreateParams as CompletionCreateParams from .moderation_create_params import ModerationCreateParams as ModerationCreateParams +from .vector_store_list_params import VectorStoreListParams as VectorStoreListParams +from .container_create_response import ContainerCreateResponse as ContainerCreateResponse from .create_embedding_response import CreateEmbeddingResponse as CreateEmbeddingResponse +from .image_gen_completed_event import ImageGenCompletedEvent as ImageGenCompletedEvent +from .image_edit_completed_event import ImageEditCompletedEvent as ImageEditCompletedEvent from .moderation_create_response import ModerationCreateResponse as ModerationCreateResponse +from .vector_store_create_params import VectorStoreCreateParams as VectorStoreCreateParams +from .vector_store_search_params import VectorStoreSearchParams as VectorStoreSearchParams +from .vector_store_update_params import VectorStoreUpdateParams as VectorStoreUpdateParams +from .container_retrieve_response import ContainerRetrieveResponse as ContainerRetrieveResponse +from .image_input_reference_param import ImageInputReferenceParam as ImageInputReferenceParam +from .moderation_text_input_param import ModerationTextInputParam as ModerationTextInputParam +from .file_chunking_strategy_param import FileChunkingStrategyParam as FileChunkingStrategyParam +from .vector_store_search_response import VectorStoreSearchResponse as VectorStoreSearchResponse +from .video_get_character_response import VideoGetCharacterResponse as VideoGetCharacterResponse +from .websocket_connection_options import ( + WebSocketConnectionOptions as WebSocketConnectionOptions, + WebsocketConnectionOptions as WebsocketConnectionOptions, +) from .image_create_variation_params import ImageCreateVariationParams as ImageCreateVariationParams +from .image_gen_partial_image_event import ImageGenPartialImageEvent as ImageGenPartialImageEvent +from .static_file_chunking_strategy import StaticFileChunkingStrategy as StaticFileChunkingStrategy +from .video_create_character_params import VideoCreateCharacterParams as VideoCreateCharacterParams +from .video_download_content_params import VideoDownloadContentParams as VideoDownloadContentParams +from .eval_custom_data_source_config import EvalCustomDataSourceConfig as EvalCustomDataSourceConfig +from .image_edit_partial_image_event import ImageEditPartialImageEvent as ImageEditPartialImageEvent +from .video_create_character_response import VideoCreateCharacterResponse as VideoCreateCharacterResponse +from .moderation_image_url_input_param import ModerationImageURLInputParam as ModerationImageURLInputParam +from .auto_file_chunking_strategy_param import AutoFileChunkingStrategyParam as AutoFileChunkingStrategyParam +from .moderation_multi_modal_input_param import ModerationMultiModalInputParam as ModerationMultiModalInputParam +from .other_file_chunking_strategy_object import OtherFileChunkingStrategyObject as OtherFileChunkingStrategyObject +from .static_file_chunking_strategy_param import StaticFileChunkingStrategyParam as StaticFileChunkingStrategyParam +from .static_file_chunking_strategy_object import StaticFileChunkingStrategyObject as StaticFileChunkingStrategyObject +from .eval_stored_completions_data_source_config import ( + EvalStoredCompletionsDataSourceConfig as EvalStoredCompletionsDataSourceConfig, +) +from .static_file_chunking_strategy_object_param import ( + StaticFileChunkingStrategyObjectParam as StaticFileChunkingStrategyObjectParam, +) diff --git a/src/openai/types/admin/__init__.py b/src/openai/types/admin/__init__.py new file mode 100644 index 0000000000..f8ee8b14b1 --- /dev/null +++ b/src/openai/types/admin/__init__.py @@ -0,0 +1,3 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations diff --git a/src/openai/types/admin/organization/__init__.py b/src/openai/types/admin/organization/__init__.py new file mode 100644 index 0000000000..d14d8dce41 --- /dev/null +++ b/src/openai/types/admin/organization/__init__.py @@ -0,0 +1,78 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .role import Role as Role +from .group import Group as Group +from .invite import Invite as Invite +from .project import Project as Project +from .certificate import Certificate as Certificate +from .admin_api_key import AdminAPIKey as AdminAPIKey +from .role_list_params import RoleListParams as RoleListParams +from .user_list_params import UserListParams as UserListParams +from .group_list_params import GroupListParams as GroupListParams +from .organization_user import OrganizationUser as OrganizationUser +from .invite_list_params import InviteListParams as InviteListParams +from .role_create_params import RoleCreateParams as RoleCreateParams +from .role_update_params import RoleUpdateParams as RoleUpdateParams +from .usage_costs_params import UsageCostsParams as UsageCostsParams +from .user_update_params import UserUpdateParams as UserUpdateParams +from .group_create_params import GroupCreateParams as GroupCreateParams +from .group_update_params import GroupUpdateParams as GroupUpdateParams +from .project_list_params import ProjectListParams as ProjectListParams +from .usage_images_params import UsageImagesParams as UsageImagesParams +from .invite_create_params import InviteCreateParams as InviteCreateParams +from .role_delete_response import RoleDeleteResponse as RoleDeleteResponse +from .usage_costs_response import UsageCostsResponse as UsageCostsResponse +from .user_delete_response import UserDeleteResponse as UserDeleteResponse +from .audit_log_list_params import AuditLogListParams as AuditLogListParams +from .group_delete_response import GroupDeleteResponse as GroupDeleteResponse +from .group_update_response import GroupUpdateResponse as GroupUpdateResponse +from .project_create_params import ProjectCreateParams as ProjectCreateParams +from .project_update_params import ProjectUpdateParams as ProjectUpdateParams +from .usage_images_response import UsageImagesResponse as UsageImagesResponse +from .invite_delete_response import InviteDeleteResponse as InviteDeleteResponse +from .audit_log_list_response import AuditLogListResponse as AuditLogListResponse +from .certificate_list_params import CertificateListParams as CertificateListParams +from .spend_alert_list_params import SpendAlertListParams as SpendAlertListParams +from .usage_embeddings_params import UsageEmbeddingsParams as UsageEmbeddingsParams +from .organization_spend_alert import OrganizationSpendAlert as OrganizationSpendAlert +from .usage_completions_params import UsageCompletionsParams as UsageCompletionsParams +from .usage_moderations_params import UsageModerationsParams as UsageModerationsParams +from .admin_api_key_list_params import AdminAPIKeyListParams as AdminAPIKeyListParams +from .certificate_create_params import CertificateCreateParams as CertificateCreateParams +from .certificate_list_response import CertificateListResponse as CertificateListResponse +from .certificate_update_params import CertificateUpdateParams as CertificateUpdateParams +from .spend_alert_create_params import SpendAlertCreateParams as SpendAlertCreateParams +from .spend_alert_update_params import SpendAlertUpdateParams as SpendAlertUpdateParams +from .usage_embeddings_response import UsageEmbeddingsResponse as UsageEmbeddingsResponse +from .usage_completions_response import UsageCompletionsResponse as UsageCompletionsResponse +from .usage_moderations_response import UsageModerationsResponse as UsageModerationsResponse +from .usage_vector_stores_params import UsageVectorStoresParams as UsageVectorStoresParams +from .admin_api_key_create_params import AdminAPIKeyCreateParams as AdminAPIKeyCreateParams +from .certificate_activate_params import CertificateActivateParams as CertificateActivateParams +from .certificate_delete_response import CertificateDeleteResponse as CertificateDeleteResponse +from .certificate_retrieve_params import CertificateRetrieveParams as CertificateRetrieveParams +from .organization_data_retention import OrganizationDataRetention as OrganizationDataRetention +from .usage_audio_speeches_params import UsageAudioSpeechesParams as UsageAudioSpeechesParams +from .data_retention_update_params import DataRetentionUpdateParams as DataRetentionUpdateParams +from .usage_vector_stores_response import UsageVectorStoresResponse as UsageVectorStoresResponse +from .admin_api_key_create_response import AdminAPIKeyCreateResponse as AdminAPIKeyCreateResponse +from .admin_api_key_delete_response import AdminAPIKeyDeleteResponse as AdminAPIKeyDeleteResponse +from .certificate_activate_response import CertificateActivateResponse as CertificateActivateResponse +from .certificate_deactivate_params import CertificateDeactivateParams as CertificateDeactivateParams +from .usage_audio_speeches_response import UsageAudioSpeechesResponse as UsageAudioSpeechesResponse +from .usage_web_search_calls_params import UsageWebSearchCallsParams as UsageWebSearchCallsParams +from .usage_file_search_calls_params import UsageFileSearchCallsParams as UsageFileSearchCallsParams +from .certificate_deactivate_response import CertificateDeactivateResponse as CertificateDeactivateResponse +from .usage_web_search_calls_response import UsageWebSearchCallsResponse as UsageWebSearchCallsResponse +from .organization_spend_alert_deleted import OrganizationSpendAlertDeleted as OrganizationSpendAlertDeleted +from .usage_file_search_calls_response import UsageFileSearchCallsResponse as UsageFileSearchCallsResponse +from .usage_audio_transcriptions_params import UsageAudioTranscriptionsParams as UsageAudioTranscriptionsParams +from .usage_audio_transcriptions_response import UsageAudioTranscriptionsResponse as UsageAudioTranscriptionsResponse +from .usage_code_interpreter_sessions_params import ( + UsageCodeInterpreterSessionsParams as UsageCodeInterpreterSessionsParams, +) +from .usage_code_interpreter_sessions_response import ( + UsageCodeInterpreterSessionsResponse as UsageCodeInterpreterSessionsResponse, +) diff --git a/src/openai/types/admin/organization/admin_api_key.py b/src/openai/types/admin/organization/admin_api_key.py new file mode 100644 index 0000000000..5d2e5840f9 --- /dev/null +++ b/src/openai/types/admin/organization/admin_api_key.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["AdminAPIKey", "Owner"] + + +class Owner(BaseModel): + id: Optional[str] = None + """The identifier, which can be referenced in API endpoints""" + + created_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the user was created""" + + name: Optional[str] = None + """The name of the user""" + + object: Optional[str] = None + """The object type, which is always organization.user""" + + role: Optional[str] = None + """Always `owner`""" + + type: Optional[str] = None + """Always `user`""" + + +class AdminAPIKey(BaseModel): + """Represents an individual Admin API key in an org.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + created_at: int + """The Unix timestamp (in seconds) of when the API key was created""" + + object: Literal["organization.admin_api_key"] + """The object type, which is always `organization.admin_api_key`""" + + owner: Owner + + redacted_value: str + """The redacted value of the API key""" + + last_used_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the API key was last used""" + + name: Optional[str] = None + """The name of the API key""" diff --git a/src/openai/types/admin/organization/admin_api_key_create_params.py b/src/openai/types/admin/organization/admin_api_key_create_params.py new file mode 100644 index 0000000000..dccdfb8a75 --- /dev/null +++ b/src/openai/types/admin/organization/admin_api_key_create_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["AdminAPIKeyCreateParams"] + + +class AdminAPIKeyCreateParams(TypedDict, total=False): + name: Required[str] diff --git a/src/openai/types/admin/organization/admin_api_key_create_response.py b/src/openai/types/admin/organization/admin_api_key_create_response.py new file mode 100644 index 0000000000..58101a9e0a --- /dev/null +++ b/src/openai/types/admin/organization/admin_api_key_create_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .admin_api_key import AdminAPIKey + +__all__ = ["AdminAPIKeyCreateResponse"] + + +class AdminAPIKeyCreateResponse(AdminAPIKey): + """Represents an individual Admin API key in an org.""" + + value: str + """The value of the API key. Only shown on create.""" diff --git a/src/openai/types/admin/organization/admin_api_key_delete_response.py b/src/openai/types/admin/organization/admin_api_key_delete_response.py new file mode 100644 index 0000000000..df8c5171d4 --- /dev/null +++ b/src/openai/types/admin/organization/admin_api_key_delete_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["AdminAPIKeyDeleteResponse"] + + +class AdminAPIKeyDeleteResponse(BaseModel): + id: str + + deleted: bool + + object: Literal["organization.admin_api_key.deleted"] diff --git a/src/openai/types/admin/organization/admin_api_key_list_params.py b/src/openai/types/admin/organization/admin_api_key_list_params.py new file mode 100644 index 0000000000..c3b3f51008 --- /dev/null +++ b/src/openai/types/admin/organization/admin_api_key_list_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +__all__ = ["AdminAPIKeyListParams"] + + +class AdminAPIKeyListParams(TypedDict, total=False): + after: Optional[str] + """Return keys with IDs that come after this ID in the pagination order.""" + + limit: int + """Maximum number of keys to return.""" + + order: Literal["asc", "desc"] + """Order results by creation time, ascending or descending.""" diff --git a/src/openai/types/admin/organization/audit_log_list_params.py b/src/openai/types/admin/organization/audit_log_list_params.py new file mode 100644 index 0000000000..25224d47dc --- /dev/null +++ b/src/openai/types/admin/organization/audit_log_list_params.py @@ -0,0 +1,149 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["AuditLogListParams", "EffectiveAt"] + + +class AuditLogListParams(TypedDict, total=False): + actor_emails: SequenceNotStr[str] + """Return only events performed by users with these emails.""" + + actor_ids: SequenceNotStr[str] + """Return only events performed by these actors. + + Can be a user ID, a service account ID, or an api key tracking ID. + """ + + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + before: str + """A cursor for use in pagination. + + `before` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, starting with obj_foo, your + subsequent call can include before=obj_foo in order to fetch the previous page + of the list. + """ + + effective_at: EffectiveAt + """Return only events whose `effective_at` (Unix seconds) is in this range.""" + + event_types: List[ + Literal[ + "api_key.created", + "api_key.updated", + "api_key.deleted", + "certificate.created", + "certificate.updated", + "certificate.deleted", + "certificates.activated", + "certificates.deactivated", + "checkpoint.permission.created", + "checkpoint.permission.deleted", + "external_key.registered", + "external_key.removed", + "group.created", + "group.updated", + "group.deleted", + "invite.sent", + "invite.accepted", + "invite.deleted", + "ip_allowlist.created", + "ip_allowlist.updated", + "ip_allowlist.deleted", + "ip_allowlist.config.activated", + "ip_allowlist.config.deactivated", + "login.succeeded", + "login.failed", + "logout.succeeded", + "logout.failed", + "organization.updated", + "project.created", + "project.updated", + "project.archived", + "project.deleted", + "rate_limit.updated", + "rate_limit.deleted", + "resource.deleted", + "tunnel.created", + "tunnel.updated", + "tunnel.deleted", + "workload_identity_provider.created", + "workload_identity_provider.updated", + "workload_identity_provider.deleted", + "workload_identity_provider_mapping.created", + "workload_identity_provider_mapping.updated", + "workload_identity_provider_mapping.deleted", + "role.created", + "role.updated", + "role.deleted", + "role.assignment.created", + "role.assignment.deleted", + "scim.enabled", + "scim.disabled", + "service_account.created", + "service_account.updated", + "service_account.deleted", + "user.added", + "user.updated", + "user.deleted", + ] + ] + """Return only events with a `type` in one of these values. + + For example, `project.created`. For all options, see the documentation for the + [audit log object](https://platform.openai.com/docs/api-reference/audit-logs/object). + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ + + project_ids: SequenceNotStr[str] + """Return only events for these projects.""" + + resource_ids: SequenceNotStr[str] + """Return only events performed on these targets. + + For example, a project ID updated. + """ + + +class EffectiveAt(TypedDict, total=False): + """Return only events whose `effective_at` (Unix seconds) is in this range.""" + + gt: int + """ + Return only events whose `effective_at` (Unix seconds) is greater than this + value. + """ + + gte: int + """ + Return only events whose `effective_at` (Unix seconds) is greater than or equal + to this value. + """ + + lt: int + """Return only events whose `effective_at` (Unix seconds) is less than this value.""" + + lte: int + """ + Return only events whose `effective_at` (Unix seconds) is less than or equal to + this value. + """ diff --git a/src/openai/types/admin/organization/audit_log_list_response.py b/src/openai/types/admin/organization/audit_log_list_response.py new file mode 100644 index 0000000000..9fa99bd677 --- /dev/null +++ b/src/openai/types/admin/organization/audit_log_list_response.py @@ -0,0 +1,1147 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ...._models import BaseModel + +__all__ = [ + "AuditLogListResponse", + "Actor", + "ActorAPIKey", + "ActorAPIKeyServiceAccount", + "ActorAPIKeyUser", + "ActorSession", + "ActorSessionUser", + "APIKeyCreated", + "APIKeyCreatedData", + "APIKeyDeleted", + "APIKeyUpdated", + "APIKeyUpdatedChangesRequested", + "CertificateCreated", + "CertificateDeleted", + "CertificateUpdated", + "CertificatesActivated", + "CertificatesActivatedCertificate", + "CertificatesDeactivated", + "CertificatesDeactivatedCertificate", + "CheckpointPermissionCreated", + "CheckpointPermissionCreatedData", + "CheckpointPermissionDeleted", + "ExternalKeyRegistered", + "ExternalKeyRemoved", + "GroupCreated", + "GroupCreatedData", + "GroupDeleted", + "GroupUpdated", + "GroupUpdatedChangesRequested", + "InviteAccepted", + "InviteDeleted", + "InviteSent", + "InviteSentData", + "IPAllowlistConfigActivated", + "IPAllowlistConfigActivatedConfig", + "IPAllowlistConfigDeactivated", + "IPAllowlistConfigDeactivatedConfig", + "IPAllowlistCreated", + "IPAllowlistDeleted", + "IPAllowlistUpdated", + "LoginFailed", + "LogoutFailed", + "OrganizationUpdated", + "OrganizationUpdatedChangesRequested", + "Project", + "ProjectArchived", + "ProjectCreated", + "ProjectCreatedData", + "ProjectDeleted", + "ProjectUpdated", + "ProjectUpdatedChangesRequested", + "RateLimitDeleted", + "RateLimitUpdated", + "RateLimitUpdatedChangesRequested", + "RoleAssignmentCreated", + "RoleAssignmentDeleted", + "RoleCreated", + "RoleDeleted", + "RoleUpdated", + "RoleUpdatedChangesRequested", + "ScimDisabled", + "ScimEnabled", + "ServiceAccountCreated", + "ServiceAccountCreatedData", + "ServiceAccountDeleted", + "ServiceAccountUpdated", + "ServiceAccountUpdatedChangesRequested", + "UserAdded", + "UserAddedData", + "UserDeleted", + "UserUpdated", + "UserUpdatedChangesRequested", + "WorkloadIdentityProviderMappingCreated", + "WorkloadIdentityProviderMappingDeleted", + "WorkloadIdentityProviderMappingUpdated", + "WorkloadIdentityProviderCreated", + "WorkloadIdentityProviderDeleted", + "WorkloadIdentityProviderUpdated", +] + + +class ActorAPIKeyServiceAccount(BaseModel): + """The service account that performed the audit logged action.""" + + id: Optional[str] = None + """The service account id.""" + + +class ActorAPIKeyUser(BaseModel): + """The user who performed the audit logged action.""" + + id: Optional[str] = None + """The user id.""" + + email: Optional[str] = None + """The user email.""" + + +class ActorAPIKey(BaseModel): + """The API Key used to perform the audit logged action.""" + + id: Optional[str] = None + """The tracking id of the API key.""" + + service_account: Optional[ActorAPIKeyServiceAccount] = None + """The service account that performed the audit logged action.""" + + type: Optional[Literal["user", "service_account"]] = None + """The type of API key. Can be either `user` or `service_account`.""" + + user: Optional[ActorAPIKeyUser] = None + """The user who performed the audit logged action.""" + + +class ActorSessionUser(BaseModel): + """The user who performed the audit logged action.""" + + id: Optional[str] = None + """The user id.""" + + email: Optional[str] = None + """The user email.""" + + +class ActorSession(BaseModel): + """The session in which the audit logged action was performed.""" + + ip_address: Optional[str] = None + """The IP address from which the action was performed.""" + + user: Optional[ActorSessionUser] = None + """The user who performed the audit logged action.""" + + +class Actor(BaseModel): + """The actor who performed the audit logged action.""" + + api_key: Optional[ActorAPIKey] = None + """The API Key used to perform the audit logged action.""" + + session: Optional[ActorSession] = None + """The session in which the audit logged action was performed.""" + + type: Optional[Literal["session", "api_key"]] = None + """The type of actor. Is either `session` or `api_key`.""" + + +class APIKeyCreatedData(BaseModel): + """The payload used to create the API key.""" + + scopes: Optional[List[str]] = None + """A list of scopes allowed for the API key, e.g. `["api.model.request"]`""" + + +class APIKeyCreated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The tracking ID of the API key.""" + + data: Optional[APIKeyCreatedData] = None + """The payload used to create the API key.""" + + +class APIKeyDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The tracking ID of the API key.""" + + +class APIKeyUpdatedChangesRequested(BaseModel): + """The payload used to update the API key.""" + + scopes: Optional[List[str]] = None + """A list of scopes allowed for the API key, e.g. `["api.model.request"]`""" + + +class APIKeyUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The tracking ID of the API key.""" + + changes_requested: Optional[APIKeyUpdatedChangesRequested] = None + """The payload used to update the API key.""" + + +class CertificateCreated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The certificate ID.""" + + name: Optional[str] = None + """The name of the certificate.""" + + +class CertificateDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The certificate ID.""" + + certificate: Optional[str] = None + """The certificate content in PEM format.""" + + name: Optional[str] = None + """The name of the certificate.""" + + +class CertificateUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The certificate ID.""" + + name: Optional[str] = None + """The name of the certificate.""" + + +class CertificatesActivatedCertificate(BaseModel): + id: Optional[str] = None + """The certificate ID.""" + + name: Optional[str] = None + """The name of the certificate.""" + + +class CertificatesActivated(BaseModel): + """The details for events with this `type`.""" + + certificates: Optional[List[CertificatesActivatedCertificate]] = None + + +class CertificatesDeactivatedCertificate(BaseModel): + id: Optional[str] = None + """The certificate ID.""" + + name: Optional[str] = None + """The name of the certificate.""" + + +class CertificatesDeactivated(BaseModel): + """The details for events with this `type`.""" + + certificates: Optional[List[CertificatesDeactivatedCertificate]] = None + + +class CheckpointPermissionCreatedData(BaseModel): + """The payload used to create the checkpoint permission.""" + + fine_tuned_model_checkpoint: Optional[str] = None + """The ID of the fine-tuned model checkpoint.""" + + project_id: Optional[str] = None + """The ID of the project that the checkpoint permission was created for.""" + + +class CheckpointPermissionCreated(BaseModel): + """ + The project and fine-tuned model checkpoint that the checkpoint permission was created for. + """ + + id: Optional[str] = None + """The ID of the checkpoint permission.""" + + data: Optional[CheckpointPermissionCreatedData] = None + """The payload used to create the checkpoint permission.""" + + +class CheckpointPermissionDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the checkpoint permission.""" + + +class ExternalKeyRegistered(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the external key configuration.""" + + data: Optional[object] = None + """The configuration for the external key.""" + + +class ExternalKeyRemoved(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the external key configuration.""" + + +class GroupCreatedData(BaseModel): + """Information about the created group.""" + + group_name: Optional[str] = None + """The group name.""" + + +class GroupCreated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the group.""" + + data: Optional[GroupCreatedData] = None + """Information about the created group.""" + + +class GroupDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the group.""" + + +class GroupUpdatedChangesRequested(BaseModel): + """The payload used to update the group.""" + + group_name: Optional[str] = None + """The updated group name.""" + + +class GroupUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the group.""" + + changes_requested: Optional[GroupUpdatedChangesRequested] = None + """The payload used to update the group.""" + + +class InviteAccepted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the invite.""" + + +class InviteDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the invite.""" + + +class InviteSentData(BaseModel): + """The payload used to create the invite.""" + + email: Optional[str] = None + """The email invited to the organization.""" + + role: Optional[str] = None + """The role the email was invited to be. Is either `owner` or `member`.""" + + +class InviteSent(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the invite.""" + + data: Optional[InviteSentData] = None + """The payload used to create the invite.""" + + +class IPAllowlistConfigActivatedConfig(BaseModel): + id: Optional[str] = None + """The ID of the IP allowlist configuration.""" + + name: Optional[str] = None + """The name of the IP allowlist configuration.""" + + +class IPAllowlistConfigActivated(BaseModel): + """The details for events with this `type`.""" + + configs: Optional[List[IPAllowlistConfigActivatedConfig]] = None + """The configurations that were activated.""" + + +class IPAllowlistConfigDeactivatedConfig(BaseModel): + id: Optional[str] = None + """The ID of the IP allowlist configuration.""" + + name: Optional[str] = None + """The name of the IP allowlist configuration.""" + + +class IPAllowlistConfigDeactivated(BaseModel): + """The details for events with this `type`.""" + + configs: Optional[List[IPAllowlistConfigDeactivatedConfig]] = None + """The configurations that were deactivated.""" + + +class IPAllowlistCreated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the IP allowlist configuration.""" + + allowed_ips: Optional[List[str]] = None + """The IP addresses or CIDR ranges included in the configuration.""" + + name: Optional[str] = None + """The name of the IP allowlist configuration.""" + + +class IPAllowlistDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the IP allowlist configuration.""" + + allowed_ips: Optional[List[str]] = None + """The IP addresses or CIDR ranges that were in the configuration.""" + + name: Optional[str] = None + """The name of the IP allowlist configuration.""" + + +class IPAllowlistUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the IP allowlist configuration.""" + + allowed_ips: Optional[List[str]] = None + """The updated set of IP addresses or CIDR ranges in the configuration.""" + + +class LoginFailed(BaseModel): + """The details for events with this `type`.""" + + error_code: Optional[str] = None + """The error code of the failure.""" + + error_message: Optional[str] = None + """The error message of the failure.""" + + +class LogoutFailed(BaseModel): + """The details for events with this `type`.""" + + error_code: Optional[str] = None + """The error code of the failure.""" + + error_message: Optional[str] = None + """The error message of the failure.""" + + +class OrganizationUpdatedChangesRequested(BaseModel): + """The payload used to update the organization settings.""" + + api_call_logging: Optional[str] = None + """How your organization logs data from supported API calls. + + One of `disabled`, `enabled_per_call`, `enabled_for_all_projects`, or + `enabled_for_selected_projects` + """ + + api_call_logging_project_ids: Optional[str] = None + """ + The list of project ids if api_call_logging is set to + `enabled_for_selected_projects` + """ + + description: Optional[str] = None + """The organization description.""" + + name: Optional[str] = None + """The organization name.""" + + threads_ui_visibility: Optional[str] = None + """ + Visibility of the threads page which shows messages created with the Assistants + API and Playground. One of `ANY_ROLE`, `OWNERS`, or `NONE`. + """ + + title: Optional[str] = None + """The organization title.""" + + usage_dashboard_visibility: Optional[str] = None + """ + Visibility of the usage dashboard which shows activity and costs for your + organization. One of `ANY_ROLE` or `OWNERS`. + """ + + +class OrganizationUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The organization ID.""" + + changes_requested: Optional[OrganizationUpdatedChangesRequested] = None + """The payload used to update the organization settings.""" + + +class Project(BaseModel): + """The project that the action was scoped to. + + Absent for actions not scoped to projects. Note that any admin actions taken via Admin API keys are associated with the default project. + """ + + id: Optional[str] = None + """The project ID.""" + + name: Optional[str] = None + """The project title.""" + + +class ProjectArchived(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The project ID.""" + + +class ProjectCreatedData(BaseModel): + """The payload used to create the project.""" + + name: Optional[str] = None + """The project name.""" + + title: Optional[str] = None + """The title of the project as seen on the dashboard.""" + + +class ProjectCreated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The project ID.""" + + data: Optional[ProjectCreatedData] = None + """The payload used to create the project.""" + + +class ProjectDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The project ID.""" + + +class ProjectUpdatedChangesRequested(BaseModel): + """The payload used to update the project.""" + + title: Optional[str] = None + """The title of the project as seen on the dashboard.""" + + +class ProjectUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The project ID.""" + + changes_requested: Optional[ProjectUpdatedChangesRequested] = None + """The payload used to update the project.""" + + +class RateLimitDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The rate limit ID""" + + +class RateLimitUpdatedChangesRequested(BaseModel): + """The payload used to update the rate limits.""" + + batch_1_day_max_input_tokens: Optional[int] = None + """The maximum batch input tokens per day. Only relevant for certain models.""" + + max_audio_megabytes_per_1_minute: Optional[int] = None + """The maximum audio megabytes per minute. Only relevant for certain models.""" + + max_images_per_1_minute: Optional[int] = None + """The maximum images per minute. Only relevant for certain models.""" + + max_requests_per_1_day: Optional[int] = None + """The maximum requests per day. Only relevant for certain models.""" + + max_requests_per_1_minute: Optional[int] = None + """The maximum requests per minute.""" + + max_tokens_per_1_minute: Optional[int] = None + """The maximum tokens per minute.""" + + +class RateLimitUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The rate limit ID""" + + changes_requested: Optional[RateLimitUpdatedChangesRequested] = None + """The payload used to update the rate limits.""" + + +class RoleAssignmentCreated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The identifier of the role assignment.""" + + principal_id: Optional[str] = None + """The principal (user or group) that received the role.""" + + principal_type: Optional[str] = None + """The type of principal (user or group) that received the role.""" + + resource_id: Optional[str] = None + """The resource the role assignment is scoped to.""" + + resource_type: Optional[str] = None + """The type of resource the role assignment is scoped to.""" + + +class RoleAssignmentDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The identifier of the role assignment.""" + + principal_id: Optional[str] = None + """The principal (user or group) that had the role removed.""" + + principal_type: Optional[str] = None + """The type of principal (user or group) that had the role removed.""" + + resource_id: Optional[str] = None + """The resource the role assignment was scoped to.""" + + resource_type: Optional[str] = None + """The type of resource the role assignment was scoped to.""" + + +class RoleCreated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The role ID.""" + + permissions: Optional[List[str]] = None + """The permissions granted by the role.""" + + resource_id: Optional[str] = None + """The resource the role is scoped to.""" + + resource_type: Optional[str] = None + """The type of resource the role belongs to.""" + + role_name: Optional[str] = None + """The name of the role.""" + + +class RoleDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The role ID.""" + + +class RoleUpdatedChangesRequested(BaseModel): + """The payload used to update the role.""" + + description: Optional[str] = None + """The updated role description, when provided.""" + + metadata: Optional[object] = None + """Additional metadata stored on the role.""" + + permissions_added: Optional[List[str]] = None + """The permissions added to the role.""" + + permissions_removed: Optional[List[str]] = None + """The permissions removed from the role.""" + + resource_id: Optional[str] = None + """The resource the role is scoped to.""" + + resource_type: Optional[str] = None + """The type of resource the role belongs to.""" + + role_name: Optional[str] = None + """The updated role name, when provided.""" + + +class RoleUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The role ID.""" + + changes_requested: Optional[RoleUpdatedChangesRequested] = None + """The payload used to update the role.""" + + +class ScimDisabled(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the SCIM was disabled for.""" + + +class ScimEnabled(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The ID of the SCIM was enabled for.""" + + +class ServiceAccountCreatedData(BaseModel): + """The payload used to create the service account.""" + + role: Optional[str] = None + """The role of the service account. Is either `owner` or `member`.""" + + +class ServiceAccountCreated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The service account ID.""" + + data: Optional[ServiceAccountCreatedData] = None + """The payload used to create the service account.""" + + +class ServiceAccountDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The service account ID.""" + + +class ServiceAccountUpdatedChangesRequested(BaseModel): + """The payload used to updated the service account.""" + + role: Optional[str] = None + """The role of the service account. Is either `owner` or `member`.""" + + +class ServiceAccountUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The service account ID.""" + + changes_requested: Optional[ServiceAccountUpdatedChangesRequested] = None + """The payload used to updated the service account.""" + + +class UserAddedData(BaseModel): + """The payload used to add the user to the project.""" + + role: Optional[str] = None + """The role of the user. Is either `owner` or `member`.""" + + +class UserAdded(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The user ID.""" + + data: Optional[UserAddedData] = None + """The payload used to add the user to the project.""" + + +class UserDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The user ID.""" + + +class UserUpdatedChangesRequested(BaseModel): + """The payload used to update the user.""" + + role: Optional[str] = None + """The role of the user. Is either `owner` or `member`.""" + + +class UserUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The project ID.""" + + changes_requested: Optional[UserUpdatedChangesRequested] = None + """The payload used to update the user.""" + + +class WorkloadIdentityProviderMappingCreated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The workload identity provider mapping ID.""" + + data: Optional[object] = None + """The payload used to create the workload identity provider mapping.""" + + identity_provider_id: Optional[str] = None + """The workload identity provider ID.""" + + +class WorkloadIdentityProviderMappingDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The workload identity provider mapping ID.""" + + identity_provider_id: Optional[str] = None + """The workload identity provider ID.""" + + project_id: Optional[str] = None + """The project ID.""" + + service_account_id: Optional[str] = None + """The mapped service account ID.""" + + +class WorkloadIdentityProviderMappingUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The workload identity provider mapping ID.""" + + changes_requested: Optional[object] = None + """The payload used to update the workload identity provider mapping.""" + + identity_provider_id: Optional[str] = None + """The workload identity provider ID.""" + + +class WorkloadIdentityProviderCreated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The workload identity provider ID.""" + + data: Optional[object] = None + """The payload used to create the workload identity provider.""" + + +class WorkloadIdentityProviderDeleted(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The workload identity provider ID.""" + + name: Optional[str] = None + """The workload identity provider name.""" + + +class WorkloadIdentityProviderUpdated(BaseModel): + """The details for events with this `type`.""" + + id: Optional[str] = None + """The workload identity provider ID.""" + + changes_requested: Optional[object] = None + """The payload used to update the workload identity provider.""" + + +class AuditLogListResponse(BaseModel): + """A log of a user action or configuration change within this organization.""" + + id: str + """The ID of this log.""" + + effective_at: int + """The Unix timestamp (in seconds) of the event.""" + + type: Literal[ + "api_key.created", + "api_key.updated", + "api_key.deleted", + "certificate.created", + "certificate.updated", + "certificate.deleted", + "certificates.activated", + "certificates.deactivated", + "checkpoint.permission.created", + "checkpoint.permission.deleted", + "external_key.registered", + "external_key.removed", + "group.created", + "group.updated", + "group.deleted", + "invite.sent", + "invite.accepted", + "invite.deleted", + "ip_allowlist.created", + "ip_allowlist.updated", + "ip_allowlist.deleted", + "ip_allowlist.config.activated", + "ip_allowlist.config.deactivated", + "login.succeeded", + "login.failed", + "logout.succeeded", + "logout.failed", + "organization.updated", + "project.created", + "project.updated", + "project.archived", + "project.deleted", + "rate_limit.updated", + "rate_limit.deleted", + "resource.deleted", + "tunnel.created", + "tunnel.updated", + "tunnel.deleted", + "workload_identity_provider.created", + "workload_identity_provider.updated", + "workload_identity_provider.deleted", + "workload_identity_provider_mapping.created", + "workload_identity_provider_mapping.updated", + "workload_identity_provider_mapping.deleted", + "role.created", + "role.updated", + "role.deleted", + "role.assignment.created", + "role.assignment.deleted", + "scim.enabled", + "scim.disabled", + "service_account.created", + "service_account.updated", + "service_account.deleted", + "user.added", + "user.updated", + "user.deleted", + ] + """The event type.""" + + actor: Optional[Actor] = None + """The actor who performed the audit logged action.""" + + api_key_created: Optional[APIKeyCreated] = FieldInfo(alias="api_key.created", default=None) + """The details for events with this `type`.""" + + api_key_deleted: Optional[APIKeyDeleted] = FieldInfo(alias="api_key.deleted", default=None) + """The details for events with this `type`.""" + + api_key_updated: Optional[APIKeyUpdated] = FieldInfo(alias="api_key.updated", default=None) + """The details for events with this `type`.""" + + certificate_created: Optional[CertificateCreated] = FieldInfo(alias="certificate.created", default=None) + """The details for events with this `type`.""" + + certificate_deleted: Optional[CertificateDeleted] = FieldInfo(alias="certificate.deleted", default=None) + """The details for events with this `type`.""" + + certificate_updated: Optional[CertificateUpdated] = FieldInfo(alias="certificate.updated", default=None) + """The details for events with this `type`.""" + + certificates_activated: Optional[CertificatesActivated] = FieldInfo(alias="certificates.activated", default=None) + """The details for events with this `type`.""" + + certificates_deactivated: Optional[CertificatesDeactivated] = FieldInfo( + alias="certificates.deactivated", default=None + ) + """The details for events with this `type`.""" + + checkpoint_permission_created: Optional[CheckpointPermissionCreated] = FieldInfo( + alias="checkpoint.permission.created", default=None + ) + """ + The project and fine-tuned model checkpoint that the checkpoint permission was + created for. + """ + + checkpoint_permission_deleted: Optional[CheckpointPermissionDeleted] = FieldInfo( + alias="checkpoint.permission.deleted", default=None + ) + """The details for events with this `type`.""" + + external_key_registered: Optional[ExternalKeyRegistered] = FieldInfo(alias="external_key.registered", default=None) + """The details for events with this `type`.""" + + external_key_removed: Optional[ExternalKeyRemoved] = FieldInfo(alias="external_key.removed", default=None) + """The details for events with this `type`.""" + + group_created: Optional[GroupCreated] = FieldInfo(alias="group.created", default=None) + """The details for events with this `type`.""" + + group_deleted: Optional[GroupDeleted] = FieldInfo(alias="group.deleted", default=None) + """The details for events with this `type`.""" + + group_updated: Optional[GroupUpdated] = FieldInfo(alias="group.updated", default=None) + """The details for events with this `type`.""" + + invite_accepted: Optional[InviteAccepted] = FieldInfo(alias="invite.accepted", default=None) + """The details for events with this `type`.""" + + invite_deleted: Optional[InviteDeleted] = FieldInfo(alias="invite.deleted", default=None) + """The details for events with this `type`.""" + + invite_sent: Optional[InviteSent] = FieldInfo(alias="invite.sent", default=None) + """The details for events with this `type`.""" + + ip_allowlist_config_activated: Optional[IPAllowlistConfigActivated] = FieldInfo( + alias="ip_allowlist.config.activated", default=None + ) + """The details for events with this `type`.""" + + ip_allowlist_config_deactivated: Optional[IPAllowlistConfigDeactivated] = FieldInfo( + alias="ip_allowlist.config.deactivated", default=None + ) + """The details for events with this `type`.""" + + ip_allowlist_created: Optional[IPAllowlistCreated] = FieldInfo(alias="ip_allowlist.created", default=None) + """The details for events with this `type`.""" + + ip_allowlist_deleted: Optional[IPAllowlistDeleted] = FieldInfo(alias="ip_allowlist.deleted", default=None) + """The details for events with this `type`.""" + + ip_allowlist_updated: Optional[IPAllowlistUpdated] = FieldInfo(alias="ip_allowlist.updated", default=None) + """The details for events with this `type`.""" + + login_failed: Optional[LoginFailed] = FieldInfo(alias="login.failed", default=None) + """The details for events with this `type`.""" + + login_succeeded: Optional[object] = FieldInfo(alias="login.succeeded", default=None) + """This event has no additional fields beyond the standard audit log attributes.""" + + logout_failed: Optional[LogoutFailed] = FieldInfo(alias="logout.failed", default=None) + """The details for events with this `type`.""" + + logout_succeeded: Optional[object] = FieldInfo(alias="logout.succeeded", default=None) + """This event has no additional fields beyond the standard audit log attributes.""" + + organization_updated: Optional[OrganizationUpdated] = FieldInfo(alias="organization.updated", default=None) + """The details for events with this `type`.""" + + project: Optional[Project] = None + """The project that the action was scoped to. + + Absent for actions not scoped to projects. Note that any admin actions taken via + Admin API keys are associated with the default project. + """ + + project_archived: Optional[ProjectArchived] = FieldInfo(alias="project.archived", default=None) + """The details for events with this `type`.""" + + project_created: Optional[ProjectCreated] = FieldInfo(alias="project.created", default=None) + """The details for events with this `type`.""" + + project_deleted: Optional[ProjectDeleted] = FieldInfo(alias="project.deleted", default=None) + """The details for events with this `type`.""" + + project_updated: Optional[ProjectUpdated] = FieldInfo(alias="project.updated", default=None) + """The details for events with this `type`.""" + + rate_limit_deleted: Optional[RateLimitDeleted] = FieldInfo(alias="rate_limit.deleted", default=None) + """The details for events with this `type`.""" + + rate_limit_updated: Optional[RateLimitUpdated] = FieldInfo(alias="rate_limit.updated", default=None) + """The details for events with this `type`.""" + + role_assignment_created: Optional[RoleAssignmentCreated] = FieldInfo(alias="role.assignment.created", default=None) + """The details for events with this `type`.""" + + role_assignment_deleted: Optional[RoleAssignmentDeleted] = FieldInfo(alias="role.assignment.deleted", default=None) + """The details for events with this `type`.""" + + role_created: Optional[RoleCreated] = FieldInfo(alias="role.created", default=None) + """The details for events with this `type`.""" + + role_deleted: Optional[RoleDeleted] = FieldInfo(alias="role.deleted", default=None) + """The details for events with this `type`.""" + + role_updated: Optional[RoleUpdated] = FieldInfo(alias="role.updated", default=None) + """The details for events with this `type`.""" + + scim_disabled: Optional[ScimDisabled] = FieldInfo(alias="scim.disabled", default=None) + """The details for events with this `type`.""" + + scim_enabled: Optional[ScimEnabled] = FieldInfo(alias="scim.enabled", default=None) + """The details for events with this `type`.""" + + service_account_created: Optional[ServiceAccountCreated] = FieldInfo(alias="service_account.created", default=None) + """The details for events with this `type`.""" + + service_account_deleted: Optional[ServiceAccountDeleted] = FieldInfo(alias="service_account.deleted", default=None) + """The details for events with this `type`.""" + + service_account_updated: Optional[ServiceAccountUpdated] = FieldInfo(alias="service_account.updated", default=None) + """The details for events with this `type`.""" + + user_added: Optional[UserAdded] = FieldInfo(alias="user.added", default=None) + """The details for events with this `type`.""" + + user_deleted: Optional[UserDeleted] = FieldInfo(alias="user.deleted", default=None) + """The details for events with this `type`.""" + + user_updated: Optional[UserUpdated] = FieldInfo(alias="user.updated", default=None) + """The details for events with this `type`.""" + + workload_identity_provider_mapping_created: Optional[WorkloadIdentityProviderMappingCreated] = FieldInfo( + alias="workload_identity_provider_mapping.created", default=None + ) + """The details for events with this `type`.""" + + workload_identity_provider_mapping_deleted: Optional[WorkloadIdentityProviderMappingDeleted] = FieldInfo( + alias="workload_identity_provider_mapping.deleted", default=None + ) + """The details for events with this `type`.""" + + workload_identity_provider_mapping_updated: Optional[WorkloadIdentityProviderMappingUpdated] = FieldInfo( + alias="workload_identity_provider_mapping.updated", default=None + ) + """The details for events with this `type`.""" + + workload_identity_provider_created: Optional[WorkloadIdentityProviderCreated] = FieldInfo( + alias="workload_identity_provider.created", default=None + ) + """The details for events with this `type`.""" + + workload_identity_provider_deleted: Optional[WorkloadIdentityProviderDeleted] = FieldInfo( + alias="workload_identity_provider.deleted", default=None + ) + """The details for events with this `type`.""" + + workload_identity_provider_updated: Optional[WorkloadIdentityProviderUpdated] = FieldInfo( + alias="workload_identity_provider.updated", default=None + ) + """The details for events with this `type`.""" diff --git a/src/openai/types/admin/organization/certificate.py b/src/openai/types/admin/organization/certificate.py new file mode 100644 index 0000000000..a8663b4e4a --- /dev/null +++ b/src/openai/types/admin/organization/certificate.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["Certificate", "CertificateDetails"] + + +class CertificateDetails(BaseModel): + content: Optional[str] = None + """The content of the certificate in PEM format.""" + + expires_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate expires.""" + + valid_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate becomes valid.""" + + +class Certificate(BaseModel): + """Represents an individual `certificate` uploaded to the organization.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + certificate_details: CertificateDetails + + created_at: int + """The Unix timestamp (in seconds) of when the certificate was uploaded.""" + + name: Optional[str] = None + """The name of the certificate.""" + + object: Literal["certificate", "organization.certificate", "organization.project.certificate"] + """The object type. + + - If creating, updating, or getting a specific certificate, the object type is + `certificate`. + - If listing, activating, or deactivating certificates for the organization, the + object type is `organization.certificate`. + - If listing, activating, or deactivating certificates for a project, the object + type is `organization.project.certificate`. + """ + + active: Optional[bool] = None + """Whether the certificate is currently active at the specified scope. + + Not returned when getting details for a specific certificate. + """ diff --git a/src/openai/types/admin/organization/certificate_activate_params.py b/src/openai/types/admin/organization/certificate_activate_params.py new file mode 100644 index 0000000000..0bb6474c7c --- /dev/null +++ b/src/openai/types/admin/organization/certificate_activate_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["CertificateActivateParams"] + + +class CertificateActivateParams(TypedDict, total=False): + certificate_ids: Required[SequenceNotStr[str]] diff --git a/src/openai/types/admin/organization/certificate_activate_response.py b/src/openai/types/admin/organization/certificate_activate_response.py new file mode 100644 index 0000000000..64239c3a93 --- /dev/null +++ b/src/openai/types/admin/organization/certificate_activate_response.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["CertificateActivateResponse", "CertificateDetails"] + + +class CertificateDetails(BaseModel): + expires_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate expires.""" + + valid_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate becomes valid.""" + + +class CertificateActivateResponse(BaseModel): + """Represents an individual certificate configured at the organization level.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + active: bool + """Whether the certificate is currently active at the organization level.""" + + certificate_details: CertificateDetails + + created_at: int + """The Unix timestamp (in seconds) of when the certificate was uploaded.""" + + name: Optional[str] = None + """The name of the certificate.""" + + object: Literal["organization.certificate"] + """The object type, which is always `organization.certificate`.""" diff --git a/src/openai/types/admin/organization/certificate_create_params.py b/src/openai/types/admin/organization/certificate_create_params.py new file mode 100644 index 0000000000..9aeb3bbc94 --- /dev/null +++ b/src/openai/types/admin/organization/certificate_create_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["CertificateCreateParams"] + + +class CertificateCreateParams(TypedDict, total=False): + certificate: Required[str] + """The certificate content in PEM format""" + + name: str + """An optional name for the certificate""" diff --git a/src/openai/types/admin/organization/certificate_deactivate_params.py b/src/openai/types/admin/organization/certificate_deactivate_params.py new file mode 100644 index 0000000000..827af54d35 --- /dev/null +++ b/src/openai/types/admin/organization/certificate_deactivate_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["CertificateDeactivateParams"] + + +class CertificateDeactivateParams(TypedDict, total=False): + certificate_ids: Required[SequenceNotStr[str]] diff --git a/src/openai/types/admin/organization/certificate_deactivate_response.py b/src/openai/types/admin/organization/certificate_deactivate_response.py new file mode 100644 index 0000000000..874251cadc --- /dev/null +++ b/src/openai/types/admin/organization/certificate_deactivate_response.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["CertificateDeactivateResponse", "CertificateDetails"] + + +class CertificateDetails(BaseModel): + expires_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate expires.""" + + valid_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate becomes valid.""" + + +class CertificateDeactivateResponse(BaseModel): + """Represents an individual certificate configured at the organization level.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + active: bool + """Whether the certificate is currently active at the organization level.""" + + certificate_details: CertificateDetails + + created_at: int + """The Unix timestamp (in seconds) of when the certificate was uploaded.""" + + name: Optional[str] = None + """The name of the certificate.""" + + object: Literal["organization.certificate"] + """The object type, which is always `organization.certificate`.""" diff --git a/src/openai/types/admin/organization/certificate_delete_response.py b/src/openai/types/admin/organization/certificate_delete_response.py new file mode 100644 index 0000000000..b0dc1fa018 --- /dev/null +++ b/src/openai/types/admin/organization/certificate_delete_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["CertificateDeleteResponse"] + + +class CertificateDeleteResponse(BaseModel): + id: str + """The ID of the certificate that was deleted.""" + + object: Literal["certificate.deleted"] + """The object type, must be `certificate.deleted`.""" diff --git a/src/openai/types/admin/organization/certificate_list_params.py b/src/openai/types/admin/organization/certificate_list_params.py new file mode 100644 index 0000000000..1d9aff4b4a --- /dev/null +++ b/src/openai/types/admin/organization/certificate_list_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["CertificateListParams"] + + +class CertificateListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ + + order: Literal["asc", "desc"] + """Sort order by the `created_at` timestamp of the objects. + + `asc` for ascending order and `desc` for descending order. + """ diff --git a/src/openai/types/admin/organization/certificate_list_response.py b/src/openai/types/admin/organization/certificate_list_response.py new file mode 100644 index 0000000000..8d9816520f --- /dev/null +++ b/src/openai/types/admin/organization/certificate_list_response.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["CertificateListResponse", "CertificateDetails"] + + +class CertificateDetails(BaseModel): + expires_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate expires.""" + + valid_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate becomes valid.""" + + +class CertificateListResponse(BaseModel): + """Represents an individual certificate configured at the organization level.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + active: bool + """Whether the certificate is currently active at the organization level.""" + + certificate_details: CertificateDetails + + created_at: int + """The Unix timestamp (in seconds) of when the certificate was uploaded.""" + + name: Optional[str] = None + """The name of the certificate.""" + + object: Literal["organization.certificate"] + """The object type, which is always `organization.certificate`.""" diff --git a/src/openai/types/admin/organization/certificate_retrieve_params.py b/src/openai/types/admin/organization/certificate_retrieve_params.py new file mode 100644 index 0000000000..29bc8dedc5 --- /dev/null +++ b/src/openai/types/admin/organization/certificate_retrieve_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, TypedDict + +__all__ = ["CertificateRetrieveParams"] + + +class CertificateRetrieveParams(TypedDict, total=False): + include: List[Literal["content"]] + """A list of additional fields to include in the response. + + Currently the only supported value is `content` to fetch the PEM content of the + certificate. + """ diff --git a/src/openai/types/admin/organization/certificate_update_params.py b/src/openai/types/admin/organization/certificate_update_params.py new file mode 100644 index 0000000000..c55b3aceb2 --- /dev/null +++ b/src/openai/types/admin/organization/certificate_update_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["CertificateUpdateParams"] + + +class CertificateUpdateParams(TypedDict, total=False): + name: str + """The updated name for the certificate""" diff --git a/src/openai/types/admin/organization/data_retention_update_params.py b/src/openai/types/admin/organization/data_retention_update_params.py new file mode 100644 index 0000000000..b6510e7955 --- /dev/null +++ b/src/openai/types/admin/organization/data_retention_update_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["DataRetentionUpdateParams"] + + +class DataRetentionUpdateParams(TypedDict, total=False): + retention_type: Required[ + Literal[ + "zero_data_retention", + "modified_abuse_monitoring", + "enhanced_zero_data_retention", + "enhanced_modified_abuse_monitoring", + ] + ] + """The desired organization data retention type.""" diff --git a/src/openai/types/admin/organization/group.py b/src/openai/types/admin/organization/group.py new file mode 100644 index 0000000000..045980478f --- /dev/null +++ b/src/openai/types/admin/organization/group.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["Group"] + + +class Group(BaseModel): + """Details about an organization group.""" + + id: str + """Identifier for the group.""" + + created_at: int + """Unix timestamp (in seconds) when the group was created.""" + + group_type: Literal["group", "tenant_group"] + """The type of the group.""" + + is_scim_managed: bool + """ + Whether the group is managed through SCIM and controlled by your identity + provider. + """ + + name: str + """Display name of the group.""" diff --git a/src/openai/types/admin/organization/group_create_params.py b/src/openai/types/admin/organization/group_create_params.py new file mode 100644 index 0000000000..8e27d299d3 --- /dev/null +++ b/src/openai/types/admin/organization/group_create_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["GroupCreateParams"] + + +class GroupCreateParams(TypedDict, total=False): + name: Required[str] + """Human readable name for the group.""" diff --git a/src/openai/types/admin/organization/group_delete_response.py b/src/openai/types/admin/organization/group_delete_response.py new file mode 100644 index 0000000000..6dec56e58d --- /dev/null +++ b/src/openai/types/admin/organization/group_delete_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["GroupDeleteResponse"] + + +class GroupDeleteResponse(BaseModel): + """Confirmation payload returned after deleting a group.""" + + id: str + """Identifier of the deleted group.""" + + deleted: bool + """Whether the group was deleted.""" + + object: Literal["group.deleted"] + """Always `group.deleted`.""" diff --git a/src/openai/types/admin/organization/group_list_params.py b/src/openai/types/admin/organization/group_list_params.py new file mode 100644 index 0000000000..198478b35a --- /dev/null +++ b/src/openai/types/admin/organization/group_list_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["GroupListParams"] + + +class GroupListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is a group ID that defines your place in the list. For instance, if you + make a list request and receive 100 objects, ending with group_abc, your + subsequent call can include `after=group_abc` in order to fetch the next page of + the list. + """ + + limit: int + """A limit on the number of groups to be returned. + + Limit can range between 0 and 1000, and the default is 100. + """ + + order: Literal["asc", "desc"] + """Specifies the sort order of the returned groups.""" diff --git a/src/openai/types/admin/organization/group_update_params.py b/src/openai/types/admin/organization/group_update_params.py new file mode 100644 index 0000000000..2bb3a9d8fe --- /dev/null +++ b/src/openai/types/admin/organization/group_update_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["GroupUpdateParams"] + + +class GroupUpdateParams(TypedDict, total=False): + name: Required[str] + """New display name for the group.""" diff --git a/src/openai/types/admin/organization/group_update_response.py b/src/openai/types/admin/organization/group_update_response.py new file mode 100644 index 0000000000..1ae6f86a64 --- /dev/null +++ b/src/openai/types/admin/organization/group_update_response.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ...._models import BaseModel + +__all__ = ["GroupUpdateResponse"] + + +class GroupUpdateResponse(BaseModel): + """Response returned after updating a group.""" + + id: str + """Identifier for the group.""" + + created_at: int + """Unix timestamp (in seconds) when the group was created.""" + + is_scim_managed: bool + """ + Whether the group is managed through SCIM and controlled by your identity + provider. + """ + + name: str + """Updated display name for the group.""" diff --git a/src/openai/types/admin/organization/groups/__init__.py b/src/openai/types/admin/organization/groups/__init__.py new file mode 100644 index 0000000000..884cc0d92f --- /dev/null +++ b/src/openai/types/admin/organization/groups/__init__.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .role_list_params import RoleListParams as RoleListParams +from .user_list_params import UserListParams as UserListParams +from .role_create_params import RoleCreateParams as RoleCreateParams +from .role_list_response import RoleListResponse as RoleListResponse +from .user_create_params import UserCreateParams as UserCreateParams +from .role_create_response import RoleCreateResponse as RoleCreateResponse +from .role_delete_response import RoleDeleteResponse as RoleDeleteResponse +from .user_create_response import UserCreateResponse as UserCreateResponse +from .user_delete_response import UserDeleteResponse as UserDeleteResponse +from .role_retrieve_response import RoleRetrieveResponse as RoleRetrieveResponse +from .user_retrieve_response import UserRetrieveResponse as UserRetrieveResponse +from .organization_group_user import OrganizationGroupUser as OrganizationGroupUser diff --git a/src/openai/types/admin/organization/groups/organization_group_user.py b/src/openai/types/admin/organization/groups/organization_group_user.py new file mode 100644 index 0000000000..792022c4a9 --- /dev/null +++ b/src/openai/types/admin/organization/groups/organization_group_user.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ....._models import BaseModel + +__all__ = ["OrganizationGroupUser"] + + +class OrganizationGroupUser(BaseModel): + """Represents an individual user returned when inspecting group membership.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + email: Optional[str] = None + """The email address of the user.""" + + name: str + """The name of the user.""" diff --git a/src/openai/types/admin/organization/groups/role_create_params.py b/src/openai/types/admin/organization/groups/role_create_params.py new file mode 100644 index 0000000000..0ebc196eef --- /dev/null +++ b/src/openai/types/admin/organization/groups/role_create_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["RoleCreateParams"] + + +class RoleCreateParams(TypedDict, total=False): + role_id: Required[str] + """Identifier of the role to assign.""" diff --git a/src/openai/types/admin/organization/groups/role_create_response.py b/src/openai/types/admin/organization/groups/role_create_response.py new file mode 100644 index 0000000000..8f82bfc542 --- /dev/null +++ b/src/openai/types/admin/organization/groups/role_create_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..role import Role +from ....._models import BaseModel + +__all__ = ["RoleCreateResponse", "Group"] + + +class Group(BaseModel): + """Summary information about a group returned in role assignment responses.""" + + id: str + """Identifier for the group.""" + + created_at: int + """Unix timestamp (in seconds) when the group was created.""" + + name: str + """Display name of the group.""" + + object: Literal["group"] + """Always `group`.""" + + scim_managed: bool + """Whether the group is managed through SCIM.""" + + +class RoleCreateResponse(BaseModel): + """Role assignment linking a group to a role.""" + + group: Group + """Summary information about a group returned in role assignment responses.""" + + object: Literal["group.role"] + """Always `group.role`.""" + + role: Role + """Details about a role that can be assigned through the public Roles API.""" diff --git a/src/openai/types/admin/organization/groups/role_delete_response.py b/src/openai/types/admin/organization/groups/role_delete_response.py new file mode 100644 index 0000000000..fb6a111614 --- /dev/null +++ b/src/openai/types/admin/organization/groups/role_delete_response.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ....._models import BaseModel + +__all__ = ["RoleDeleteResponse"] + + +class RoleDeleteResponse(BaseModel): + """Confirmation payload returned after unassigning a role.""" + + deleted: bool + """Whether the assignment was removed.""" + + object: str + """ + Identifier for the deleted assignment, such as `group.role.deleted` or + `user.role.deleted`. + """ diff --git a/src/openai/types/admin/organization/groups/role_list_params.py b/src/openai/types/admin/organization/groups/role_list_params.py new file mode 100644 index 0000000000..451a1a2045 --- /dev/null +++ b/src/openai/types/admin/organization/groups/role_list_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["RoleListParams"] + + +class RoleListParams(TypedDict, total=False): + after: str + """Cursor for pagination. + + Provide the value from the previous response's `next` field to continue listing + organization roles. + """ + + limit: int + """A limit on the number of organization role assignments to return.""" + + order: Literal["asc", "desc"] + """Sort order for the returned organization roles.""" diff --git a/src/openai/types/admin/organization/groups/role_list_response.py b/src/openai/types/admin/organization/groups/role_list_response.py new file mode 100644 index 0000000000..fc64f8e9a0 --- /dev/null +++ b/src/openai/types/admin/organization/groups/role_list_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from ....._models import BaseModel + +__all__ = ["RoleListResponse", "AssignmentSource"] + + +class AssignmentSource(BaseModel): + principal_id: str + + principal_type: str + + +class RoleListResponse(BaseModel): + """ + Detailed information about a role assignment entry returned when listing assignments. + """ + + id: str + """Identifier for the role.""" + + assignment_sources: Optional[List[AssignmentSource]] = None + """Principals from which the role assignment is inherited, when available.""" + + created_at: Optional[int] = None + """When the role was created.""" + + created_by: Optional[str] = None + """Identifier of the actor who created the role.""" + + created_by_user_obj: Optional[Dict[str, object]] = None + """User details for the actor that created the role, when available.""" + + description: Optional[str] = None + """Description of the role.""" + + metadata: Optional[Dict[str, object]] = None + """Arbitrary metadata stored on the role.""" + + name: str + """Name of the role.""" + + permissions: List[str] + """Permissions associated with the role.""" + + predefined_role: bool + """Whether the role is predefined by OpenAI.""" + + resource_type: str + """Resource type the role applies to.""" + + updated_at: Optional[int] = None + """When the role was last updated.""" diff --git a/src/openai/types/admin/organization/groups/role_retrieve_response.py b/src/openai/types/admin/organization/groups/role_retrieve_response.py new file mode 100644 index 0000000000..576010daad --- /dev/null +++ b/src/openai/types/admin/organization/groups/role_retrieve_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from ....._models import BaseModel + +__all__ = ["RoleRetrieveResponse", "AssignmentSource"] + + +class AssignmentSource(BaseModel): + principal_id: str + + principal_type: str + + +class RoleRetrieveResponse(BaseModel): + """ + Detailed information about a role assignment entry returned when listing assignments. + """ + + id: str + """Identifier for the role.""" + + assignment_sources: Optional[List[AssignmentSource]] = None + """Principals from which the role assignment is inherited, when available.""" + + created_at: Optional[int] = None + """When the role was created.""" + + created_by: Optional[str] = None + """Identifier of the actor who created the role.""" + + created_by_user_obj: Optional[Dict[str, object]] = None + """User details for the actor that created the role, when available.""" + + description: Optional[str] = None + """Description of the role.""" + + metadata: Optional[Dict[str, object]] = None + """Arbitrary metadata stored on the role.""" + + name: str + """Name of the role.""" + + permissions: List[str] + """Permissions associated with the role.""" + + predefined_role: bool + """Whether the role is predefined by OpenAI.""" + + resource_type: str + """Resource type the role applies to.""" + + updated_at: Optional[int] = None + """When the role was last updated.""" diff --git a/src/openai/types/admin/organization/groups/user_create_params.py b/src/openai/types/admin/organization/groups/user_create_params.py new file mode 100644 index 0000000000..ec30b46f1c --- /dev/null +++ b/src/openai/types/admin/organization/groups/user_create_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["UserCreateParams"] + + +class UserCreateParams(TypedDict, total=False): + user_id: Required[str] + """Identifier of the user to add to the group.""" diff --git a/src/openai/types/admin/organization/groups/user_create_response.py b/src/openai/types/admin/organization/groups/user_create_response.py new file mode 100644 index 0000000000..508f51747e --- /dev/null +++ b/src/openai/types/admin/organization/groups/user_create_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["UserCreateResponse"] + + +class UserCreateResponse(BaseModel): + """Confirmation payload returned after adding a user to a group.""" + + group_id: str + """Identifier of the group the user was added to.""" + + object: Literal["group.user"] + """Always `group.user`.""" + + user_id: str + """Identifier of the user that was added.""" diff --git a/src/openai/types/admin/organization/groups/user_delete_response.py b/src/openai/types/admin/organization/groups/user_delete_response.py new file mode 100644 index 0000000000..3b484a9baf --- /dev/null +++ b/src/openai/types/admin/organization/groups/user_delete_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["UserDeleteResponse"] + + +class UserDeleteResponse(BaseModel): + """Confirmation payload returned after removing a user from a group.""" + + deleted: bool + """Whether the group membership was removed.""" + + object: Literal["group.user.deleted"] + """Always `group.user.deleted`.""" diff --git a/src/openai/types/admin/organization/groups/user_list_params.py b/src/openai/types/admin/organization/groups/user_list_params.py new file mode 100644 index 0000000000..09bcfb1ba7 --- /dev/null +++ b/src/openai/types/admin/organization/groups/user_list_params.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["UserListParams"] + + +class UserListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + Provide the ID of the last user from the previous list response to retrieve the + next page. + """ + + limit: int + """A limit on the number of users to be returned. + + Limit can range between 0 and 1000, and the default is 100. + """ + + order: Literal["asc", "desc"] + """Specifies the sort order of users in the list.""" diff --git a/src/openai/types/admin/organization/groups/user_retrieve_response.py b/src/openai/types/admin/organization/groups/user_retrieve_response.py new file mode 100644 index 0000000000..977db3577f --- /dev/null +++ b/src/openai/types/admin/organization/groups/user_retrieve_response.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["UserRetrieveResponse"] + + +class UserRetrieveResponse(BaseModel): + """Details about a user returned from an organization group membership lookup.""" + + id: str + """Identifier for the user.""" + + email: Optional[str] = None + """Email address of the user, or `null` for users without an email.""" + + is_service_account: Optional[bool] = None + """Whether the user is a service account.""" + + name: str + """Display name of the user.""" + + picture: Optional[str] = None + """URL of the user's profile picture, if available.""" + + user_type: Literal["user", "tenant_user"] + """The type of user.""" diff --git a/src/openai/types/admin/organization/invite.py b/src/openai/types/admin/organization/invite.py new file mode 100644 index 0000000000..a3d2c50438 --- /dev/null +++ b/src/openai/types/admin/organization/invite.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["Invite", "Project"] + + +class Project(BaseModel): + id: str + """Project's public ID""" + + role: Literal["member", "owner"] + """Project membership role""" + + +class Invite(BaseModel): + """Represents an individual `invite` to the organization.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + created_at: int + """The Unix timestamp (in seconds) of when the invite was sent.""" + + email: str + """The email address of the individual to whom the invite was sent""" + + object: Literal["organization.invite"] + """The object type, which is always `organization.invite`""" + + projects: List[Project] + """The projects that were granted membership upon acceptance of the invite.""" + + role: Literal["owner", "reader"] + """`owner` or `reader`""" + + status: Literal["accepted", "expired", "pending"] + """`accepted`,`expired`, or `pending`""" + + accepted_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the invite was accepted.""" + + expires_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the invite expires.""" diff --git a/src/openai/types/admin/organization/invite_create_params.py b/src/openai/types/admin/organization/invite_create_params.py new file mode 100644 index 0000000000..51430d8694 --- /dev/null +++ b/src/openai/types/admin/organization/invite_create_params.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["InviteCreateParams", "Project"] + + +class InviteCreateParams(TypedDict, total=False): + email: Required[str] + """Send an email to this address""" + + role: Required[Literal["reader", "owner"]] + """`owner` or `reader`""" + + projects: Iterable[Project] + """ + An array of projects to which membership is granted at the same time the org + invite is accepted. If omitted, the user will be invited to the default project + for compatibility with legacy behavior. If empty list is passed, the user will + not be invited to any projects, including the default one. + """ + + +class Project(TypedDict, total=False): + id: Required[str] + """Project's public ID""" + + role: Required[Literal["member", "owner"]] + """Project membership role""" diff --git a/src/openai/types/admin/organization/invite_delete_response.py b/src/openai/types/admin/organization/invite_delete_response.py new file mode 100644 index 0000000000..1a8aa0ce2f --- /dev/null +++ b/src/openai/types/admin/organization/invite_delete_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["InviteDeleteResponse"] + + +class InviteDeleteResponse(BaseModel): + id: str + + deleted: bool + + object: Literal["organization.invite.deleted"] + """The object type, which is always `organization.invite.deleted`""" diff --git a/src/openai/types/admin/organization/invite_list_params.py b/src/openai/types/admin/organization/invite_list_params.py new file mode 100644 index 0000000000..678510d655 --- /dev/null +++ b/src/openai/types/admin/organization/invite_list_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["InviteListParams"] + + +class InviteListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ diff --git a/src/openai/types/admin/organization/organization_data_retention.py b/src/openai/types/admin/organization/organization_data_retention.py new file mode 100644 index 0000000000..0022094f69 --- /dev/null +++ b/src/openai/types/admin/organization/organization_data_retention.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["OrganizationDataRetention"] + + +class OrganizationDataRetention(BaseModel): + """Represents the organization's data retention control setting.""" + + object: Literal["organization.data_retention"] + """The object type, which is always `organization.data_retention`.""" + + type: Literal[ + "zero_data_retention", + "modified_abuse_monitoring", + "enhanced_zero_data_retention", + "enhanced_modified_abuse_monitoring", + ] + """The configured organization data retention type.""" diff --git a/src/openai/types/admin/organization/organization_spend_alert.py b/src/openai/types/admin/organization/organization_spend_alert.py new file mode 100644 index 0000000000..a541926a01 --- /dev/null +++ b/src/openai/types/admin/organization/organization_spend_alert.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["OrganizationSpendAlert", "NotificationChannel"] + + +class NotificationChannel(BaseModel): + """Email notification settings for a spend alert.""" + + recipients: List[str] + """Email addresses that receive the spend alert notification.""" + + type: Literal["email"] + """The notification channel type. Currently only `email` is supported.""" + + subject_prefix: Optional[str] = None + """Optional subject prefix for alert emails.""" + + +class OrganizationSpendAlert(BaseModel): + """Represents a spend alert configured at the organization level.""" + + id: str + """The identifier, which can be referenced in API endpoints.""" + + currency: Literal["USD"] + """The currency for the threshold amount.""" + + interval: Literal["month"] + """The time interval for evaluating spend against the threshold.""" + + notification_channel: NotificationChannel + """Email notification settings for a spend alert.""" + + object: Literal["organization.spend_alert"] + """The object type, which is always `organization.spend_alert`.""" + + threshold_amount: int + """The alert threshold amount, in cents.""" diff --git a/src/openai/types/admin/organization/organization_spend_alert_deleted.py b/src/openai/types/admin/organization/organization_spend_alert_deleted.py new file mode 100644 index 0000000000..74fab027ee --- /dev/null +++ b/src/openai/types/admin/organization/organization_spend_alert_deleted.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["OrganizationSpendAlertDeleted"] + + +class OrganizationSpendAlertDeleted(BaseModel): + """Confirmation payload returned after deleting an organization spend alert.""" + + id: str + """The deleted spend alert ID.""" + + deleted: bool + """Whether the spend alert was deleted.""" + + object: Literal["organization.spend_alert.deleted"] + """Always `organization.spend_alert.deleted`.""" diff --git a/src/openai/types/admin/organization/organization_user.py b/src/openai/types/admin/organization/organization_user.py new file mode 100644 index 0000000000..3d1d43a8b6 --- /dev/null +++ b/src/openai/types/admin/organization/organization_user.py @@ -0,0 +1,96 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["OrganizationUser", "Projects", "ProjectsData", "User"] + + +class ProjectsData(BaseModel): + id: Optional[str] = None + + name: Optional[str] = None + + role: Optional[str] = None + + +class Projects(BaseModel): + """Projects associated with the user, if included.""" + + data: List[ProjectsData] + + object: Literal["list"] + + +class User(BaseModel): + """Nested user details.""" + + id: str + + object: Literal["user"] + + banned: Optional[bool] = None + + banned_at: Optional[int] = None + + email: Optional[str] = None + + enabled: Optional[bool] = None + + name: Optional[str] = None + + picture: Optional[str] = None + + +class OrganizationUser(BaseModel): + """Represents an individual `user` within an organization.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + added_at: int + """The Unix timestamp (in seconds) of when the user was added.""" + + object: Literal["organization.user"] + """The object type, which is always `organization.user`""" + + api_key_last_used_at: Optional[int] = None + """The Unix timestamp (in seconds) of the user's last API key usage.""" + + created: Optional[int] = None + """The Unix timestamp (in seconds) of when the user was created.""" + + developer_persona: Optional[str] = None + """The developer persona metadata for the user.""" + + email: Optional[str] = None + """The email address of the user""" + + is_default: Optional[bool] = None + """Whether this is the organization's default user.""" + + is_scale_tier_authorized_purchaser: Optional[bool] = None + """Whether the user is an authorized purchaser for Scale Tier.""" + + is_scim_managed: Optional[bool] = None + """Whether the user is managed through SCIM.""" + + is_service_account: Optional[bool] = None + """Whether the user is a service account.""" + + name: Optional[str] = None + """The name of the user""" + + projects: Optional[Projects] = None + """Projects associated with the user, if included.""" + + role: Optional[str] = None + """`owner` or `reader`""" + + technical_level: Optional[str] = None + """The technical level metadata for the user.""" + + user: Optional[User] = None + """Nested user details.""" diff --git a/src/openai/types/admin/organization/project.py b/src/openai/types/admin/organization/project.py new file mode 100644 index 0000000000..982bb1e4b3 --- /dev/null +++ b/src/openai/types/admin/organization/project.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["Project"] + + +class Project(BaseModel): + """Represents an individual project.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + created_at: int + """The Unix timestamp (in seconds) of when the project was created.""" + + object: Literal["organization.project"] + """The object type, which is always `organization.project`""" + + archived_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the project was archived or `null`.""" + + external_key_id: Optional[str] = None + """The external key associated with the project.""" + + name: Optional[str] = None + """The name of the project. This appears in reporting.""" + + status: Optional[str] = None + """`active` or `archived`""" diff --git a/src/openai/types/admin/organization/project_create_params.py b/src/openai/types/admin/organization/project_create_params.py new file mode 100644 index 0000000000..a4b7b2d424 --- /dev/null +++ b/src/openai/types/admin/organization/project_create_params.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ProjectCreateParams"] + + +class ProjectCreateParams(TypedDict, total=False): + name: Required[str] + """The friendly name of the project, this name appears in reports.""" + + external_key_id: Optional[str] + """External key ID to associate with the project.""" + + geography: Optional[str] + """Create the project with the specified data residency region. + + Your organization must have access to Data residency functionality in order to + use. See + [data residency controls](https://platform.openai.com/docs/guides/your-data#data-residency-controls) + to review the functionality and limitations of setting this field. + """ diff --git a/src/openai/types/admin/organization/project_list_params.py b/src/openai/types/admin/organization/project_list_params.py new file mode 100644 index 0000000000..f55fb8a392 --- /dev/null +++ b/src/openai/types/admin/organization/project_list_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ProjectListParams"] + + +class ProjectListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + include_archived: bool + """If `true` returns all projects including those that have been `archived`. + + Archived projects are not included by default. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ diff --git a/src/openai/types/admin/organization/project_update_params.py b/src/openai/types/admin/organization/project_update_params.py new file mode 100644 index 0000000000..2ebdd09f4a --- /dev/null +++ b/src/openai/types/admin/organization/project_update_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["ProjectUpdateParams"] + + +class ProjectUpdateParams(TypedDict, total=False): + external_key_id: Optional[str] + """External key ID to associate with the project.""" + + geography: Optional[str] + """Geography for the project.""" + + name: Optional[str] + """The updated name of the project, this name appears in reports.""" diff --git a/src/openai/types/admin/organization/projects/__init__.py b/src/openai/types/admin/organization/projects/__init__.py new file mode 100644 index 0000000000..1c510be58d --- /dev/null +++ b/src/openai/types/admin/organization/projects/__init__.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .project_user import ProjectUser as ProjectUser +from .project_group import ProjectGroup as ProjectGroup +from .project_api_key import ProjectAPIKey as ProjectAPIKey +from .role_list_params import RoleListParams as RoleListParams +from .user_list_params import UserListParams as UserListParams +from .group_list_params import GroupListParams as GroupListParams +from .project_rate_limit import ProjectRateLimit as ProjectRateLimit +from .role_create_params import RoleCreateParams as RoleCreateParams +from .role_update_params import RoleUpdateParams as RoleUpdateParams +from .user_create_params import UserCreateParams as UserCreateParams +from .user_update_params import UserUpdateParams as UserUpdateParams +from .api_key_list_params import APIKeyListParams as APIKeyListParams +from .group_create_params import GroupCreateParams as GroupCreateParams +from .project_spend_alert import ProjectSpendAlert as ProjectSpendAlert +from .role_delete_response import RoleDeleteResponse as RoleDeleteResponse +from .user_delete_response import UserDeleteResponse as UserDeleteResponse +from .group_delete_response import GroupDeleteResponse as GroupDeleteResponse +from .group_retrieve_params import GroupRetrieveParams as GroupRetrieveParams +from .project_data_retention import ProjectDataRetention as ProjectDataRetention +from .api_key_delete_response import APIKeyDeleteResponse as APIKeyDeleteResponse +from .certificate_list_params import CertificateListParams as CertificateListParams +from .project_service_account import ProjectServiceAccount as ProjectServiceAccount +from .spend_alert_list_params import SpendAlertListParams as SpendAlertListParams +from .certificate_list_response import CertificateListResponse as CertificateListResponse +from .project_model_permissions import ProjectModelPermissions as ProjectModelPermissions +from .spend_alert_create_params import SpendAlertCreateParams as SpendAlertCreateParams +from .spend_alert_update_params import SpendAlertUpdateParams as SpendAlertUpdateParams +from .certificate_activate_params import CertificateActivateParams as CertificateActivateParams +from .project_spend_alert_deleted import ProjectSpendAlertDeleted as ProjectSpendAlertDeleted +from .service_account_list_params import ServiceAccountListParams as ServiceAccountListParams +from .data_retention_update_params import DataRetentionUpdateParams as DataRetentionUpdateParams +from .certificate_activate_response import CertificateActivateResponse as CertificateActivateResponse +from .certificate_deactivate_params import CertificateDeactivateParams as CertificateDeactivateParams +from .service_account_create_params import ServiceAccountCreateParams as ServiceAccountCreateParams +from .service_account_update_params import ServiceAccountUpdateParams as ServiceAccountUpdateParams +from .model_permission_update_params import ModelPermissionUpdateParams as ModelPermissionUpdateParams +from .certificate_deactivate_response import CertificateDeactivateResponse as CertificateDeactivateResponse +from .project_hosted_tool_permissions import ProjectHostedToolPermissions as ProjectHostedToolPermissions +from .service_account_create_response import ServiceAccountCreateResponse as ServiceAccountCreateResponse +from .service_account_delete_response import ServiceAccountDeleteResponse as ServiceAccountDeleteResponse +from .project_model_permissions_deleted import ProjectModelPermissionsDeleted as ProjectModelPermissionsDeleted +from .rate_limit_list_rate_limits_params import RateLimitListRateLimitsParams as RateLimitListRateLimitsParams +from .rate_limit_update_rate_limit_params import RateLimitUpdateRateLimitParams as RateLimitUpdateRateLimitParams +from .hosted_tool_permission_update_params import HostedToolPermissionUpdateParams as HostedToolPermissionUpdateParams diff --git a/src/openai/types/admin/organization/projects/api_key_delete_response.py b/src/openai/types/admin/organization/projects/api_key_delete_response.py new file mode 100644 index 0000000000..253a6746ba --- /dev/null +++ b/src/openai/types/admin/organization/projects/api_key_delete_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["APIKeyDeleteResponse"] + + +class APIKeyDeleteResponse(BaseModel): + id: str + + deleted: bool + + object: Literal["organization.project.api_key.deleted"] diff --git a/src/openai/types/admin/organization/projects/api_key_list_params.py b/src/openai/types/admin/organization/projects/api_key_list_params.py new file mode 100644 index 0000000000..422a28518e --- /dev/null +++ b/src/openai/types/admin/organization/projects/api_key_list_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["APIKeyListParams"] + + +class APIKeyListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ diff --git a/src/openai/types/admin/organization/projects/certificate_activate_params.py b/src/openai/types/admin/organization/projects/certificate_activate_params.py new file mode 100644 index 0000000000..a0e7cd20e8 --- /dev/null +++ b/src/openai/types/admin/organization/projects/certificate_activate_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ....._types import SequenceNotStr + +__all__ = ["CertificateActivateParams"] + + +class CertificateActivateParams(TypedDict, total=False): + certificate_ids: Required[SequenceNotStr[str]] diff --git a/src/openai/types/admin/organization/projects/certificate_activate_response.py b/src/openai/types/admin/organization/projects/certificate_activate_response.py new file mode 100644 index 0000000000..4ee8ae07f4 --- /dev/null +++ b/src/openai/types/admin/organization/projects/certificate_activate_response.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["CertificateActivateResponse", "CertificateDetails"] + + +class CertificateDetails(BaseModel): + expires_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate expires.""" + + valid_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate becomes valid.""" + + +class CertificateActivateResponse(BaseModel): + """Represents an individual certificate configured at the project level.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + active: bool + """Whether the certificate is currently active at the project level.""" + + certificate_details: CertificateDetails + + created_at: int + """The Unix timestamp (in seconds) of when the certificate was uploaded.""" + + name: Optional[str] = None + """The name of the certificate.""" + + object: Literal["organization.project.certificate"] + """The object type, which is always `organization.project.certificate`.""" diff --git a/src/openai/types/admin/organization/projects/certificate_deactivate_params.py b/src/openai/types/admin/organization/projects/certificate_deactivate_params.py new file mode 100644 index 0000000000..75c32708ba --- /dev/null +++ b/src/openai/types/admin/organization/projects/certificate_deactivate_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ....._types import SequenceNotStr + +__all__ = ["CertificateDeactivateParams"] + + +class CertificateDeactivateParams(TypedDict, total=False): + certificate_ids: Required[SequenceNotStr[str]] diff --git a/src/openai/types/admin/organization/projects/certificate_deactivate_response.py b/src/openai/types/admin/organization/projects/certificate_deactivate_response.py new file mode 100644 index 0000000000..846c7a2ab7 --- /dev/null +++ b/src/openai/types/admin/organization/projects/certificate_deactivate_response.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["CertificateDeactivateResponse", "CertificateDetails"] + + +class CertificateDetails(BaseModel): + expires_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate expires.""" + + valid_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate becomes valid.""" + + +class CertificateDeactivateResponse(BaseModel): + """Represents an individual certificate configured at the project level.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + active: bool + """Whether the certificate is currently active at the project level.""" + + certificate_details: CertificateDetails + + created_at: int + """The Unix timestamp (in seconds) of when the certificate was uploaded.""" + + name: Optional[str] = None + """The name of the certificate.""" + + object: Literal["organization.project.certificate"] + """The object type, which is always `organization.project.certificate`.""" diff --git a/src/openai/types/admin/organization/projects/certificate_list_params.py b/src/openai/types/admin/organization/projects/certificate_list_params.py new file mode 100644 index 0000000000..1d9aff4b4a --- /dev/null +++ b/src/openai/types/admin/organization/projects/certificate_list_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["CertificateListParams"] + + +class CertificateListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ + + order: Literal["asc", "desc"] + """Sort order by the `created_at` timestamp of the objects. + + `asc` for ascending order and `desc` for descending order. + """ diff --git a/src/openai/types/admin/organization/projects/certificate_list_response.py b/src/openai/types/admin/organization/projects/certificate_list_response.py new file mode 100644 index 0000000000..d4345b3b8c --- /dev/null +++ b/src/openai/types/admin/organization/projects/certificate_list_response.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["CertificateListResponse", "CertificateDetails"] + + +class CertificateDetails(BaseModel): + expires_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate expires.""" + + valid_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the certificate becomes valid.""" + + +class CertificateListResponse(BaseModel): + """Represents an individual certificate configured at the project level.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + active: bool + """Whether the certificate is currently active at the project level.""" + + certificate_details: CertificateDetails + + created_at: int + """The Unix timestamp (in seconds) of when the certificate was uploaded.""" + + name: Optional[str] = None + """The name of the certificate.""" + + object: Literal["organization.project.certificate"] + """The object type, which is always `organization.project.certificate`.""" diff --git a/src/openai/types/admin/organization/projects/data_retention_update_params.py b/src/openai/types/admin/organization/projects/data_retention_update_params.py new file mode 100644 index 0000000000..e7291d207a --- /dev/null +++ b/src/openai/types/admin/organization/projects/data_retention_update_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["DataRetentionUpdateParams"] + + +class DataRetentionUpdateParams(TypedDict, total=False): + retention_type: Required[ + Literal[ + "organization_default", + "none", + "zero_data_retention", + "modified_abuse_monitoring", + "enhanced_zero_data_retention", + "enhanced_modified_abuse_monitoring", + ] + ] + """The desired project data retention type.""" diff --git a/src/openai/types/admin/organization/projects/group_create_params.py b/src/openai/types/admin/organization/projects/group_create_params.py new file mode 100644 index 0000000000..b9f4626d74 --- /dev/null +++ b/src/openai/types/admin/organization/projects/group_create_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["GroupCreateParams"] + + +class GroupCreateParams(TypedDict, total=False): + group_id: Required[str] + """Identifier of the group to add to the project.""" + + role: Required[str] + """Identifier of the project role to grant to the group.""" diff --git a/src/openai/types/admin/organization/projects/group_delete_response.py b/src/openai/types/admin/organization/projects/group_delete_response.py new file mode 100644 index 0000000000..ef1ce0ddb8 --- /dev/null +++ b/src/openai/types/admin/organization/projects/group_delete_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["GroupDeleteResponse"] + + +class GroupDeleteResponse(BaseModel): + """Confirmation payload returned after removing a group from a project.""" + + deleted: bool + """Whether the group membership in the project was removed.""" + + object: Literal["project.group.deleted"] + """Always `project.group.deleted`.""" diff --git a/src/openai/types/admin/organization/projects/group_list_params.py b/src/openai/types/admin/organization/projects/group_list_params.py new file mode 100644 index 0000000000..26ab31a88b --- /dev/null +++ b/src/openai/types/admin/organization/projects/group_list_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["GroupListParams"] + + +class GroupListParams(TypedDict, total=False): + after: str + """Cursor for pagination. + + Provide the ID of the last group from the previous response to fetch the next + page. + """ + + limit: int + """A limit on the number of project groups to return. Defaults to 20.""" + + order: Literal["asc", "desc"] + """Sort order for the returned groups.""" diff --git a/src/openai/types/admin/organization/projects/group_retrieve_params.py b/src/openai/types/admin/organization/projects/group_retrieve_params.py new file mode 100644 index 0000000000..084f345f43 --- /dev/null +++ b/src/openai/types/admin/organization/projects/group_retrieve_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["GroupRetrieveParams"] + + +class GroupRetrieveParams(TypedDict, total=False): + project_id: Required[str] + + group_type: Literal["group", "tenant_group"] + """The type of group to retrieve.""" diff --git a/src/openai/types/admin/organization/projects/groups/__init__.py b/src/openai/types/admin/organization/projects/groups/__init__.py new file mode 100644 index 0000000000..5e67517593 --- /dev/null +++ b/src/openai/types/admin/organization/projects/groups/__init__.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .role_list_params import RoleListParams as RoleListParams +from .role_create_params import RoleCreateParams as RoleCreateParams +from .role_list_response import RoleListResponse as RoleListResponse +from .role_create_response import RoleCreateResponse as RoleCreateResponse +from .role_delete_response import RoleDeleteResponse as RoleDeleteResponse +from .role_retrieve_response import RoleRetrieveResponse as RoleRetrieveResponse diff --git a/src/openai/types/admin/organization/projects/groups/role_create_params.py b/src/openai/types/admin/organization/projects/groups/role_create_params.py new file mode 100644 index 0000000000..2aba01e8a4 --- /dev/null +++ b/src/openai/types/admin/organization/projects/groups/role_create_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["RoleCreateParams"] + + +class RoleCreateParams(TypedDict, total=False): + project_id: Required[str] + + role_id: Required[str] + """Identifier of the role to assign.""" diff --git a/src/openai/types/admin/organization/projects/groups/role_create_response.py b/src/openai/types/admin/organization/projects/groups/role_create_response.py new file mode 100644 index 0000000000..c6e7a1c048 --- /dev/null +++ b/src/openai/types/admin/organization/projects/groups/role_create_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...role import Role +from ......_models import BaseModel + +__all__ = ["RoleCreateResponse", "Group"] + + +class Group(BaseModel): + """Summary information about a group returned in role assignment responses.""" + + id: str + """Identifier for the group.""" + + created_at: int + """Unix timestamp (in seconds) when the group was created.""" + + name: str + """Display name of the group.""" + + object: Literal["group"] + """Always `group`.""" + + scim_managed: bool + """Whether the group is managed through SCIM.""" + + +class RoleCreateResponse(BaseModel): + """Role assignment linking a group to a role.""" + + group: Group + """Summary information about a group returned in role assignment responses.""" + + object: Literal["group.role"] + """Always `group.role`.""" + + role: Role + """Details about a role that can be assigned through the public Roles API.""" diff --git a/src/openai/types/admin/organization/projects/groups/role_delete_response.py b/src/openai/types/admin/organization/projects/groups/role_delete_response.py new file mode 100644 index 0000000000..704de05117 --- /dev/null +++ b/src/openai/types/admin/organization/projects/groups/role_delete_response.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ......_models import BaseModel + +__all__ = ["RoleDeleteResponse"] + + +class RoleDeleteResponse(BaseModel): + """Confirmation payload returned after unassigning a role.""" + + deleted: bool + """Whether the assignment was removed.""" + + object: str + """ + Identifier for the deleted assignment, such as `group.role.deleted` or + `user.role.deleted`. + """ diff --git a/src/openai/types/admin/organization/projects/groups/role_list_params.py b/src/openai/types/admin/organization/projects/groups/role_list_params.py new file mode 100644 index 0000000000..ffdbe210d2 --- /dev/null +++ b/src/openai/types/admin/organization/projects/groups/role_list_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RoleListParams"] + + +class RoleListParams(TypedDict, total=False): + project_id: Required[str] + + after: str + """Cursor for pagination. + + Provide the value from the previous response's `next` field to continue listing + project roles. + """ + + limit: int + """A limit on the number of project role assignments to return.""" + + order: Literal["asc", "desc"] + """Sort order for the returned project roles.""" diff --git a/src/openai/types/admin/organization/projects/groups/role_list_response.py b/src/openai/types/admin/organization/projects/groups/role_list_response.py new file mode 100644 index 0000000000..d43d0f806b --- /dev/null +++ b/src/openai/types/admin/organization/projects/groups/role_list_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from ......_models import BaseModel + +__all__ = ["RoleListResponse", "AssignmentSource"] + + +class AssignmentSource(BaseModel): + principal_id: str + + principal_type: str + + +class RoleListResponse(BaseModel): + """ + Detailed information about a role assignment entry returned when listing assignments. + """ + + id: str + """Identifier for the role.""" + + assignment_sources: Optional[List[AssignmentSource]] = None + """Principals from which the role assignment is inherited, when available.""" + + created_at: Optional[int] = None + """When the role was created.""" + + created_by: Optional[str] = None + """Identifier of the actor who created the role.""" + + created_by_user_obj: Optional[Dict[str, object]] = None + """User details for the actor that created the role, when available.""" + + description: Optional[str] = None + """Description of the role.""" + + metadata: Optional[Dict[str, object]] = None + """Arbitrary metadata stored on the role.""" + + name: str + """Name of the role.""" + + permissions: List[str] + """Permissions associated with the role.""" + + predefined_role: bool + """Whether the role is predefined by OpenAI.""" + + resource_type: str + """Resource type the role applies to.""" + + updated_at: Optional[int] = None + """When the role was last updated.""" diff --git a/src/openai/types/admin/organization/projects/groups/role_retrieve_response.py b/src/openai/types/admin/organization/projects/groups/role_retrieve_response.py new file mode 100644 index 0000000000..0c10cf0092 --- /dev/null +++ b/src/openai/types/admin/organization/projects/groups/role_retrieve_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from ......_models import BaseModel + +__all__ = ["RoleRetrieveResponse", "AssignmentSource"] + + +class AssignmentSource(BaseModel): + principal_id: str + + principal_type: str + + +class RoleRetrieveResponse(BaseModel): + """ + Detailed information about a role assignment entry returned when listing assignments. + """ + + id: str + """Identifier for the role.""" + + assignment_sources: Optional[List[AssignmentSource]] = None + """Principals from which the role assignment is inherited, when available.""" + + created_at: Optional[int] = None + """When the role was created.""" + + created_by: Optional[str] = None + """Identifier of the actor who created the role.""" + + created_by_user_obj: Optional[Dict[str, object]] = None + """User details for the actor that created the role, when available.""" + + description: Optional[str] = None + """Description of the role.""" + + metadata: Optional[Dict[str, object]] = None + """Arbitrary metadata stored on the role.""" + + name: str + """Name of the role.""" + + permissions: List[str] + """Permissions associated with the role.""" + + predefined_role: bool + """Whether the role is predefined by OpenAI.""" + + resource_type: str + """Resource type the role applies to.""" + + updated_at: Optional[int] = None + """When the role was last updated.""" diff --git a/src/openai/types/admin/organization/projects/hosted_tool_permission_update_params.py b/src/openai/types/admin/organization/projects/hosted_tool_permission_update_params.py new file mode 100644 index 0000000000..e14f03741f --- /dev/null +++ b/src/openai/types/admin/organization/projects/hosted_tool_permission_update_params.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["HostedToolPermissionUpdateParams", "CodeInterpreter", "FileSearch", "ImageGeneration", "Mcp", "WebSearch"] + + +class HostedToolPermissionUpdateParams(TypedDict, total=False): + code_interpreter: Optional[CodeInterpreter] + """The code interpreter permission update.""" + + file_search: Optional[FileSearch] + """The file search permission update.""" + + image_generation: Optional[ImageGeneration] + """The image generation permission update.""" + + mcp: Optional[Mcp] + """The MCP permission update.""" + + web_search: Optional[WebSearch] + """The web search permission update.""" + + +class CodeInterpreter(TypedDict, total=False): + """The code interpreter permission update.""" + + enabled: Required[bool] + """Whether to enable the hosted tool for the project.""" + + +class FileSearch(TypedDict, total=False): + """The file search permission update.""" + + enabled: Required[bool] + """Whether to enable the hosted tool for the project.""" + + +class ImageGeneration(TypedDict, total=False): + """The image generation permission update.""" + + enabled: Required[bool] + """Whether to enable the hosted tool for the project.""" + + +class Mcp(TypedDict, total=False): + """The MCP permission update.""" + + enabled: Required[bool] + """Whether to enable the hosted tool for the project.""" + + +class WebSearch(TypedDict, total=False): + """The web search permission update.""" + + enabled: Required[bool] + """Whether to enable the hosted tool for the project.""" diff --git a/src/openai/types/admin/organization/projects/model_permission_update_params.py b/src/openai/types/admin/organization/projects/model_permission_update_params.py new file mode 100644 index 0000000000..bf7cb1d56c --- /dev/null +++ b/src/openai/types/admin/organization/projects/model_permission_update_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from ....._types import SequenceNotStr + +__all__ = ["ModelPermissionUpdateParams"] + + +class ModelPermissionUpdateParams(TypedDict, total=False): + mode: Required[Literal["allow_list", "deny_list"]] + """The model permissions mode to apply.""" + + model_ids: Required[SequenceNotStr[str]] + """The model IDs included in this permissions policy.""" diff --git a/src/openai/types/admin/organization/projects/project_api_key.py b/src/openai/types/admin/organization/projects/project_api_key.py new file mode 100644 index 0000000000..7e5e6949eb --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_api_key.py @@ -0,0 +1,78 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ProjectAPIKey", "Owner", "OwnerServiceAccount", "OwnerUser"] + + +class OwnerServiceAccount(BaseModel): + """The service account that owns a project API key.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + created_at: int + """The Unix timestamp (in seconds) of when the service account was created.""" + + name: str + """The name of the service account.""" + + role: str + """The service account's project role.""" + + +class OwnerUser(BaseModel): + """The user that owns a project API key.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + created_at: int + """The Unix timestamp (in seconds) of when the user was created.""" + + email: str + """The email address of the user.""" + + name: str + """The name of the user.""" + + role: str + """The user's project role.""" + + +class Owner(BaseModel): + service_account: Optional[OwnerServiceAccount] = None + """The service account that owns a project API key.""" + + type: Optional[Literal["user", "service_account"]] = None + """`user` or `service_account`""" + + user: Optional[OwnerUser] = None + """The user that owns a project API key.""" + + +class ProjectAPIKey(BaseModel): + """Represents an individual API key in a project.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + created_at: int + """The Unix timestamp (in seconds) of when the API key was created""" + + last_used_at: Optional[int] = None + """The Unix timestamp (in seconds) of when the API key was last used.""" + + name: str + """The name of the API key""" + + object: Literal["organization.project.api_key"] + """The object type, which is always `organization.project.api_key`""" + + owner: Owner + + redacted_value: str + """The redacted value of the API key""" diff --git a/src/openai/types/admin/organization/projects/project_data_retention.py b/src/openai/types/admin/organization/projects/project_data_retention.py new file mode 100644 index 0000000000..30329e60f4 --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_data_retention.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ProjectDataRetention"] + + +class ProjectDataRetention(BaseModel): + """Represents a project's data retention control setting.""" + + object: Literal["project.data_retention"] + """The object type, which is always `project.data_retention`.""" + + type: Literal[ + "organization_default", + "none", + "zero_data_retention", + "modified_abuse_monitoring", + "enhanced_zero_data_retention", + "enhanced_modified_abuse_monitoring", + ] + """The configured project data retention type.""" diff --git a/src/openai/types/admin/organization/projects/project_group.py b/src/openai/types/admin/organization/projects/project_group.py new file mode 100644 index 0000000000..4efb29f91e --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_group.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ProjectGroup"] + + +class ProjectGroup(BaseModel): + """Details about a group's membership in a project.""" + + created_at: int + """Unix timestamp (in seconds) when the group was granted project access.""" + + group_id: str + """Identifier of the group that has access to the project.""" + + group_name: str + """Display name of the group.""" + + group_type: Literal["group", "tenant_group"] + """The type of the group.""" + + object: Literal["project.group"] + """Always `project.group`.""" + + project_id: str + """Identifier of the project.""" diff --git a/src/openai/types/admin/organization/projects/project_hosted_tool_permissions.py b/src/openai/types/admin/organization/projects/project_hosted_tool_permissions.py new file mode 100644 index 0000000000..e89ed18af0 --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_hosted_tool_permissions.py @@ -0,0 +1,59 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ....._models import BaseModel + +__all__ = ["ProjectHostedToolPermissions", "CodeInterpreter", "FileSearch", "ImageGeneration", "Mcp", "WebSearch"] + + +class CodeInterpreter(BaseModel): + """Permission state for a single hosted tool on a project.""" + + enabled: bool + """Whether the hosted tool is enabled for the project.""" + + +class FileSearch(BaseModel): + """Permission state for a single hosted tool on a project.""" + + enabled: bool + """Whether the hosted tool is enabled for the project.""" + + +class ImageGeneration(BaseModel): + """Permission state for a single hosted tool on a project.""" + + enabled: bool + """Whether the hosted tool is enabled for the project.""" + + +class Mcp(BaseModel): + """Permission state for a single hosted tool on a project.""" + + enabled: bool + """Whether the hosted tool is enabled for the project.""" + + +class WebSearch(BaseModel): + """Permission state for a single hosted tool on a project.""" + + enabled: bool + """Whether the hosted tool is enabled for the project.""" + + +class ProjectHostedToolPermissions(BaseModel): + """Represents hosted tool permissions for a project.""" + + code_interpreter: CodeInterpreter + """Permission state for a single hosted tool on a project.""" + + file_search: FileSearch + """Permission state for a single hosted tool on a project.""" + + image_generation: ImageGeneration + """Permission state for a single hosted tool on a project.""" + + mcp: Mcp + """Permission state for a single hosted tool on a project.""" + + web_search: WebSearch + """Permission state for a single hosted tool on a project.""" diff --git a/src/openai/types/admin/organization/projects/project_model_permissions.py b/src/openai/types/admin/organization/projects/project_model_permissions.py new file mode 100644 index 0000000000..597ae4e257 --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_model_permissions.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ....._models import BaseModel + +__all__ = ["ProjectModelPermissions"] + + +class ProjectModelPermissions(BaseModel): + """Represents the model allowlist or denylist policy for a project.""" + + mode: Literal["allow_list", "deny_list"] + """Whether the project uses an allowlist or a denylist.""" + + api_model_ids: List[str] = FieldInfo(alias="model_ids") + """The model IDs included in the model permissions policy.""" + + object: Literal["project.model_permissions"] + """The object type, which is always `project.model_permissions`.""" diff --git a/src/openai/types/admin/organization/projects/project_model_permissions_deleted.py b/src/openai/types/admin/organization/projects/project_model_permissions_deleted.py new file mode 100644 index 0000000000..cb3972dce8 --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_model_permissions_deleted.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ProjectModelPermissionsDeleted"] + + +class ProjectModelPermissionsDeleted(BaseModel): + """Confirmation payload returned after deleting project model permissions.""" + + deleted: bool + """Whether the project model permissions were deleted.""" + + object: Literal["project.model_permissions.deleted"] + """The object type, which is always `project.model_permissions.deleted`.""" diff --git a/src/openai/types/admin/organization/projects/project_rate_limit.py b/src/openai/types/admin/organization/projects/project_rate_limit.py new file mode 100644 index 0000000000..46ff1ef036 --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_rate_limit.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ProjectRateLimit"] + + +class ProjectRateLimit(BaseModel): + """Represents a project rate limit config.""" + + id: str + """The identifier, which can be referenced in API endpoints.""" + + max_requests_per_1_minute: int + """The maximum requests per minute.""" + + max_tokens_per_1_minute: int + """The maximum tokens per minute.""" + + model: str + """The model this rate limit applies to.""" + + object: Literal["project.rate_limit"] + """The object type, which is always `project.rate_limit`""" + + batch_1_day_max_input_tokens: Optional[int] = None + """The maximum batch input tokens per day. Only present for relevant models.""" + + max_audio_megabytes_per_1_minute: Optional[int] = None + """The maximum audio megabytes per minute. Only present for relevant models.""" + + max_images_per_1_minute: Optional[int] = None + """The maximum images per minute. Only present for relevant models.""" + + max_requests_per_1_day: Optional[int] = None + """The maximum requests per day. Only present for relevant models.""" diff --git a/src/openai/types/admin/organization/projects/project_service_account.py b/src/openai/types/admin/organization/projects/project_service_account.py new file mode 100644 index 0000000000..ca7bf0bdae --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_service_account.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ProjectServiceAccount"] + + +class ProjectServiceAccount(BaseModel): + """Represents an individual service account in a project.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + created_at: int + """The Unix timestamp (in seconds) of when the service account was created""" + + name: str + """The name of the service account""" + + object: Literal["organization.project.service_account"] + """The object type, which is always `organization.project.service_account`""" + + role: Literal["owner", "member"] + """`owner` or `member`""" diff --git a/src/openai/types/admin/organization/projects/project_spend_alert.py b/src/openai/types/admin/organization/projects/project_spend_alert.py new file mode 100644 index 0000000000..269ea54db5 --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_spend_alert.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ProjectSpendAlert", "NotificationChannel"] + + +class NotificationChannel(BaseModel): + """Email notification settings for a spend alert.""" + + recipients: List[str] + """Email addresses that receive the spend alert notification.""" + + type: Literal["email"] + """The notification channel type. Currently only `email` is supported.""" + + subject_prefix: Optional[str] = None + """Optional subject prefix for alert emails.""" + + +class ProjectSpendAlert(BaseModel): + """Represents a spend alert configured at the project level.""" + + id: str + """The identifier, which can be referenced in API endpoints.""" + + currency: Literal["USD"] + """The currency for the threshold amount.""" + + interval: Literal["month"] + """The time interval for evaluating spend against the threshold.""" + + notification_channel: NotificationChannel + """Email notification settings for a spend alert.""" + + object: Literal["project.spend_alert"] + """The object type, which is always `project.spend_alert`.""" + + threshold_amount: int + """The alert threshold amount, in cents.""" diff --git a/src/openai/types/admin/organization/projects/project_spend_alert_deleted.py b/src/openai/types/admin/organization/projects/project_spend_alert_deleted.py new file mode 100644 index 0000000000..68be581307 --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_spend_alert_deleted.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ProjectSpendAlertDeleted"] + + +class ProjectSpendAlertDeleted(BaseModel): + """Confirmation payload returned after deleting a project spend alert.""" + + id: str + """The deleted spend alert ID.""" + + deleted: bool + """Whether the spend alert was deleted.""" + + object: Literal["project.spend_alert.deleted"] + """Always `project.spend_alert.deleted`.""" diff --git a/src/openai/types/admin/organization/projects/project_user.py b/src/openai/types/admin/organization/projects/project_user.py new file mode 100644 index 0000000000..7aaa9fc05b --- /dev/null +++ b/src/openai/types/admin/organization/projects/project_user.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ProjectUser"] + + +class ProjectUser(BaseModel): + """Represents an individual user in a project.""" + + id: str + """The identifier, which can be referenced in API endpoints""" + + added_at: int + """The Unix timestamp (in seconds) of when the project was added.""" + + object: Literal["organization.project.user"] + """The object type, which is always `organization.project.user`""" + + role: str + """`owner` or `member`""" + + email: Optional[str] = None + """The email address of the user""" + + name: Optional[str] = None + """The name of the user""" diff --git a/src/openai/types/admin/organization/projects/rate_limit_list_rate_limits_params.py b/src/openai/types/admin/organization/projects/rate_limit_list_rate_limits_params.py new file mode 100644 index 0000000000..198fe94f19 --- /dev/null +++ b/src/openai/types/admin/organization/projects/rate_limit_list_rate_limits_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["RateLimitListRateLimitsParams"] + + +class RateLimitListRateLimitsParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + before: str + """A cursor for use in pagination. + + `before` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, beginning with obj_foo, your + subsequent call can include before=obj_foo in order to fetch the previous page + of the list. + """ + + limit: int + """A limit on the number of objects to be returned. The default is 100.""" diff --git a/src/openai/types/admin/organization/projects/rate_limit_update_rate_limit_params.py b/src/openai/types/admin/organization/projects/rate_limit_update_rate_limit_params.py new file mode 100644 index 0000000000..5d5e515515 --- /dev/null +++ b/src/openai/types/admin/organization/projects/rate_limit_update_rate_limit_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["RateLimitUpdateRateLimitParams"] + + +class RateLimitUpdateRateLimitParams(TypedDict, total=False): + project_id: Required[str] + + batch_1_day_max_input_tokens: int + """The maximum batch input tokens per day. Only relevant for certain models.""" + + max_audio_megabytes_per_1_minute: int + """The maximum audio megabytes per minute. Only relevant for certain models.""" + + max_images_per_1_minute: int + """The maximum images per minute. Only relevant for certain models.""" + + max_requests_per_1_day: int + """The maximum requests per day. Only relevant for certain models.""" + + max_requests_per_1_minute: int + """The maximum requests per minute.""" + + max_tokens_per_1_minute: int + """The maximum tokens per minute.""" diff --git a/src/openai/types/admin/organization/projects/role_create_params.py b/src/openai/types/admin/organization/projects/role_create_params.py new file mode 100644 index 0000000000..e05c8c8a63 --- /dev/null +++ b/src/openai/types/admin/organization/projects/role_create_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +from ....._types import SequenceNotStr + +__all__ = ["RoleCreateParams"] + + +class RoleCreateParams(TypedDict, total=False): + permissions: Required[SequenceNotStr[str]] + """Permissions to grant to the role.""" + + role_name: Required[str] + """Unique name for the role.""" + + description: Optional[str] + """Optional description of the role.""" diff --git a/src/openai/types/admin/organization/projects/role_delete_response.py b/src/openai/types/admin/organization/projects/role_delete_response.py new file mode 100644 index 0000000000..87fa8e6200 --- /dev/null +++ b/src/openai/types/admin/organization/projects/role_delete_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["RoleDeleteResponse"] + + +class RoleDeleteResponse(BaseModel): + """Confirmation payload returned after deleting a role.""" + + id: str + """Identifier of the deleted role.""" + + deleted: bool + """Whether the role was deleted.""" + + object: Literal["role.deleted"] + """Always `role.deleted`.""" diff --git a/src/openai/types/admin/organization/projects/role_list_params.py b/src/openai/types/admin/organization/projects/role_list_params.py new file mode 100644 index 0000000000..88e957f886 --- /dev/null +++ b/src/openai/types/admin/organization/projects/role_list_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["RoleListParams"] + + +class RoleListParams(TypedDict, total=False): + after: str + """Cursor for pagination. + + Provide the value from the previous response's `next` field to continue listing + roles. + """ + + limit: int + """A limit on the number of roles to return. Defaults to 1000.""" + + order: Literal["asc", "desc"] + """Sort order for the returned roles.""" diff --git a/src/openai/types/admin/organization/projects/role_update_params.py b/src/openai/types/admin/organization/projects/role_update_params.py new file mode 100644 index 0000000000..4d440c87cd --- /dev/null +++ b/src/openai/types/admin/organization/projects/role_update_params.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +from ....._types import SequenceNotStr + +__all__ = ["RoleUpdateParams"] + + +class RoleUpdateParams(TypedDict, total=False): + project_id: Required[str] + + description: Optional[str] + """New description for the role.""" + + permissions: Optional[SequenceNotStr[str]] + """Updated set of permissions for the role.""" + + role_name: Optional[str] + """New name for the role.""" diff --git a/src/openai/types/admin/organization/projects/service_account_create_params.py b/src/openai/types/admin/organization/projects/service_account_create_params.py new file mode 100644 index 0000000000..409dcba500 --- /dev/null +++ b/src/openai/types/admin/organization/projects/service_account_create_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ServiceAccountCreateParams"] + + +class ServiceAccountCreateParams(TypedDict, total=False): + name: Required[str] + """The name of the service account being created.""" diff --git a/src/openai/types/admin/organization/projects/service_account_create_response.py b/src/openai/types/admin/organization/projects/service_account_create_response.py new file mode 100644 index 0000000000..430b11f655 --- /dev/null +++ b/src/openai/types/admin/organization/projects/service_account_create_response.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ServiceAccountCreateResponse", "APIKey"] + + +class APIKey(BaseModel): + id: str + + created_at: int + + name: str + + object: Literal["organization.project.service_account.api_key"] + """The object type, which is always `organization.project.service_account.api_key`""" + + value: str + + +class ServiceAccountCreateResponse(BaseModel): + id: str + + api_key: Optional[APIKey] = None + + created_at: int + + name: str + + object: Literal["organization.project.service_account"] + + role: Literal["member"] + """Service accounts can only have one role of type `member`""" diff --git a/src/openai/types/admin/organization/projects/service_account_delete_response.py b/src/openai/types/admin/organization/projects/service_account_delete_response.py new file mode 100644 index 0000000000..e67e635aab --- /dev/null +++ b/src/openai/types/admin/organization/projects/service_account_delete_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["ServiceAccountDeleteResponse"] + + +class ServiceAccountDeleteResponse(BaseModel): + id: str + + deleted: bool + + object: Literal["organization.project.service_account.deleted"] diff --git a/src/openai/types/admin/organization/projects/service_account_list_params.py b/src/openai/types/admin/organization/projects/service_account_list_params.py new file mode 100644 index 0000000000..7f808e285a --- /dev/null +++ b/src/openai/types/admin/organization/projects/service_account_list_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ServiceAccountListParams"] + + +class ServiceAccountListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ diff --git a/src/openai/types/admin/organization/projects/service_account_update_params.py b/src/openai/types/admin/organization/projects/service_account_update_params.py new file mode 100644 index 0000000000..852e5d5a5a --- /dev/null +++ b/src/openai/types/admin/organization/projects/service_account_update_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ServiceAccountUpdateParams"] + + +class ServiceAccountUpdateParams(TypedDict, total=False): + project_id: Required[str] + + name: str + """The updated service account name.""" + + role: Literal["member", "owner"] + """The updated service account role.""" diff --git a/src/openai/types/admin/organization/projects/spend_alert_create_params.py b/src/openai/types/admin/organization/projects/spend_alert_create_params.py new file mode 100644 index 0000000000..74914d8646 --- /dev/null +++ b/src/openai/types/admin/organization/projects/spend_alert_create_params.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from ....._types import SequenceNotStr + +__all__ = ["SpendAlertCreateParams", "NotificationChannel"] + + +class SpendAlertCreateParams(TypedDict, total=False): + currency: Required[Literal["USD"]] + """The currency for the threshold amount.""" + + interval: Required[Literal["month"]] + """The time interval for evaluating spend against the threshold.""" + + notification_channel: Required[NotificationChannel] + """Email notification settings for a spend alert.""" + + threshold_amount: Required[int] + """The alert threshold amount, in cents.""" + + +class NotificationChannel(TypedDict, total=False): + """Email notification settings for a spend alert.""" + + recipients: Required[SequenceNotStr[str]] + """Email addresses that receive the spend alert notification.""" + + type: Required[Literal["email"]] + """The notification channel type. Currently only `email` is supported.""" + + subject_prefix: Optional[str] + """Optional subject prefix for alert emails.""" diff --git a/src/openai/types/admin/organization/projects/spend_alert_list_params.py b/src/openai/types/admin/organization/projects/spend_alert_list_params.py new file mode 100644 index 0000000000..a37bad5879 --- /dev/null +++ b/src/openai/types/admin/organization/projects/spend_alert_list_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["SpendAlertListParams"] + + +class SpendAlertListParams(TypedDict, total=False): + after: str + """Cursor for pagination. + + Provide the ID of the last spend alert from the previous response to fetch the + next page. + """ + + before: str + """Cursor for pagination. + + Provide the ID of the first spend alert from the previous response to fetch the + previous page. + """ + + limit: int + """A limit on the number of spend alerts to return. Defaults to 20.""" + + order: Literal["asc", "desc"] + """Sort order for the returned spend alerts.""" diff --git a/src/openai/types/admin/organization/projects/spend_alert_update_params.py b/src/openai/types/admin/organization/projects/spend_alert_update_params.py new file mode 100644 index 0000000000..1611c531e8 --- /dev/null +++ b/src/openai/types/admin/organization/projects/spend_alert_update_params.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from ....._types import SequenceNotStr + +__all__ = ["SpendAlertUpdateParams", "NotificationChannel"] + + +class SpendAlertUpdateParams(TypedDict, total=False): + project_id: Required[str] + + currency: Required[Literal["USD"]] + """The currency for the threshold amount.""" + + interval: Required[Literal["month"]] + """The time interval for evaluating spend against the threshold.""" + + notification_channel: Required[NotificationChannel] + """Email notification settings for a spend alert.""" + + threshold_amount: Required[int] + """The alert threshold amount, in cents.""" + + +class NotificationChannel(TypedDict, total=False): + """Email notification settings for a spend alert.""" + + recipients: Required[SequenceNotStr[str]] + """Email addresses that receive the spend alert notification.""" + + type: Required[Literal["email"]] + """The notification channel type. Currently only `email` is supported.""" + + subject_prefix: Optional[str] + """Optional subject prefix for alert emails.""" diff --git a/src/openai/types/admin/organization/projects/user_create_params.py b/src/openai/types/admin/organization/projects/user_create_params.py new file mode 100644 index 0000000000..4266ed01f9 --- /dev/null +++ b/src/openai/types/admin/organization/projects/user_create_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["UserCreateParams"] + + +class UserCreateParams(TypedDict, total=False): + role: Required[str] + """`owner` or `member`""" + + email: Optional[str] + """Email of the user to add.""" + + user_id: Optional[str] + """The ID of the user.""" diff --git a/src/openai/types/admin/organization/projects/user_delete_response.py b/src/openai/types/admin/organization/projects/user_delete_response.py new file mode 100644 index 0000000000..271d3a4126 --- /dev/null +++ b/src/openai/types/admin/organization/projects/user_delete_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ....._models import BaseModel + +__all__ = ["UserDeleteResponse"] + + +class UserDeleteResponse(BaseModel): + id: str + + deleted: bool + + object: Literal["organization.project.user.deleted"] diff --git a/src/openai/types/admin/organization/projects/user_list_params.py b/src/openai/types/admin/organization/projects/user_list_params.py new file mode 100644 index 0000000000..d561e907b1 --- /dev/null +++ b/src/openai/types/admin/organization/projects/user_list_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["UserListParams"] + + +class UserListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ diff --git a/src/openai/types/admin/organization/projects/user_update_params.py b/src/openai/types/admin/organization/projects/user_update_params.py new file mode 100644 index 0000000000..20a4276567 --- /dev/null +++ b/src/openai/types/admin/organization/projects/user_update_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["UserUpdateParams"] + + +class UserUpdateParams(TypedDict, total=False): + project_id: Required[str] + + role: Optional[str] + """`owner` or `member`""" diff --git a/src/openai/types/admin/organization/projects/users/__init__.py b/src/openai/types/admin/organization/projects/users/__init__.py new file mode 100644 index 0000000000..5e67517593 --- /dev/null +++ b/src/openai/types/admin/organization/projects/users/__init__.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .role_list_params import RoleListParams as RoleListParams +from .role_create_params import RoleCreateParams as RoleCreateParams +from .role_list_response import RoleListResponse as RoleListResponse +from .role_create_response import RoleCreateResponse as RoleCreateResponse +from .role_delete_response import RoleDeleteResponse as RoleDeleteResponse +from .role_retrieve_response import RoleRetrieveResponse as RoleRetrieveResponse diff --git a/src/openai/types/admin/organization/projects/users/role_create_params.py b/src/openai/types/admin/organization/projects/users/role_create_params.py new file mode 100644 index 0000000000..2aba01e8a4 --- /dev/null +++ b/src/openai/types/admin/organization/projects/users/role_create_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["RoleCreateParams"] + + +class RoleCreateParams(TypedDict, total=False): + project_id: Required[str] + + role_id: Required[str] + """Identifier of the role to assign.""" diff --git a/src/openai/types/admin/organization/projects/users/role_create_response.py b/src/openai/types/admin/organization/projects/users/role_create_response.py new file mode 100644 index 0000000000..533df36500 --- /dev/null +++ b/src/openai/types/admin/organization/projects/users/role_create_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...role import Role +from ......_models import BaseModel +from ...organization_user import OrganizationUser + +__all__ = ["RoleCreateResponse"] + + +class RoleCreateResponse(BaseModel): + """Role assignment linking a user to a role.""" + + object: Literal["user.role"] + """Always `user.role`.""" + + role: Role + """Details about a role that can be assigned through the public Roles API.""" + + user: OrganizationUser + """Represents an individual `user` within an organization.""" diff --git a/src/openai/types/admin/organization/projects/users/role_delete_response.py b/src/openai/types/admin/organization/projects/users/role_delete_response.py new file mode 100644 index 0000000000..704de05117 --- /dev/null +++ b/src/openai/types/admin/organization/projects/users/role_delete_response.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ......_models import BaseModel + +__all__ = ["RoleDeleteResponse"] + + +class RoleDeleteResponse(BaseModel): + """Confirmation payload returned after unassigning a role.""" + + deleted: bool + """Whether the assignment was removed.""" + + object: str + """ + Identifier for the deleted assignment, such as `group.role.deleted` or + `user.role.deleted`. + """ diff --git a/src/openai/types/admin/organization/projects/users/role_list_params.py b/src/openai/types/admin/organization/projects/users/role_list_params.py new file mode 100644 index 0000000000..ffdbe210d2 --- /dev/null +++ b/src/openai/types/admin/organization/projects/users/role_list_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RoleListParams"] + + +class RoleListParams(TypedDict, total=False): + project_id: Required[str] + + after: str + """Cursor for pagination. + + Provide the value from the previous response's `next` field to continue listing + project roles. + """ + + limit: int + """A limit on the number of project role assignments to return.""" + + order: Literal["asc", "desc"] + """Sort order for the returned project roles.""" diff --git a/src/openai/types/admin/organization/projects/users/role_list_response.py b/src/openai/types/admin/organization/projects/users/role_list_response.py new file mode 100644 index 0000000000..d43d0f806b --- /dev/null +++ b/src/openai/types/admin/organization/projects/users/role_list_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from ......_models import BaseModel + +__all__ = ["RoleListResponse", "AssignmentSource"] + + +class AssignmentSource(BaseModel): + principal_id: str + + principal_type: str + + +class RoleListResponse(BaseModel): + """ + Detailed information about a role assignment entry returned when listing assignments. + """ + + id: str + """Identifier for the role.""" + + assignment_sources: Optional[List[AssignmentSource]] = None + """Principals from which the role assignment is inherited, when available.""" + + created_at: Optional[int] = None + """When the role was created.""" + + created_by: Optional[str] = None + """Identifier of the actor who created the role.""" + + created_by_user_obj: Optional[Dict[str, object]] = None + """User details for the actor that created the role, when available.""" + + description: Optional[str] = None + """Description of the role.""" + + metadata: Optional[Dict[str, object]] = None + """Arbitrary metadata stored on the role.""" + + name: str + """Name of the role.""" + + permissions: List[str] + """Permissions associated with the role.""" + + predefined_role: bool + """Whether the role is predefined by OpenAI.""" + + resource_type: str + """Resource type the role applies to.""" + + updated_at: Optional[int] = None + """When the role was last updated.""" diff --git a/src/openai/types/admin/organization/projects/users/role_retrieve_response.py b/src/openai/types/admin/organization/projects/users/role_retrieve_response.py new file mode 100644 index 0000000000..0c10cf0092 --- /dev/null +++ b/src/openai/types/admin/organization/projects/users/role_retrieve_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from ......_models import BaseModel + +__all__ = ["RoleRetrieveResponse", "AssignmentSource"] + + +class AssignmentSource(BaseModel): + principal_id: str + + principal_type: str + + +class RoleRetrieveResponse(BaseModel): + """ + Detailed information about a role assignment entry returned when listing assignments. + """ + + id: str + """Identifier for the role.""" + + assignment_sources: Optional[List[AssignmentSource]] = None + """Principals from which the role assignment is inherited, when available.""" + + created_at: Optional[int] = None + """When the role was created.""" + + created_by: Optional[str] = None + """Identifier of the actor who created the role.""" + + created_by_user_obj: Optional[Dict[str, object]] = None + """User details for the actor that created the role, when available.""" + + description: Optional[str] = None + """Description of the role.""" + + metadata: Optional[Dict[str, object]] = None + """Arbitrary metadata stored on the role.""" + + name: str + """Name of the role.""" + + permissions: List[str] + """Permissions associated with the role.""" + + predefined_role: bool + """Whether the role is predefined by OpenAI.""" + + resource_type: str + """Resource type the role applies to.""" + + updated_at: Optional[int] = None + """When the role was last updated.""" diff --git a/src/openai/types/admin/organization/role.py b/src/openai/types/admin/organization/role.py new file mode 100644 index 0000000000..4795144c68 --- /dev/null +++ b/src/openai/types/admin/organization/role.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["Role"] + + +class Role(BaseModel): + """Details about a role that can be assigned through the public Roles API.""" + + id: str + """Identifier for the role.""" + + description: Optional[str] = None + """Optional description of the role.""" + + name: str + """Unique name for the role.""" + + object: Literal["role"] + """Always `role`.""" + + permissions: List[str] + """Permissions granted by the role.""" + + predefined_role: bool + """Whether the role is predefined and managed by OpenAI.""" + + resource_type: str + """ + Resource type the role is bound to (for example `api.organization` or + `api.project`). + """ diff --git a/src/openai/types/admin/organization/role_create_params.py b/src/openai/types/admin/organization/role_create_params.py new file mode 100644 index 0000000000..60aaeb7383 --- /dev/null +++ b/src/openai/types/admin/organization/role_create_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["RoleCreateParams"] + + +class RoleCreateParams(TypedDict, total=False): + permissions: Required[SequenceNotStr[str]] + """Permissions to grant to the role.""" + + role_name: Required[str] + """Unique name for the role.""" + + description: Optional[str] + """Optional description of the role.""" diff --git a/src/openai/types/admin/organization/role_delete_response.py b/src/openai/types/admin/organization/role_delete_response.py new file mode 100644 index 0000000000..934140d363 --- /dev/null +++ b/src/openai/types/admin/organization/role_delete_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["RoleDeleteResponse"] + + +class RoleDeleteResponse(BaseModel): + """Confirmation payload returned after deleting a role.""" + + id: str + """Identifier of the deleted role.""" + + deleted: bool + """Whether the role was deleted.""" + + object: Literal["role.deleted"] + """Always `role.deleted`.""" diff --git a/src/openai/types/admin/organization/role_list_params.py b/src/openai/types/admin/organization/role_list_params.py new file mode 100644 index 0000000000..88e957f886 --- /dev/null +++ b/src/openai/types/admin/organization/role_list_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["RoleListParams"] + + +class RoleListParams(TypedDict, total=False): + after: str + """Cursor for pagination. + + Provide the value from the previous response's `next` field to continue listing + roles. + """ + + limit: int + """A limit on the number of roles to return. Defaults to 1000.""" + + order: Literal["asc", "desc"] + """Sort order for the returned roles.""" diff --git a/src/openai/types/admin/organization/role_update_params.py b/src/openai/types/admin/organization/role_update_params.py new file mode 100644 index 0000000000..955ca170a8 --- /dev/null +++ b/src/openai/types/admin/organization/role_update_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["RoleUpdateParams"] + + +class RoleUpdateParams(TypedDict, total=False): + description: Optional[str] + """New description for the role.""" + + permissions: Optional[SequenceNotStr[str]] + """Updated set of permissions for the role.""" + + role_name: Optional[str] + """New name for the role.""" diff --git a/src/openai/types/admin/organization/spend_alert_create_params.py b/src/openai/types/admin/organization/spend_alert_create_params.py new file mode 100644 index 0000000000..f787abdfe1 --- /dev/null +++ b/src/openai/types/admin/organization/spend_alert_create_params.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["SpendAlertCreateParams", "NotificationChannel"] + + +class SpendAlertCreateParams(TypedDict, total=False): + currency: Required[Literal["USD"]] + """The currency for the threshold amount.""" + + interval: Required[Literal["month"]] + """The time interval for evaluating spend against the threshold.""" + + notification_channel: Required[NotificationChannel] + """Email notification settings for a spend alert.""" + + threshold_amount: Required[int] + """The alert threshold amount, in cents.""" + + +class NotificationChannel(TypedDict, total=False): + """Email notification settings for a spend alert.""" + + recipients: Required[SequenceNotStr[str]] + """Email addresses that receive the spend alert notification.""" + + type: Required[Literal["email"]] + """The notification channel type. Currently only `email` is supported.""" + + subject_prefix: Optional[str] + """Optional subject prefix for alert emails.""" diff --git a/src/openai/types/admin/organization/spend_alert_list_params.py b/src/openai/types/admin/organization/spend_alert_list_params.py new file mode 100644 index 0000000000..a37bad5879 --- /dev/null +++ b/src/openai/types/admin/organization/spend_alert_list_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["SpendAlertListParams"] + + +class SpendAlertListParams(TypedDict, total=False): + after: str + """Cursor for pagination. + + Provide the ID of the last spend alert from the previous response to fetch the + next page. + """ + + before: str + """Cursor for pagination. + + Provide the ID of the first spend alert from the previous response to fetch the + previous page. + """ + + limit: int + """A limit on the number of spend alerts to return. Defaults to 20.""" + + order: Literal["asc", "desc"] + """Sort order for the returned spend alerts.""" diff --git a/src/openai/types/admin/organization/spend_alert_update_params.py b/src/openai/types/admin/organization/spend_alert_update_params.py new file mode 100644 index 0000000000..ddba8a81e1 --- /dev/null +++ b/src/openai/types/admin/organization/spend_alert_update_params.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["SpendAlertUpdateParams", "NotificationChannel"] + + +class SpendAlertUpdateParams(TypedDict, total=False): + currency: Required[Literal["USD"]] + """The currency for the threshold amount.""" + + interval: Required[Literal["month"]] + """The time interval for evaluating spend against the threshold.""" + + notification_channel: Required[NotificationChannel] + """Email notification settings for a spend alert.""" + + threshold_amount: Required[int] + """The alert threshold amount, in cents.""" + + +class NotificationChannel(TypedDict, total=False): + """Email notification settings for a spend alert.""" + + recipients: Required[SequenceNotStr[str]] + """Email addresses that receive the spend alert notification.""" + + type: Required[Literal["email"]] + """The notification channel type. Currently only `email` is supported.""" + + subject_prefix: Optional[str] + """Optional subject prefix for alert emails.""" diff --git a/src/openai/types/admin/organization/usage_audio_speeches_params.py b/src/openai/types/admin/organization/usage_audio_speeches_params.py new file mode 100644 index 0000000000..5d3eff286c --- /dev/null +++ b/src/openai/types/admin/organization/usage_audio_speeches_params.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageAudioSpeechesParams"] + + +class UsageAudioSpeechesParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + api_key_ids: SequenceNotStr[str] + """Return only usage for these API keys.""" + + bucket_width: Literal["1m", "1h", "1d"] + """Width of each time bucket in response. + + Currently `1m`, `1h` and `1d` are supported, default to `1d`. + """ + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] + """Group the usage data by the specified fields. + + Support fields include `project_id`, `user_id`, `api_key_id`, `model` or any + combination of them. + """ + + limit: int + """Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + """ + + models: SequenceNotStr[str] + """Return only usage for these models.""" + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only usage for these projects.""" + + user_ids: SequenceNotStr[str] + """Return only usage for these users.""" diff --git a/src/openai/types/admin/organization/usage_audio_speeches_response.py b/src/openai/types/admin/organization/usage_audio_speeches_response.py new file mode 100644 index 0000000000..f99a73eeb0 --- /dev/null +++ b/src/openai/types/admin/organization/usage_audio_speeches_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageAudioSpeechesResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageAudioSpeechesResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/usage_audio_transcriptions_params.py b/src/openai/types/admin/organization/usage_audio_transcriptions_params.py new file mode 100644 index 0000000000..ca5cd13c78 --- /dev/null +++ b/src/openai/types/admin/organization/usage_audio_transcriptions_params.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageAudioTranscriptionsParams"] + + +class UsageAudioTranscriptionsParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + api_key_ids: SequenceNotStr[str] + """Return only usage for these API keys.""" + + bucket_width: Literal["1m", "1h", "1d"] + """Width of each time bucket in response. + + Currently `1m`, `1h` and `1d` are supported, default to `1d`. + """ + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] + """Group the usage data by the specified fields. + + Support fields include `project_id`, `user_id`, `api_key_id`, `model` or any + combination of them. + """ + + limit: int + """Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + """ + + models: SequenceNotStr[str] + """Return only usage for these models.""" + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only usage for these projects.""" + + user_ids: SequenceNotStr[str] + """Return only usage for these users.""" diff --git a/src/openai/types/admin/organization/usage_audio_transcriptions_response.py b/src/openai/types/admin/organization/usage_audio_transcriptions_response.py new file mode 100644 index 0000000000..d8f436b0b5 --- /dev/null +++ b/src/openai/types/admin/organization/usage_audio_transcriptions_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageAudioTranscriptionsResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageAudioTranscriptionsResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/usage_code_interpreter_sessions_params.py b/src/openai/types/admin/organization/usage_code_interpreter_sessions_params.py new file mode 100644 index 0000000000..dd4b59c2ab --- /dev/null +++ b/src/openai/types/admin/organization/usage_code_interpreter_sessions_params.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageCodeInterpreterSessionsParams"] + + +class UsageCodeInterpreterSessionsParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + bucket_width: Literal["1m", "1h", "1d"] + """Width of each time bucket in response. + + Currently `1m`, `1h` and `1d` are supported, default to `1d`. + """ + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id"]] + """Group the usage data by the specified fields. + + Support fields include `project_id`. + """ + + limit: int + """Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + """ + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only usage for these projects.""" diff --git a/src/openai/types/admin/organization/usage_code_interpreter_sessions_response.py b/src/openai/types/admin/organization/usage_code_interpreter_sessions_response.py new file mode 100644 index 0000000000..0c92f61446 --- /dev/null +++ b/src/openai/types/admin/organization/usage_code_interpreter_sessions_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageCodeInterpreterSessionsResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageCodeInterpreterSessionsResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/usage_completions_params.py b/src/openai/types/admin/organization/usage_completions_params.py new file mode 100644 index 0000000000..e7dbe2187e --- /dev/null +++ b/src/openai/types/admin/organization/usage_completions_params.py @@ -0,0 +1,63 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageCompletionsParams"] + + +class UsageCompletionsParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + api_key_ids: SequenceNotStr[str] + """Return only usage for these API keys.""" + + batch: bool + """If `true`, return batch jobs only. + + If `false`, return non-batch jobs only. By default, return both. + """ + + bucket_width: Literal["1m", "1h", "1d"] + """Width of each time bucket in response. + + Currently `1m`, `1h` and `1d` are supported, default to `1d`. + """ + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "batch", "service_tier"]] + """Group the usage data by the specified fields. + + Support fields include `project_id`, `user_id`, `api_key_id`, `model`, `batch`, + `service_tier` or any combination of them. + """ + + limit: int + """Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + """ + + models: SequenceNotStr[str] + """Return only usage for these models.""" + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only usage for these projects.""" + + user_ids: SequenceNotStr[str] + """Return only usage for these users.""" diff --git a/src/openai/types/admin/organization/usage_completions_response.py b/src/openai/types/admin/organization/usage_completions_response.py new file mode 100644 index 0000000000..57f0b699ec --- /dev/null +++ b/src/openai/types/admin/organization/usage_completions_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageCompletionsResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageCompletionsResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/usage_costs_params.py b/src/openai/types/admin/organization/usage_costs_params.py new file mode 100644 index 0000000000..e848643339 --- /dev/null +++ b/src/openai/types/admin/organization/usage_costs_params.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageCostsParams"] + + +class UsageCostsParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + api_key_ids: SequenceNotStr[str] + """Return only costs for these API keys.""" + + bucket_width: Literal["1d"] + """Width of each time bucket in response. + + Currently only `1d` is supported, default to `1d`. + """ + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id", "line_item", "api_key_id"]] + """Group the costs by the specified fields. + + Support fields include `project_id`, `line_item`, `api_key_id` and any + combination of them. + """ + + limit: int + """A limit on the number of buckets to be returned. + + Limit can range between 1 and 180, and the default is 7. + """ + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only costs for these projects.""" diff --git a/src/openai/types/admin/organization/usage_costs_response.py b/src/openai/types/admin/organization/usage_costs_response.py new file mode 100644 index 0000000000..71eee1188a --- /dev/null +++ b/src/openai/types/admin/organization/usage_costs_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageCostsResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageCostsResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/usage_embeddings_params.py b/src/openai/types/admin/organization/usage_embeddings_params.py new file mode 100644 index 0000000000..56c9107b51 --- /dev/null +++ b/src/openai/types/admin/organization/usage_embeddings_params.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageEmbeddingsParams"] + + +class UsageEmbeddingsParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + api_key_ids: SequenceNotStr[str] + """Return only usage for these API keys.""" + + bucket_width: Literal["1m", "1h", "1d"] + """Width of each time bucket in response. + + Currently `1m`, `1h` and `1d` are supported, default to `1d`. + """ + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] + """Group the usage data by the specified fields. + + Support fields include `project_id`, `user_id`, `api_key_id`, `model` or any + combination of them. + """ + + limit: int + """Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + """ + + models: SequenceNotStr[str] + """Return only usage for these models.""" + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only usage for these projects.""" + + user_ids: SequenceNotStr[str] + """Return only usage for these users.""" diff --git a/src/openai/types/admin/organization/usage_embeddings_response.py b/src/openai/types/admin/organization/usage_embeddings_response.py new file mode 100644 index 0000000000..69697ee4e7 --- /dev/null +++ b/src/openai/types/admin/organization/usage_embeddings_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageEmbeddingsResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageEmbeddingsResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/usage_file_search_calls_params.py b/src/openai/types/admin/organization/usage_file_search_calls_params.py new file mode 100644 index 0000000000..914d568c33 --- /dev/null +++ b/src/openai/types/admin/organization/usage_file_search_calls_params.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageFileSearchCallsParams"] + + +class UsageFileSearchCallsParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + api_key_ids: SequenceNotStr[str] + """Return only usage for these API keys.""" + + bucket_width: Literal["1m", "1h", "1d"] + """Width of each time bucket in response. + + Currently `1m`, `1h` and `1d` are supported, default to `1d`. + """ + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id", "user_id", "api_key_id", "vector_store_id"]] + """Group the usage data by the specified fields. + + Support fields include `project_id`, `user_id`, `api_key_id`, `vector_store_id` + or any combination of them. + """ + + limit: int + """Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + """ + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only usage for these projects.""" + + user_ids: SequenceNotStr[str] + """Return only usage for these users.""" + + vector_store_ids: SequenceNotStr[str] + """Return only usage for these vector stores.""" diff --git a/src/openai/types/admin/organization/usage_file_search_calls_response.py b/src/openai/types/admin/organization/usage_file_search_calls_response.py new file mode 100644 index 0000000000..0e093b8346 --- /dev/null +++ b/src/openai/types/admin/organization/usage_file_search_calls_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageFileSearchCallsResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageFileSearchCallsResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/usage_images_params.py b/src/openai/types/admin/organization/usage_images_params.py new file mode 100644 index 0000000000..4cce8c456a --- /dev/null +++ b/src/openai/types/admin/organization/usage_images_params.py @@ -0,0 +1,71 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageImagesParams"] + + +class UsageImagesParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + api_key_ids: SequenceNotStr[str] + """Return only usage for these API keys.""" + + bucket_width: Literal["1m", "1h", "1d"] + """Width of each time bucket in response. + + Currently `1m`, `1h` and `1d` are supported, default to `1d`. + """ + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "size", "source"]] + """Group the usage data by the specified fields. + + Support fields include `project_id`, `user_id`, `api_key_id`, `model`, `size`, + `source` or any combination of them. + """ + + limit: int + """Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + """ + + models: SequenceNotStr[str] + """Return only usage for these models.""" + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only usage for these projects.""" + + sizes: List[Literal["256x256", "512x512", "1024x1024", "1792x1792", "1024x1792"]] + """Return only usages for these image sizes. + + Possible values are `256x256`, `512x512`, `1024x1024`, `1792x1792`, `1024x1792` + or any combination of them. + """ + + sources: List[Literal["image.generation", "image.edit", "image.variation"]] + """Return only usages for these sources. + + Possible values are `image.generation`, `image.edit`, `image.variation` or any + combination of them. + """ + + user_ids: SequenceNotStr[str] + """Return only usage for these users.""" diff --git a/src/openai/types/admin/organization/usage_images_response.py b/src/openai/types/admin/organization/usage_images_response.py new file mode 100644 index 0000000000..33c3ebb130 --- /dev/null +++ b/src/openai/types/admin/organization/usage_images_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageImagesResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageImagesResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/usage_moderations_params.py b/src/openai/types/admin/organization/usage_moderations_params.py new file mode 100644 index 0000000000..acb401b9a8 --- /dev/null +++ b/src/openai/types/admin/organization/usage_moderations_params.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageModerationsParams"] + + +class UsageModerationsParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + api_key_ids: SequenceNotStr[str] + """Return only usage for these API keys.""" + + bucket_width: Literal["1m", "1h", "1d"] + """Width of each time bucket in response. + + Currently `1m`, `1h` and `1d` are supported, default to `1d`. + """ + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id", "user_id", "api_key_id", "model"]] + """Group the usage data by the specified fields. + + Support fields include `project_id`, `user_id`, `api_key_id`, `model` or any + combination of them. + """ + + limit: int + """Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + """ + + models: SequenceNotStr[str] + """Return only usage for these models.""" + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only usage for these projects.""" + + user_ids: SequenceNotStr[str] + """Return only usage for these users.""" diff --git a/src/openai/types/admin/organization/usage_moderations_response.py b/src/openai/types/admin/organization/usage_moderations_response.py new file mode 100644 index 0000000000..3aa5d9193d --- /dev/null +++ b/src/openai/types/admin/organization/usage_moderations_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageModerationsResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageModerationsResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/usage_vector_stores_params.py b/src/openai/types/admin/organization/usage_vector_stores_params.py new file mode 100644 index 0000000000..bfb8dcede4 --- /dev/null +++ b/src/openai/types/admin/organization/usage_vector_stores_params.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageVectorStoresParams"] + + +class UsageVectorStoresParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + bucket_width: Literal["1m", "1h", "1d"] + """Width of each time bucket in response. + + Currently `1m`, `1h` and `1d` are supported, default to `1d`. + """ + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id"]] + """Group the usage data by the specified fields. + + Support fields include `project_id`. + """ + + limit: int + """Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + """ + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only usage for these projects.""" diff --git a/src/openai/types/admin/organization/usage_vector_stores_response.py b/src/openai/types/admin/organization/usage_vector_stores_response.py new file mode 100644 index 0000000000..a5ecc31185 --- /dev/null +++ b/src/openai/types/admin/organization/usage_vector_stores_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageVectorStoresResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageVectorStoresResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/usage_web_search_calls_params.py b/src/openai/types/admin/organization/usage_web_search_calls_params.py new file mode 100644 index 0000000000..544060ade4 --- /dev/null +++ b/src/openai/types/admin/organization/usage_web_search_calls_params.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UsageWebSearchCallsParams"] + + +class UsageWebSearchCallsParams(TypedDict, total=False): + start_time: Required[int] + """Start time (Unix seconds) of the query time range, inclusive.""" + + api_key_ids: SequenceNotStr[str] + """Return only usage for these API keys.""" + + bucket_width: Literal["1m", "1h", "1d"] + """Width of each time bucket in response. + + Currently `1m`, `1h` and `1d` are supported, default to `1d`. + """ + + context_levels: List[Literal["low", "medium", "high"]] + """Return only web search usage for these context levels.""" + + end_time: int + """End time (Unix seconds) of the query time range, exclusive.""" + + group_by: List[Literal["project_id", "user_id", "api_key_id", "model", "context_level"]] + """Group the usage data by the specified fields. + + Support fields include `project_id`, `user_id`, `api_key_id`, `model`, + `context_level` or any combination of them. + """ + + limit: int + """Specifies the number of buckets to return. + + - `bucket_width=1d`: default: 7, max: 31 + - `bucket_width=1h`: default: 24, max: 168 + - `bucket_width=1m`: default: 60, max: 1440 + """ + + models: SequenceNotStr[str] + """Return only usage for these models.""" + + page: str + """A cursor for use in pagination. + + Corresponding to the `next_page` field from the previous response. + """ + + project_ids: SequenceNotStr[str] + """Return only usage for these projects.""" + + user_ids: SequenceNotStr[str] + """Return only usage for these users.""" diff --git a/src/openai/types/admin/organization/usage_web_search_calls_response.py b/src/openai/types/admin/organization/usage_web_search_calls_response.py new file mode 100644 index 0000000000..b6fcd4ac6c --- /dev/null +++ b/src/openai/types/admin/organization/usage_web_search_calls_response.py @@ -0,0 +1,475 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "UsageWebSearchCallsResponse", + "Data", + "DataResult", + "DataResultOrganizationUsageCompletionsResult", + "DataResultOrganizationUsageEmbeddingsResult", + "DataResultOrganizationUsageModerationsResult", + "DataResultOrganizationUsageImagesResult", + "DataResultOrganizationUsageAudioSpeechesResult", + "DataResultOrganizationUsageAudioTranscriptionsResult", + "DataResultOrganizationUsageVectorStoresResult", + "DataResultOrganizationUsageCodeInterpreterSessionsResult", + "DataResultOrganizationUsageFileSearchesResult", + "DataResultOrganizationUsageWebSearchesResult", + "DataResultOrganizationCostsResult", + "DataResultOrganizationCostsResultAmount", +] + + +class DataResultOrganizationUsageCompletionsResult(BaseModel): + """The aggregated completions usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of text input tokens used, including cached tokens. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.completions.result"] + + output_tokens: int + """The aggregated number of text output tokens used. + + For customers subscribe to scale tier, this includes scale tier tokens. + """ + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + batch: Optional[bool] = None + """ + When `group_by=batch`, this field tells whether the grouped usage result is + batch or not. + """ + + input_audio_tokens: Optional[int] = None + """The aggregated number of audio input tokens used, including cached tokens.""" + + input_cached_tokens: Optional[int] = None + """ + The aggregated number of text input tokens that has been cached from previous + requests. For customers subscribe to scale tier, this includes scale tier + tokens. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + output_audio_tokens: Optional[int] = None + """The aggregated number of audio output tokens used.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + service_tier: Optional[str] = None + """ + When `group_by=service_tier`, this field provides the service tier of the + grouped usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageEmbeddingsResult(BaseModel): + """The aggregated embeddings usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.embeddings.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageModerationsResult(BaseModel): + """The aggregated moderations usage details of the specific time bucket.""" + + input_tokens: int + """The aggregated number of input tokens used.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.moderations.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageImagesResult(BaseModel): + """The aggregated images usage details of the specific time bucket.""" + + images: int + """The number of images processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.images.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + size: Optional[str] = None + """ + When `group_by=size`, this field provides the image size of the grouped usage + result. + """ + + source: Optional[str] = None + """ + When `group_by=source`, this field provides the source of the grouped usage + result, possible values are `image.generation`, `image.edit`, `image.variation`. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioSpeechesResult(BaseModel): + """The aggregated audio speeches usage details of the specific time bucket.""" + + characters: int + """The number of characters processed.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_speeches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageAudioTranscriptionsResult(BaseModel): + """The aggregated audio transcriptions usage details of the specific time bucket.""" + + num_model_requests: int + """The count of requests made to the model.""" + + object: Literal["organization.usage.audio_transcriptions.result"] + + seconds: int + """The number of seconds processed.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationUsageVectorStoresResult(BaseModel): + """The aggregated vector stores usage details of the specific time bucket.""" + + object: Literal["organization.usage.vector_stores.result"] + + usage_bytes: int + """The vector stores usage in bytes.""" + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageCodeInterpreterSessionsResult(BaseModel): + """ + The aggregated code interpreter sessions usage details of the specific time bucket. + """ + + num_sessions: int + """The number of code interpreter sessions.""" + + object: Literal["organization.usage.code_interpreter_sessions.result"] + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + +class DataResultOrganizationUsageFileSearchesResult(BaseModel): + """The aggregated file search calls usage details of the specific time bucket.""" + + num_requests: int + """The count of file search calls.""" + + object: Literal["organization.usage.file_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + vector_store_id: Optional[str] = None + """ + When `group_by=vector_store_id`, this field provides the vector store ID of the + grouped usage result. + """ + + +class DataResultOrganizationUsageWebSearchesResult(BaseModel): + """The aggregated web search calls usage details of the specific time bucket.""" + + num_model_requests: int + """The count of model requests.""" + + num_requests: int + """The count of web search calls.""" + + object: Literal["organization.usage.web_searches.result"] + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API key ID of the grouped + usage result. + """ + + context_level: Optional[str] = None + """ + When `group_by=context_level`, this field provides the search context size of + the grouped usage result. + """ + + model: Optional[str] = None + """ + When `group_by=model`, this field provides the model name of the grouped usage + result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + usage result. + """ + + user_id: Optional[str] = None + """ + When `group_by=user_id`, this field provides the user ID of the grouped usage + result. + """ + + +class DataResultOrganizationCostsResultAmount(BaseModel): + """The monetary value in its associated currency.""" + + currency: Optional[str] = None + """Lowercase ISO-4217 currency e.g. "usd" """ + + value: Optional[float] = None + """The numeric value of the cost.""" + + +class DataResultOrganizationCostsResult(BaseModel): + """The aggregated costs details of the specific time bucket.""" + + object: Literal["organization.costs.result"] + + amount: Optional[DataResultOrganizationCostsResultAmount] = None + """The monetary value in its associated currency.""" + + api_key_id: Optional[str] = None + """ + When `group_by=api_key_id`, this field provides the API Key ID of the grouped + costs result. + """ + + line_item: Optional[str] = None + """ + When `group_by=line_item`, this field provides the line item of the grouped + costs result. + """ + + project_id: Optional[str] = None + """ + When `group_by=project_id`, this field provides the project ID of the grouped + costs result. + """ + + quantity: Optional[float] = None + """ + When `group_by=line_item`, this field provides the quantity of the grouped costs + result. + """ + + +DataResult: TypeAlias = Annotated[ + Union[ + DataResultOrganizationUsageCompletionsResult, + DataResultOrganizationUsageEmbeddingsResult, + DataResultOrganizationUsageModerationsResult, + DataResultOrganizationUsageImagesResult, + DataResultOrganizationUsageAudioSpeechesResult, + DataResultOrganizationUsageAudioTranscriptionsResult, + DataResultOrganizationUsageVectorStoresResult, + DataResultOrganizationUsageCodeInterpreterSessionsResult, + DataResultOrganizationUsageFileSearchesResult, + DataResultOrganizationUsageWebSearchesResult, + DataResultOrganizationCostsResult, + ], + PropertyInfo(discriminator="object"), +] + + +class Data(BaseModel): + end_time: int + + object: Literal["bucket"] + + results: List[DataResult] + + start_time: int + + +class UsageWebSearchCallsResponse(BaseModel): + data: List[Data] + + has_more: bool + + next_page: Optional[str] = None + + object: Literal["page"] diff --git a/src/openai/types/admin/organization/user_delete_response.py b/src/openai/types/admin/organization/user_delete_response.py new file mode 100644 index 0000000000..b8fc8c994a --- /dev/null +++ b/src/openai/types/admin/organization/user_delete_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["UserDeleteResponse"] + + +class UserDeleteResponse(BaseModel): + id: str + + deleted: bool + + object: Literal["organization.user.deleted"] diff --git a/src/openai/types/admin/organization/user_list_params.py b/src/openai/types/admin/organization/user_list_params.py new file mode 100644 index 0000000000..7bcfc78979 --- /dev/null +++ b/src/openai/types/admin/organization/user_list_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["UserListParams"] + + +class UserListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + emails: SequenceNotStr[str] + """Filter by the email address of users.""" + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ diff --git a/src/openai/types/admin/organization/user_update_params.py b/src/openai/types/admin/organization/user_update_params.py new file mode 100644 index 0000000000..24181b0649 --- /dev/null +++ b/src/openai/types/admin/organization/user_update_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["UserUpdateParams"] + + +class UserUpdateParams(TypedDict, total=False): + developer_persona: Optional[str] + """Developer persona metadata.""" + + role: Optional[str] + """`owner` or `reader`""" + + role_id: Optional[str] + """Role ID to assign to the user.""" + + technical_level: Optional[str] + """Technical level metadata.""" diff --git a/src/openai/types/admin/organization/users/__init__.py b/src/openai/types/admin/organization/users/__init__.py new file mode 100644 index 0000000000..5e67517593 --- /dev/null +++ b/src/openai/types/admin/organization/users/__init__.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .role_list_params import RoleListParams as RoleListParams +from .role_create_params import RoleCreateParams as RoleCreateParams +from .role_list_response import RoleListResponse as RoleListResponse +from .role_create_response import RoleCreateResponse as RoleCreateResponse +from .role_delete_response import RoleDeleteResponse as RoleDeleteResponse +from .role_retrieve_response import RoleRetrieveResponse as RoleRetrieveResponse diff --git a/src/openai/types/admin/organization/users/role_create_params.py b/src/openai/types/admin/organization/users/role_create_params.py new file mode 100644 index 0000000000..0ebc196eef --- /dev/null +++ b/src/openai/types/admin/organization/users/role_create_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["RoleCreateParams"] + + +class RoleCreateParams(TypedDict, total=False): + role_id: Required[str] + """Identifier of the role to assign.""" diff --git a/src/openai/types/admin/organization/users/role_create_response.py b/src/openai/types/admin/organization/users/role_create_response.py new file mode 100644 index 0000000000..0b989ad461 --- /dev/null +++ b/src/openai/types/admin/organization/users/role_create_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..role import Role +from ....._models import BaseModel +from ..organization_user import OrganizationUser + +__all__ = ["RoleCreateResponse"] + + +class RoleCreateResponse(BaseModel): + """Role assignment linking a user to a role.""" + + object: Literal["user.role"] + """Always `user.role`.""" + + role: Role + """Details about a role that can be assigned through the public Roles API.""" + + user: OrganizationUser + """Represents an individual `user` within an organization.""" diff --git a/src/openai/types/admin/organization/users/role_delete_response.py b/src/openai/types/admin/organization/users/role_delete_response.py new file mode 100644 index 0000000000..fb6a111614 --- /dev/null +++ b/src/openai/types/admin/organization/users/role_delete_response.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ....._models import BaseModel + +__all__ = ["RoleDeleteResponse"] + + +class RoleDeleteResponse(BaseModel): + """Confirmation payload returned after unassigning a role.""" + + deleted: bool + """Whether the assignment was removed.""" + + object: str + """ + Identifier for the deleted assignment, such as `group.role.deleted` or + `user.role.deleted`. + """ diff --git a/src/openai/types/admin/organization/users/role_list_params.py b/src/openai/types/admin/organization/users/role_list_params.py new file mode 100644 index 0000000000..451a1a2045 --- /dev/null +++ b/src/openai/types/admin/organization/users/role_list_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["RoleListParams"] + + +class RoleListParams(TypedDict, total=False): + after: str + """Cursor for pagination. + + Provide the value from the previous response's `next` field to continue listing + organization roles. + """ + + limit: int + """A limit on the number of organization role assignments to return.""" + + order: Literal["asc", "desc"] + """Sort order for the returned organization roles.""" diff --git a/src/openai/types/admin/organization/users/role_list_response.py b/src/openai/types/admin/organization/users/role_list_response.py new file mode 100644 index 0000000000..fc64f8e9a0 --- /dev/null +++ b/src/openai/types/admin/organization/users/role_list_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from ....._models import BaseModel + +__all__ = ["RoleListResponse", "AssignmentSource"] + + +class AssignmentSource(BaseModel): + principal_id: str + + principal_type: str + + +class RoleListResponse(BaseModel): + """ + Detailed information about a role assignment entry returned when listing assignments. + """ + + id: str + """Identifier for the role.""" + + assignment_sources: Optional[List[AssignmentSource]] = None + """Principals from which the role assignment is inherited, when available.""" + + created_at: Optional[int] = None + """When the role was created.""" + + created_by: Optional[str] = None + """Identifier of the actor who created the role.""" + + created_by_user_obj: Optional[Dict[str, object]] = None + """User details for the actor that created the role, when available.""" + + description: Optional[str] = None + """Description of the role.""" + + metadata: Optional[Dict[str, object]] = None + """Arbitrary metadata stored on the role.""" + + name: str + """Name of the role.""" + + permissions: List[str] + """Permissions associated with the role.""" + + predefined_role: bool + """Whether the role is predefined by OpenAI.""" + + resource_type: str + """Resource type the role applies to.""" + + updated_at: Optional[int] = None + """When the role was last updated.""" diff --git a/src/openai/types/admin/organization/users/role_retrieve_response.py b/src/openai/types/admin/organization/users/role_retrieve_response.py new file mode 100644 index 0000000000..576010daad --- /dev/null +++ b/src/openai/types/admin/organization/users/role_retrieve_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from ....._models import BaseModel + +__all__ = ["RoleRetrieveResponse", "AssignmentSource"] + + +class AssignmentSource(BaseModel): + principal_id: str + + principal_type: str + + +class RoleRetrieveResponse(BaseModel): + """ + Detailed information about a role assignment entry returned when listing assignments. + """ + + id: str + """Identifier for the role.""" + + assignment_sources: Optional[List[AssignmentSource]] = None + """Principals from which the role assignment is inherited, when available.""" + + created_at: Optional[int] = None + """When the role was created.""" + + created_by: Optional[str] = None + """Identifier of the actor who created the role.""" + + created_by_user_obj: Optional[Dict[str, object]] = None + """User details for the actor that created the role, when available.""" + + description: Optional[str] = None + """Description of the role.""" + + metadata: Optional[Dict[str, object]] = None + """Arbitrary metadata stored on the role.""" + + name: str + """Name of the role.""" + + permissions: List[str] + """Permissions associated with the role.""" + + predefined_role: bool + """Whether the role is predefined by OpenAI.""" + + resource_type: str + """Resource type the role applies to.""" + + updated_at: Optional[int] = None + """When the role was last updated.""" diff --git a/src/openai/types/audio/__init__.py b/src/openai/types/audio/__init__.py index 1de5c0ff82..2ff2b8185d 100644 --- a/src/openai/types/audio/__init__.py +++ b/src/openai/types/audio/__init__.py @@ -5,6 +5,19 @@ from .translation import Translation as Translation from .speech_model import SpeechModel as SpeechModel from .transcription import Transcription as Transcription +from .transcription_word import TranscriptionWord as TranscriptionWord +from .translation_verbose import TranslationVerbose as TranslationVerbose from .speech_create_params import SpeechCreateParams as SpeechCreateParams +from .transcription_include import TranscriptionInclude as TranscriptionInclude +from .transcription_segment import TranscriptionSegment as TranscriptionSegment +from .transcription_verbose import TranscriptionVerbose as TranscriptionVerbose +from .transcription_diarized import TranscriptionDiarized as TranscriptionDiarized from .translation_create_params import TranslationCreateParams as TranslationCreateParams +from .transcription_stream_event import TranscriptionStreamEvent as TranscriptionStreamEvent from .transcription_create_params import TranscriptionCreateParams as TranscriptionCreateParams +from .translation_create_response import TranslationCreateResponse as TranslationCreateResponse +from .transcription_create_response import TranscriptionCreateResponse as TranscriptionCreateResponse +from .transcription_text_done_event import TranscriptionTextDoneEvent as TranscriptionTextDoneEvent +from .transcription_diarized_segment import TranscriptionDiarizedSegment as TranscriptionDiarizedSegment +from .transcription_text_delta_event import TranscriptionTextDeltaEvent as TranscriptionTextDeltaEvent +from .transcription_text_segment_event import TranscriptionTextSegmentEvent as TranscriptionTextSegmentEvent diff --git a/src/openai/types/audio/speech_create_params.py b/src/openai/types/audio/speech_create_params.py index dff66e49c7..1c0472ea85 100644 --- a/src/openai/types/audio/speech_create_params.py +++ b/src/openai/types/audio/speech_create_params.py @@ -3,11 +3,11 @@ from __future__ import annotations from typing import Union -from typing_extensions import Literal, Required, TypedDict +from typing_extensions import Literal, Required, TypeAlias, TypedDict from .speech_model import SpeechModel -__all__ = ["SpeechCreateParams"] +__all__ = ["SpeechCreateParams", "Voice", "VoiceID"] class SpeechCreateParams(TypedDict, total=False): @@ -16,16 +16,24 @@ class SpeechCreateParams(TypedDict, total=False): model: Required[Union[str, SpeechModel]] """ - One of the available [TTS models](https://platform.openai.com/docs/models/tts): - `tts-1` or `tts-1-hd` + One of the available [TTS models](https://platform.openai.com/docs/models#tts): + `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`, or `gpt-4o-mini-tts-2025-12-15`. """ - voice: Required[Literal["alloy", "echo", "fable", "onyx", "nova", "shimmer"]] + voice: Required[Voice] """The voice to use when generating the audio. - Supported voices are `alloy`, `echo`, `fable`, `onyx`, `nova`, and `shimmer`. - Previews of the voices are available in the - [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech/voice-options). + Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, + `fable`, `onyx`, `nova`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You + may also provide a custom voice object with an `id`, for example + `{ "id": "voice_1234" }`. Previews of the voices are available in the + [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech#voice-options). + """ + + instructions: str + """Control the voice of your generated audio with additional instructions. + + Does not work with `tts-1` or `tts-1-hd`. """ response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] @@ -39,3 +47,22 @@ class SpeechCreateParams(TypedDict, total=False): Select a value from `0.25` to `4.0`. `1.0` is the default. """ + + stream_format: Literal["sse", "audio"] + """The format to stream the audio in. + + Supported formats are `sse` and `audio`. `sse` is not supported for `tts-1` or + `tts-1-hd`. + """ + + +class VoiceID(TypedDict, total=False): + """Custom voice reference.""" + + id: Required[str] + """The custom voice ID, e.g. `voice_1234`.""" + + +Voice: TypeAlias = Union[ + str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"], VoiceID +] diff --git a/src/openai/types/audio/speech_model.py b/src/openai/types/audio/speech_model.py index bd685ab34d..31294a05b6 100644 --- a/src/openai/types/audio/speech_model.py +++ b/src/openai/types/audio/speech_model.py @@ -4,4 +4,4 @@ __all__ = ["SpeechModel"] -SpeechModel: TypeAlias = Literal["tts-1", "tts-1-hd"] +SpeechModel: TypeAlias = Literal["tts-1", "tts-1-hd", "gpt-4o-mini-tts", "gpt-4o-mini-tts-2025-12-15"] diff --git a/src/openai/types/audio/transcription.py b/src/openai/types/audio/transcription.py index edb5f227fc..cbae8bf750 100644 --- a/src/openai/types/audio/transcription.py +++ b/src/openai/types/audio/transcription.py @@ -1,11 +1,81 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias +from ..._utils import PropertyInfo from ..._models import BaseModel -__all__ = ["Transcription"] +__all__ = ["Transcription", "Logprob", "Usage", "UsageTokens", "UsageTokensInputTokenDetails", "UsageDuration"] + + +class Logprob(BaseModel): + token: Optional[str] = None + """The token in the transcription.""" + + bytes: Optional[List[float]] = None + """The bytes of the token.""" + + logprob: Optional[float] = None + """The log probability of the token.""" + + +class UsageTokensInputTokenDetails(BaseModel): + """Details about the input tokens billed for this request.""" + + audio_tokens: Optional[int] = None + """Number of audio tokens billed for this request.""" + + text_tokens: Optional[int] = None + """Number of text tokens billed for this request.""" + + +class UsageTokens(BaseModel): + """Usage statistics for models billed by token usage.""" + + input_tokens: int + """Number of input tokens billed for this request.""" + + output_tokens: int + """Number of output tokens generated.""" + + total_tokens: int + """Total number of tokens used (input + output).""" + + type: Literal["tokens"] + """The type of the usage object. Always `tokens` for this variant.""" + + input_token_details: Optional[UsageTokensInputTokenDetails] = None + """Details about the input tokens billed for this request.""" + + +class UsageDuration(BaseModel): + """Usage statistics for models billed by audio input duration.""" + + seconds: float + """Duration of the input audio in seconds.""" + + type: Literal["duration"] + """The type of the usage object. Always `duration` for this variant.""" + + +Usage: TypeAlias = Annotated[Union[UsageTokens, UsageDuration], PropertyInfo(discriminator="type")] class Transcription(BaseModel): + """ + Represents a transcription response returned by model, based on the provided input. + """ + text: str """The transcribed text.""" + + logprobs: Optional[List[Logprob]] = None + """The log probabilities of the tokens in the transcription. + + Only returned with the models `gpt-4o-transcribe` and `gpt-4o-mini-transcribe` + if `logprobs` is added to the `include` array. + """ + + usage: Optional[Usage] = None + """Token usage statistics for the request.""" diff --git a/src/openai/types/audio/transcription_create_params.py b/src/openai/types/audio/transcription_create_params.py index a825fefecb..15540fc73f 100644 --- a/src/openai/types/audio/transcription_create_params.py +++ b/src/openai/types/audio/transcription_create_params.py @@ -2,16 +2,24 @@ from __future__ import annotations -from typing import List, Union -from typing_extensions import Literal, Required, TypedDict +from typing import List, Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict -from ..._types import FileTypes +from ..._types import FileTypes, SequenceNotStr from ..audio_model import AudioModel +from .transcription_include import TranscriptionInclude +from ..audio_response_format import AudioResponseFormat -__all__ = ["TranscriptionCreateParams"] +__all__ = [ + "TranscriptionCreateParamsBase", + "ChunkingStrategy", + "ChunkingStrategyVadConfig", + "TranscriptionCreateParamsNonStreaming", + "TranscriptionCreateParamsStreaming", +] -class TranscriptionCreateParams(TypedDict, total=False): +class TranscriptionCreateParamsBase(TypedDict, total=False): file: Required[FileTypes] """ The audio file object (not file name) to transcribe, in one of these formats: @@ -21,30 +29,71 @@ class TranscriptionCreateParams(TypedDict, total=False): model: Required[Union[str, AudioModel]] """ID of the model to use. - Only `whisper-1` (which is powered by our open source Whisper V2 model) is - currently available. + The options are `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, + `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1` (which is powered by our open + source Whisper V2 model), and `gpt-4o-transcribe-diarize`. + """ + + chunking_strategy: Optional[ChunkingStrategy] + """Controls how the audio is cut into chunks. + + When set to `"auto"`, the server first normalizes loudness and then uses voice + activity detection (VAD) to choose boundaries. `server_vad` object can be + provided to tweak VAD detection parameters manually. If unset, the audio is + transcribed as a single block. Required when using `gpt-4o-transcribe-diarize` + for inputs longer than 30 seconds. + """ + + include: List[TranscriptionInclude] + """ + Additional information to include in the transcription response. `logprobs` will + return the log probabilities of the tokens in the response to understand the + model's confidence in the transcription. `logprobs` only works with + response_format set to `json` and only with the models `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is + not supported when using `gpt-4o-transcribe-diarize`. + """ + + known_speaker_names: SequenceNotStr[str] + """ + Optional list of speaker names that correspond to the audio samples provided in + `known_speaker_references[]`. Each entry should be a short identifier (for + example `customer` or `agent`). Up to 4 speakers are supported. + """ + + known_speaker_references: SequenceNotStr[str] + """ + Optional list of audio samples (as + [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)) + that contain known speaker references matching `known_speaker_names[]`. Each + sample must be between 2 and 10 seconds, and can use any of the same input audio + formats supported by `file`. """ language: str """The language of the input audio. Supplying the input language in - [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format will - improve accuracy and latency. + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. """ prompt: str """An optional text to guide the model's style or continue a previous audio segment. - The [prompt](https://platform.openai.com/docs/guides/speech-to-text/prompting) - should match the audio language. + The [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) + should match the audio language. This field is not supported when using + `gpt-4o-transcribe-diarize`. """ - response_format: Literal["json", "text", "srt", "verbose_json", "vtt"] + response_format: AudioResponseFormat """ - The format of the transcript output, in one of these options: `json`, `text`, - `srt`, `verbose_json`, or `vtt`. + The format of the output, in one of these options: `json`, `text`, `srt`, + `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and + `gpt-4o-mini-transcribe`, the only supported format is `json`. For + `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and + `diarized_json`, with `diarized_json` required to receive speaker annotations. """ temperature: float @@ -62,5 +111,62 @@ class TranscriptionCreateParams(TypedDict, total=False): `response_format` must be set `verbose_json` to use timestamp granularities. Either or both of these options are supported: `word`, or `segment`. Note: There is no additional latency for segment timestamps, but generating word timestamps - incurs additional latency. + incurs additional latency. This option is not available for + `gpt-4o-transcribe-diarize`. + """ + + +class ChunkingStrategyVadConfig(TypedDict, total=False): + type: Required[Literal["server_vad"]] + """Must be set to `server_vad` to enable manual chunking using server side VAD.""" + + prefix_padding_ms: int + """Amount of audio to include before the VAD detected speech (in milliseconds).""" + + silence_duration_ms: int """ + Duration of silence to detect speech stop (in milliseconds). With shorter values + the model will respond more quickly, but may jump in on short pauses from the + user. + """ + + threshold: float + """Sensitivity threshold (0.0 to 1.0) for voice activity detection. + + A higher threshold will require louder audio to activate the model, and thus + might perform better in noisy environments. + """ + + +ChunkingStrategy: TypeAlias = Union[Literal["auto"], ChunkingStrategyVadConfig] + + +class TranscriptionCreateParamsNonStreaming(TranscriptionCreateParamsBase, total=False): + stream: Optional[Literal[False]] + """ + If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions) + for more information. + + Note: Streaming is not supported for the `whisper-1` model and will be ignored. + """ + + +class TranscriptionCreateParamsStreaming(TranscriptionCreateParamsBase): + stream: Required[Literal[True]] + """ + If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section of the Speech-to-Text guide](https://platform.openai.com/docs/guides/speech-to-text?lang=curl#streaming-transcriptions) + for more information. + + Note: Streaming is not supported for the `whisper-1` model and will be ignored. + """ + + +TranscriptionCreateParams = Union[TranscriptionCreateParamsNonStreaming, TranscriptionCreateParamsStreaming] diff --git a/src/openai/types/audio/transcription_create_response.py b/src/openai/types/audio/transcription_create_response.py new file mode 100644 index 0000000000..5717a3e701 --- /dev/null +++ b/src/openai/types/audio/transcription_create_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import TypeAlias + +from .transcription import Transcription +from .transcription_verbose import TranscriptionVerbose +from .transcription_diarized import TranscriptionDiarized + +__all__ = ["TranscriptionCreateResponse"] + +TranscriptionCreateResponse: TypeAlias = Union[Transcription, TranscriptionDiarized, TranscriptionVerbose] diff --git a/src/openai/types/audio/transcription_diarized.py b/src/openai/types/audio/transcription_diarized.py new file mode 100644 index 0000000000..07585fe239 --- /dev/null +++ b/src/openai/types/audio/transcription_diarized.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .transcription_diarized_segment import TranscriptionDiarizedSegment + +__all__ = ["TranscriptionDiarized", "Usage", "UsageTokens", "UsageTokensInputTokenDetails", "UsageDuration"] + + +class UsageTokensInputTokenDetails(BaseModel): + """Details about the input tokens billed for this request.""" + + audio_tokens: Optional[int] = None + """Number of audio tokens billed for this request.""" + + text_tokens: Optional[int] = None + """Number of text tokens billed for this request.""" + + +class UsageTokens(BaseModel): + """Usage statistics for models billed by token usage.""" + + input_tokens: int + """Number of input tokens billed for this request.""" + + output_tokens: int + """Number of output tokens generated.""" + + total_tokens: int + """Total number of tokens used (input + output).""" + + type: Literal["tokens"] + """The type of the usage object. Always `tokens` for this variant.""" + + input_token_details: Optional[UsageTokensInputTokenDetails] = None + """Details about the input tokens billed for this request.""" + + +class UsageDuration(BaseModel): + """Usage statistics for models billed by audio input duration.""" + + seconds: float + """Duration of the input audio in seconds.""" + + type: Literal["duration"] + """The type of the usage object. Always `duration` for this variant.""" + + +Usage: TypeAlias = Annotated[Union[UsageTokens, UsageDuration], PropertyInfo(discriminator="type")] + + +class TranscriptionDiarized(BaseModel): + """ + Represents a diarized transcription response returned by the model, including the combined transcript and speaker-segment annotations. + """ + + duration: float + """Duration of the input audio in seconds.""" + + segments: List[TranscriptionDiarizedSegment] + """Segments of the transcript annotated with timestamps and speaker labels.""" + + task: Literal["transcribe"] + """The type of task that was run. Always `transcribe`.""" + + text: str + """The concatenated transcript text for the entire audio input.""" + + usage: Optional[Usage] = None + """Token or duration usage statistics for the request.""" diff --git a/src/openai/types/audio/transcription_diarized_segment.py b/src/openai/types/audio/transcription_diarized_segment.py new file mode 100644 index 0000000000..fcfdb3634f --- /dev/null +++ b/src/openai/types/audio/transcription_diarized_segment.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["TranscriptionDiarizedSegment"] + + +class TranscriptionDiarizedSegment(BaseModel): + """A segment of diarized transcript text with speaker metadata.""" + + id: str + """Unique identifier for the segment.""" + + end: float + """End timestamp of the segment in seconds.""" + + speaker: str + """Speaker label for this segment. + + When known speakers are provided, the label matches `known_speaker_names[]`. + Otherwise speakers are labeled sequentially using capital letters (`A`, `B`, + ...). + """ + + start: float + """Start timestamp of the segment in seconds.""" + + text: str + """Transcript text for this segment.""" + + type: Literal["transcript.text.segment"] + """The type of the segment. Always `transcript.text.segment`.""" diff --git a/src/openai/types/audio/transcription_include.py b/src/openai/types/audio/transcription_include.py new file mode 100644 index 0000000000..0e464ac934 --- /dev/null +++ b/src/openai/types/audio/transcription_include.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["TranscriptionInclude"] + +TranscriptionInclude: TypeAlias = Literal["logprobs"] diff --git a/src/openai/types/audio/transcription_segment.py b/src/openai/types/audio/transcription_segment.py new file mode 100644 index 0000000000..522c401ebb --- /dev/null +++ b/src/openai/types/audio/transcription_segment.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ..._models import BaseModel + +__all__ = ["TranscriptionSegment"] + + +class TranscriptionSegment(BaseModel): + id: int + """Unique identifier of the segment.""" + + avg_logprob: float + """Average logprob of the segment. + + If the value is lower than -1, consider the logprobs failed. + """ + + compression_ratio: float + """Compression ratio of the segment. + + If the value is greater than 2.4, consider the compression failed. + """ + + end: float + """End time of the segment in seconds.""" + + no_speech_prob: float + """Probability of no speech in the segment. + + If the value is higher than 1.0 and the `avg_logprob` is below -1, consider this + segment silent. + """ + + seek: int + """Seek offset of the segment.""" + + start: float + """Start time of the segment in seconds.""" + + temperature: float + """Temperature parameter used for generating the segment.""" + + text: str + """Text content of the segment.""" + + tokens: List[int] + """Array of token IDs for the text content.""" diff --git a/src/openai/types/audio/transcription_stream_event.py b/src/openai/types/audio/transcription_stream_event.py new file mode 100644 index 0000000000..77d3a3aeec --- /dev/null +++ b/src/openai/types/audio/transcription_stream_event.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from .transcription_text_done_event import TranscriptionTextDoneEvent +from .transcription_text_delta_event import TranscriptionTextDeltaEvent +from .transcription_text_segment_event import TranscriptionTextSegmentEvent + +__all__ = ["TranscriptionStreamEvent"] + +TranscriptionStreamEvent: TypeAlias = Annotated[ + Union[TranscriptionTextSegmentEvent, TranscriptionTextDeltaEvent, TranscriptionTextDoneEvent], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/audio/transcription_text_delta_event.py b/src/openai/types/audio/transcription_text_delta_event.py new file mode 100644 index 0000000000..a6e83133c8 --- /dev/null +++ b/src/openai/types/audio/transcription_text_delta_event.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["TranscriptionTextDeltaEvent", "Logprob"] + + +class Logprob(BaseModel): + token: Optional[str] = None + """The token that was used to generate the log probability.""" + + bytes: Optional[List[int]] = None + """The bytes that were used to generate the log probability.""" + + logprob: Optional[float] = None + """The log probability of the token.""" + + +class TranscriptionTextDeltaEvent(BaseModel): + """Emitted when there is an additional text delta. + + This is also the first event emitted when the transcription starts. Only emitted when you [create a transcription](https://platform.openai.com/docs/api-reference/audio/create-transcription) with the `Stream` parameter set to `true`. + """ + + delta: str + """The text delta that was additionally transcribed.""" + + type: Literal["transcript.text.delta"] + """The type of the event. Always `transcript.text.delta`.""" + + logprobs: Optional[List[Logprob]] = None + """The log probabilities of the delta. + + Only included if you + [create a transcription](https://platform.openai.com/docs/api-reference/audio/create-transcription) + with the `include[]` parameter set to `logprobs`. + """ + + segment_id: Optional[str] = None + """Identifier of the diarized segment that this delta belongs to. + + Only present when using `gpt-4o-transcribe-diarize`. + """ diff --git a/src/openai/types/audio/transcription_text_done_event.py b/src/openai/types/audio/transcription_text_done_event.py new file mode 100644 index 0000000000..c8f7fc0769 --- /dev/null +++ b/src/openai/types/audio/transcription_text_done_event.py @@ -0,0 +1,72 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["TranscriptionTextDoneEvent", "Logprob", "Usage", "UsageInputTokenDetails"] + + +class Logprob(BaseModel): + token: Optional[str] = None + """The token that was used to generate the log probability.""" + + bytes: Optional[List[int]] = None + """The bytes that were used to generate the log probability.""" + + logprob: Optional[float] = None + """The log probability of the token.""" + + +class UsageInputTokenDetails(BaseModel): + """Details about the input tokens billed for this request.""" + + audio_tokens: Optional[int] = None + """Number of audio tokens billed for this request.""" + + text_tokens: Optional[int] = None + """Number of text tokens billed for this request.""" + + +class Usage(BaseModel): + """Usage statistics for models billed by token usage.""" + + input_tokens: int + """Number of input tokens billed for this request.""" + + output_tokens: int + """Number of output tokens generated.""" + + total_tokens: int + """Total number of tokens used (input + output).""" + + type: Literal["tokens"] + """The type of the usage object. Always `tokens` for this variant.""" + + input_token_details: Optional[UsageInputTokenDetails] = None + """Details about the input tokens billed for this request.""" + + +class TranscriptionTextDoneEvent(BaseModel): + """Emitted when the transcription is complete. + + Contains the complete transcription text. Only emitted when you [create a transcription](https://platform.openai.com/docs/api-reference/audio/create-transcription) with the `Stream` parameter set to `true`. + """ + + text: str + """The text that was transcribed.""" + + type: Literal["transcript.text.done"] + """The type of the event. Always `transcript.text.done`.""" + + logprobs: Optional[List[Logprob]] = None + """The log probabilities of the individual tokens in the transcription. + + Only included if you + [create a transcription](https://platform.openai.com/docs/api-reference/audio/create-transcription) + with the `include[]` parameter set to `logprobs`. + """ + + usage: Optional[Usage] = None + """Usage statistics for models billed by token usage.""" diff --git a/src/openai/types/audio/transcription_text_segment_event.py b/src/openai/types/audio/transcription_text_segment_event.py new file mode 100644 index 0000000000..e95472e6c6 --- /dev/null +++ b/src/openai/types/audio/transcription_text_segment_event.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["TranscriptionTextSegmentEvent"] + + +class TranscriptionTextSegmentEvent(BaseModel): + """ + Emitted when a diarized transcription returns a completed segment with speaker information. Only emitted when you [create a transcription](https://platform.openai.com/docs/api-reference/audio/create-transcription) with `stream` set to `true` and `response_format` set to `diarized_json`. + """ + + id: str + """Unique identifier for the segment.""" + + end: float + """End timestamp of the segment in seconds.""" + + speaker: str + """Speaker label for this segment.""" + + start: float + """Start timestamp of the segment in seconds.""" + + text: str + """Transcript text for this segment.""" + + type: Literal["transcript.text.segment"] + """The type of the event. Always `transcript.text.segment`.""" diff --git a/src/openai/types/audio/transcription_verbose.py b/src/openai/types/audio/transcription_verbose.py new file mode 100644 index 0000000000..b1a95e9c72 --- /dev/null +++ b/src/openai/types/audio/transcription_verbose.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .transcription_word import TranscriptionWord +from .transcription_segment import TranscriptionSegment + +__all__ = ["TranscriptionVerbose", "Usage"] + + +class Usage(BaseModel): + """Usage statistics for models billed by audio input duration.""" + + seconds: float + """Duration of the input audio in seconds.""" + + type: Literal["duration"] + """The type of the usage object. Always `duration` for this variant.""" + + +class TranscriptionVerbose(BaseModel): + """ + Represents a verbose json transcription response returned by model, based on the provided input. + """ + + duration: float + """The duration of the input audio.""" + + language: str + """The language of the input audio.""" + + text: str + """The transcribed text.""" + + segments: Optional[List[TranscriptionSegment]] = None + """Segments of the transcribed text and their corresponding details.""" + + usage: Optional[Usage] = None + """Usage statistics for models billed by audio input duration.""" + + words: Optional[List[TranscriptionWord]] = None + """Extracted words and their corresponding timestamps.""" diff --git a/src/openai/types/audio/transcription_word.py b/src/openai/types/audio/transcription_word.py new file mode 100644 index 0000000000..2ce682f957 --- /dev/null +++ b/src/openai/types/audio/transcription_word.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["TranscriptionWord"] + + +class TranscriptionWord(BaseModel): + end: float + """End time of the word in seconds.""" + + start: float + """Start time of the word in seconds.""" + + word: str + """The text content of the word.""" diff --git a/src/openai/types/audio/translation.py b/src/openai/types/audio/translation.py index 7c0e905189..efc56f7f9b 100644 --- a/src/openai/types/audio/translation.py +++ b/src/openai/types/audio/translation.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["Translation"] diff --git a/src/openai/types/audio/translation_create_params.py b/src/openai/types/audio/translation_create_params.py index 054996a134..b23a185375 100644 --- a/src/openai/types/audio/translation_create_params.py +++ b/src/openai/types/audio/translation_create_params.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Union -from typing_extensions import Required, TypedDict +from typing_extensions import Literal, Required, TypedDict from ..._types import FileTypes from ..audio_model import AudioModel @@ -29,14 +29,14 @@ class TranslationCreateParams(TypedDict, total=False): """An optional text to guide the model's style or continue a previous audio segment. - The [prompt](https://platform.openai.com/docs/guides/speech-to-text/prompting) + The [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) should be in English. """ - response_format: str + response_format: Literal["json", "text", "srt", "verbose_json", "vtt"] """ - The format of the transcript output, in one of these options: `json`, `text`, - `srt`, `verbose_json`, or `vtt`. + The format of the output, in one of these options: `json`, `text`, `srt`, + `verbose_json`, or `vtt`. """ temperature: float diff --git a/src/openai/types/audio/translation_create_response.py b/src/openai/types/audio/translation_create_response.py new file mode 100644 index 0000000000..9953813c08 --- /dev/null +++ b/src/openai/types/audio/translation_create_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import TypeAlias + +from .translation import Translation +from .translation_verbose import TranslationVerbose + +__all__ = ["TranslationCreateResponse"] + +TranslationCreateResponse: TypeAlias = Union[Translation, TranslationVerbose] diff --git a/src/openai/types/audio/translation_verbose.py b/src/openai/types/audio/translation_verbose.py new file mode 100644 index 0000000000..27cb02d64f --- /dev/null +++ b/src/openai/types/audio/translation_verbose.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel +from .transcription_segment import TranscriptionSegment + +__all__ = ["TranslationVerbose"] + + +class TranslationVerbose(BaseModel): + duration: float + """The duration of the input audio.""" + + language: str + """The language of the output translation (always `english`).""" + + text: str + """The translated text.""" + + segments: Optional[List[TranscriptionSegment]] = None + """Segments of the translated text and their corresponding details.""" diff --git a/src/openai/types/audio_model.py b/src/openai/types/audio_model.py index 94ae84c015..8acada6dc3 100644 --- a/src/openai/types/audio_model.py +++ b/src/openai/types/audio_model.py @@ -4,4 +4,10 @@ __all__ = ["AudioModel"] -AudioModel: TypeAlias = Literal["whisper-1"] +AudioModel: TypeAlias = Literal[ + "whisper-1", + "gpt-4o-transcribe", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe-diarize", +] diff --git a/src/openai/types/audio_response_format.py b/src/openai/types/audio_response_format.py new file mode 100644 index 0000000000..1897aaf6ed --- /dev/null +++ b/src/openai/types/audio_response_format.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["AudioResponseFormat"] + +AudioResponseFormat: TypeAlias = Literal["json", "text", "srt", "verbose_json", "vtt", "diarized_json"] diff --git a/src/openai/types/beta/auto_file_chunking_strategy_param.py b/src/openai/types/auto_file_chunking_strategy_param.py similarity index 70% rename from src/openai/types/beta/auto_file_chunking_strategy_param.py rename to src/openai/types/auto_file_chunking_strategy_param.py index 6f17836bac..db7cbf596d 100644 --- a/src/openai/types/beta/auto_file_chunking_strategy_param.py +++ b/src/openai/types/auto_file_chunking_strategy_param.py @@ -8,5 +8,10 @@ class AutoFileChunkingStrategyParam(TypedDict, total=False): + """The default strategy. + + This strategy currently uses a `max_chunk_size_tokens` of `800` and `chunk_overlap_tokens` of `400`. + """ + type: Required[Literal["auto"]] """Always `auto`.""" diff --git a/src/openai/types/batch.py b/src/openai/types/batch.py index 90f6d79572..ece0513b35 100644 --- a/src/openai/types/batch.py +++ b/src/openai/types/batch.py @@ -1,11 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import builtins from typing import List, Optional from typing_extensions import Literal from .._models import BaseModel from .batch_error import BatchError +from .batch_usage import BatchUsage +from .shared.metadata import Metadata from .batch_request_counts import BatchRequestCounts __all__ = ["Batch", "Errors"] @@ -70,12 +71,23 @@ class Batch(BaseModel): in_progress_at: Optional[int] = None """The Unix timestamp (in seconds) for when the batch started processing.""" - metadata: Optional[builtins.object] = None + metadata: Optional[Metadata] = None """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: Optional[str] = None + """Model ID used to process the batch, like `gpt-5-2025-08-07`. + + OpenAI offers a wide range of models with different capabilities, performance + characteristics, and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. """ output_file_id: Optional[str] = None @@ -83,3 +95,10 @@ class Batch(BaseModel): request_counts: Optional[BatchRequestCounts] = None """The request counts for different statuses within the batch.""" + + usage: Optional[BatchUsage] = None + """ + Represents token usage details including input tokens, output tokens, a + breakdown of output tokens, and the total tokens used. Only populated on batches + created after September 7, 2025. + """ diff --git a/src/openai/types/batch_create_params.py b/src/openai/types/batch_create_params.py index 55517d285b..97bd2c67ed 100644 --- a/src/openai/types/batch_create_params.py +++ b/src/openai/types/batch_create_params.py @@ -2,10 +2,12 @@ from __future__ import annotations -from typing import Dict, Optional +from typing import Optional from typing_extensions import Literal, Required, TypedDict -__all__ = ["BatchCreateParams"] +from .shared_params.metadata import Metadata + +__all__ = ["BatchCreateParams", "OutputExpiresAfter"] class BatchCreateParams(TypedDict, total=False): @@ -15,12 +17,25 @@ class BatchCreateParams(TypedDict, total=False): Currently only `24h` is supported. """ - endpoint: Required[Literal["/v1/chat/completions", "/v1/embeddings", "/v1/completions"]] + endpoint: Required[ + Literal[ + "/v1/responses", + "/v1/chat/completions", + "/v1/embeddings", + "/v1/completions", + "/v1/moderations", + "/v1/images/generations", + "/v1/images/edits", + "/v1/videos", + ] + ] """The endpoint to be used for all requests in the batch. - Currently `/v1/chat/completions`, `/v1/embeddings`, and `/v1/completions` are - supported. Note that `/v1/embeddings` batches are also restricted to a maximum - of 50,000 embedding inputs across all requests in the batch. + Currently `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, + `/v1/completions`, `/v1/moderations`, `/v1/images/generations`, + `/v1/images/edits`, and `/v1/videos` are supported. Note that `/v1/embeddings` + batches are also restricted to a maximum of 50,000 embedding inputs across all + requests in the batch. """ input_file_id: Required[str] @@ -32,8 +47,40 @@ class BatchCreateParams(TypedDict, total=False): Your input file must be formatted as a [JSONL file](https://platform.openai.com/docs/api-reference/batch/request-input), and must be uploaded with the purpose `batch`. The file can contain up to 50,000 - requests, and can be up to 100 MB in size. + requests, and can be up to 200 MB in size. + """ + + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ - metadata: Optional[Dict[str, str]] - """Optional custom metadata for the batch.""" + output_expires_after: OutputExpiresAfter + """ + The expiration policy for the output and/or error file that are generated for a + batch. + """ + + +class OutputExpiresAfter(TypedDict, total=False): + """ + The expiration policy for the output and/or error file that are generated for a batch. + """ + + anchor: Required[Literal["created_at"]] + """Anchor timestamp after which the expiration policy applies. + + Supported anchors: `created_at`. Note that the anchor is the file creation time, + not the time the batch is created. + """ + + seconds: Required[int] + """The number of seconds after the anchor time that the file will expire. + + Must be between 3600 (1 hour) and 2592000 (30 days). + """ diff --git a/src/openai/types/batch_request_counts.py b/src/openai/types/batch_request_counts.py index 7e1d49fb88..64a570747d 100644 --- a/src/openai/types/batch_request_counts.py +++ b/src/openai/types/batch_request_counts.py @@ -1,12 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["BatchRequestCounts"] class BatchRequestCounts(BaseModel): + """The request counts for different statuses within the batch.""" + completed: int """Number of requests that have been completed successfully.""" diff --git a/src/openai/types/batch_usage.py b/src/openai/types/batch_usage.py new file mode 100644 index 0000000000..d68d7110ac --- /dev/null +++ b/src/openai/types/batch_usage.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["BatchUsage", "InputTokensDetails", "OutputTokensDetails"] + + +class InputTokensDetails(BaseModel): + """A detailed breakdown of the input tokens.""" + + cached_tokens: int + """The number of tokens that were retrieved from the cache. + + [More on prompt caching](https://platform.openai.com/docs/guides/prompt-caching). + """ + + +class OutputTokensDetails(BaseModel): + """A detailed breakdown of the output tokens.""" + + reasoning_tokens: int + """The number of reasoning tokens.""" + + +class BatchUsage(BaseModel): + """ + Represents token usage details including input tokens, output tokens, a + breakdown of output tokens, and the total tokens used. Only populated on + batches created after September 7, 2025. + """ + + input_tokens: int + """The number of input tokens.""" + + input_tokens_details: InputTokensDetails + """A detailed breakdown of the input tokens.""" + + output_tokens: int + """The number of output tokens.""" + + output_tokens_details: OutputTokensDetails + """A detailed breakdown of the output tokens.""" + + total_tokens: int + """The total number of tokens used.""" diff --git a/src/openai/types/beta/__init__.py b/src/openai/types/beta/__init__.py index 7f76fed0cd..deb2369677 100644 --- a/src/openai/types/beta/__init__.py +++ b/src/openai/types/beta/__init__.py @@ -4,42 +4,31 @@ from .thread import Thread as Thread from .assistant import Assistant as Assistant -from .vector_store import VectorStore as VectorStore from .function_tool import FunctionTool as FunctionTool from .assistant_tool import AssistantTool as AssistantTool from .thread_deleted import ThreadDeleted as ThreadDeleted +from .chatkit_workflow import ChatKitWorkflow as ChatKitWorkflow from .file_search_tool import FileSearchTool as FileSearchTool from .assistant_deleted import AssistantDeleted as AssistantDeleted from .function_tool_param import FunctionToolParam as FunctionToolParam from .assistant_tool_param import AssistantToolParam as AssistantToolParam from .thread_create_params import ThreadCreateParams as ThreadCreateParams from .thread_update_params import ThreadUpdateParams as ThreadUpdateParams -from .vector_store_deleted import VectorStoreDeleted as VectorStoreDeleted from .assistant_list_params import AssistantListParams as AssistantListParams from .assistant_tool_choice import AssistantToolChoice as AssistantToolChoice from .code_interpreter_tool import CodeInterpreterTool as CodeInterpreterTool from .assistant_stream_event import AssistantStreamEvent as AssistantStreamEvent -from .file_chunking_strategy import FileChunkingStrategy as FileChunkingStrategy from .file_search_tool_param import FileSearchToolParam as FileSearchToolParam from .assistant_create_params import AssistantCreateParams as AssistantCreateParams from .assistant_update_params import AssistantUpdateParams as AssistantUpdateParams -from .vector_store_list_params import VectorStoreListParams as VectorStoreListParams -from .vector_store_create_params import VectorStoreCreateParams as VectorStoreCreateParams -from .vector_store_update_params import VectorStoreUpdateParams as VectorStoreUpdateParams from .assistant_tool_choice_param import AssistantToolChoiceParam as AssistantToolChoiceParam from .code_interpreter_tool_param import CodeInterpreterToolParam as CodeInterpreterToolParam from .assistant_tool_choice_option import AssistantToolChoiceOption as AssistantToolChoiceOption -from .file_chunking_strategy_param import FileChunkingStrategyParam as FileChunkingStrategyParam from .thread_create_and_run_params import ThreadCreateAndRunParams as ThreadCreateAndRunParams -from .static_file_chunking_strategy import StaticFileChunkingStrategy as StaticFileChunkingStrategy from .assistant_tool_choice_function import AssistantToolChoiceFunction as AssistantToolChoiceFunction from .assistant_response_format_option import AssistantResponseFormatOption as AssistantResponseFormatOption -from .auto_file_chunking_strategy_param import AutoFileChunkingStrategyParam as AutoFileChunkingStrategyParam from .assistant_tool_choice_option_param import AssistantToolChoiceOptionParam as AssistantToolChoiceOptionParam -from .other_file_chunking_strategy_object import OtherFileChunkingStrategyObject as OtherFileChunkingStrategyObject -from .static_file_chunking_strategy_param import StaticFileChunkingStrategyParam as StaticFileChunkingStrategyParam from .assistant_tool_choice_function_param import AssistantToolChoiceFunctionParam as AssistantToolChoiceFunctionParam -from .static_file_chunking_strategy_object import StaticFileChunkingStrategyObject as StaticFileChunkingStrategyObject from .assistant_response_format_option_param import ( AssistantResponseFormatOptionParam as AssistantResponseFormatOptionParam, ) diff --git a/src/openai/types/beta/assistant.py b/src/openai/types/beta/assistant.py index b4da08745d..61344f85a1 100644 --- a/src/openai/types/beta/assistant.py +++ b/src/openai/types/beta/assistant.py @@ -5,6 +5,7 @@ from ..._models import BaseModel from .assistant_tool import AssistantTool +from ..shared.metadata import Metadata from .assistant_response_format_option import AssistantResponseFormatOption __all__ = ["Assistant", "ToolResources", "ToolResourcesCodeInterpreter", "ToolResourcesFileSearch"] @@ -30,12 +31,19 @@ class ToolResourcesFileSearch(BaseModel): class ToolResources(BaseModel): + """A set of resources that are used by the assistant's tools. + + The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs. + """ + code_interpreter: Optional[ToolResourcesCodeInterpreter] = None file_search: Optional[ToolResourcesFileSearch] = None class Assistant(BaseModel): + """Represents an `assistant` that can call the model and use tools.""" + id: str """The identifier, which can be referenced in API endpoints.""" @@ -51,12 +59,14 @@ class Assistant(BaseModel): The maximum length is 256,000 characters. """ - metadata: Optional[object] = None + metadata: Optional[Metadata] = None """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ model: str @@ -65,8 +75,8 @@ class Assistant(BaseModel): You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. """ name: Optional[str] = None @@ -85,8 +95,8 @@ class Assistant(BaseModel): response_format: Optional[AssistantResponseFormatOption] = None """Specifies the format that the model must output. - Compatible with [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + Compatible with [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured diff --git a/src/openai/types/beta/assistant_create_params.py b/src/openai/types/beta/assistant_create_params.py index eca4da0a2b..5a468a351b 100644 --- a/src/openai/types/beta/assistant_create_params.py +++ b/src/openai/types/beta/assistant_create_params.py @@ -2,12 +2,14 @@ from __future__ import annotations -from typing import List, Union, Iterable, Optional -from typing_extensions import Required, TypedDict +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict -from ..chat_model import ChatModel +from ..._types import SequenceNotStr +from ..shared.chat_model import ChatModel from .assistant_tool_param import AssistantToolParam -from .file_chunking_strategy_param import FileChunkingStrategyParam +from ..shared_params.metadata import Metadata +from ..shared.reasoning_effort import ReasoningEffort from .assistant_response_format_option_param import AssistantResponseFormatOptionParam __all__ = [ @@ -16,6 +18,10 @@ "ToolResourcesCodeInterpreter", "ToolResourcesFileSearch", "ToolResourcesFileSearchVectorStore", + "ToolResourcesFileSearchVectorStoreChunkingStrategy", + "ToolResourcesFileSearchVectorStoreChunkingStrategyAuto", + "ToolResourcesFileSearchVectorStoreChunkingStrategyStatic", + "ToolResourcesFileSearchVectorStoreChunkingStrategyStaticStatic", ] @@ -26,8 +32,8 @@ class AssistantCreateParams(TypedDict, total=False): You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. """ description: Optional[str] @@ -39,22 +45,41 @@ class AssistantCreateParams(TypedDict, total=False): The maximum length is 256,000 characters. """ - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ name: Optional[str] """The name of the assistant. The maximum length is 256 characters.""" + reasoning_effort: Optional[ReasoningEffort] + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + response_format: Optional[AssistantResponseFormatOptionParam] """Specifies the format that the model must output. - Compatible with [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + Compatible with [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -107,7 +132,7 @@ class AssistantCreateParams(TypedDict, total=False): class ToolResourcesCodeInterpreter(TypedDict, total=False): - file_ids: List[str] + file_ids: SequenceNotStr[str] """ A list of [file](https://platform.openai.com/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files @@ -115,32 +140,71 @@ class ToolResourcesCodeInterpreter(TypedDict, total=False): """ +class ToolResourcesFileSearchVectorStoreChunkingStrategyAuto(TypedDict, total=False): + """The default strategy. + + This strategy currently uses a `max_chunk_size_tokens` of `800` and `chunk_overlap_tokens` of `400`. + """ + + type: Required[Literal["auto"]] + """Always `auto`.""" + + +class ToolResourcesFileSearchVectorStoreChunkingStrategyStaticStatic(TypedDict, total=False): + chunk_overlap_tokens: Required[int] + """The number of tokens that overlap between chunks. The default value is `400`. + + Note that the overlap must not exceed half of `max_chunk_size_tokens`. + """ + + max_chunk_size_tokens: Required[int] + """The maximum number of tokens in each chunk. + + The default value is `800`. The minimum value is `100` and the maximum value is + `4096`. + """ + + +class ToolResourcesFileSearchVectorStoreChunkingStrategyStatic(TypedDict, total=False): + static: Required[ToolResourcesFileSearchVectorStoreChunkingStrategyStaticStatic] + + type: Required[Literal["static"]] + """Always `static`.""" + + +ToolResourcesFileSearchVectorStoreChunkingStrategy: TypeAlias = Union[ + ToolResourcesFileSearchVectorStoreChunkingStrategyAuto, ToolResourcesFileSearchVectorStoreChunkingStrategyStatic +] + + class ToolResourcesFileSearchVectorStore(TypedDict, total=False): - chunking_strategy: FileChunkingStrategyParam + chunking_strategy: ToolResourcesFileSearchVectorStoreChunkingStrategy """The chunking strategy used to chunk the file(s). - If not set, will use the `auto` strategy. Only applicable if `file_ids` is - non-empty. + If not set, will use the `auto` strategy. """ - file_ids: List[str] + file_ids: SequenceNotStr[str] """ A list of [file](https://platform.openai.com/docs/api-reference/files) IDs to - add to the vector store. There can be a maximum of 10000 files in a vector - store. + add to the vector store. For vector stores created before Nov 2025, there can be + a maximum of 10,000 files in a vector store. For vector stores created starting + in Nov 2025, the limit is 100,000,000 files. """ - metadata: object - """Set of 16 key-value pairs that can be attached to a vector store. + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. - This can be useful for storing additional information about the vector store in - a structured format. Keys can be a maximum of 64 characters long and values can - be a maxium of 512 characters long. + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ class ToolResourcesFileSearch(TypedDict, total=False): - vector_store_ids: List[str] + vector_store_ids: SequenceNotStr[str] """ The [vector store](https://platform.openai.com/docs/api-reference/vector-stores/object) @@ -158,6 +222,11 @@ class ToolResourcesFileSearch(TypedDict, total=False): class ToolResources(TypedDict, total=False): + """A set of resources that are used by the assistant's tools. + + The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs. + """ + code_interpreter: ToolResourcesCodeInterpreter file_search: ToolResourcesFileSearch diff --git a/src/openai/types/beta/assistant_list_params.py b/src/openai/types/beta/assistant_list_params.py index f54f63120b..834ffbcaf8 100644 --- a/src/openai/types/beta/assistant_list_params.py +++ b/src/openai/types/beta/assistant_list_params.py @@ -21,7 +21,7 @@ class AssistantListParams(TypedDict, total=False): """A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if - you make a list request and receive 100 objects, ending with obj_foo, your + you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list. """ diff --git a/src/openai/types/beta/assistant_stream_event.py b/src/openai/types/beta/assistant_stream_event.py index f1d8898ff2..87620a11d0 100644 --- a/src/openai/types/beta/assistant_stream_event.py +++ b/src/openai/types/beta/assistant_stream_event.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Union +from typing import Union, Optional from typing_extensions import Literal, Annotated, TypeAlias from .thread import Thread @@ -43,6 +43,10 @@ class ThreadCreated(BaseModel): + """ + Occurs when a new [thread](https://platform.openai.com/docs/api-reference/threads/object) is created. + """ + data: Thread """ Represents a thread that contains @@ -51,8 +55,15 @@ class ThreadCreated(BaseModel): event: Literal["thread.created"] + enabled: Optional[bool] = None + """Whether to enable input audio transcription.""" + class ThreadRunCreated(BaseModel): + """ + Occurs when a new [run](https://platform.openai.com/docs/api-reference/runs/object) is created. + """ + data: Run """ Represents an execution run on a @@ -63,6 +74,10 @@ class ThreadRunCreated(BaseModel): class ThreadRunQueued(BaseModel): + """ + Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) moves to a `queued` status. + """ + data: Run """ Represents an execution run on a @@ -73,6 +88,10 @@ class ThreadRunQueued(BaseModel): class ThreadRunInProgress(BaseModel): + """ + Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) moves to an `in_progress` status. + """ + data: Run """ Represents an execution run on a @@ -83,6 +102,10 @@ class ThreadRunInProgress(BaseModel): class ThreadRunRequiresAction(BaseModel): + """ + Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) moves to a `requires_action` status. + """ + data: Run """ Represents an execution run on a @@ -93,6 +116,10 @@ class ThreadRunRequiresAction(BaseModel): class ThreadRunCompleted(BaseModel): + """ + Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) is completed. + """ + data: Run """ Represents an execution run on a @@ -103,6 +130,10 @@ class ThreadRunCompleted(BaseModel): class ThreadRunIncomplete(BaseModel): + """ + Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) ends with status `incomplete`. + """ + data: Run """ Represents an execution run on a @@ -113,6 +144,10 @@ class ThreadRunIncomplete(BaseModel): class ThreadRunFailed(BaseModel): + """ + Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) fails. + """ + data: Run """ Represents an execution run on a @@ -123,6 +158,10 @@ class ThreadRunFailed(BaseModel): class ThreadRunCancelling(BaseModel): + """ + Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) moves to a `cancelling` status. + """ + data: Run """ Represents an execution run on a @@ -133,6 +172,10 @@ class ThreadRunCancelling(BaseModel): class ThreadRunCancelled(BaseModel): + """ + Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) is cancelled. + """ + data: Run """ Represents an execution run on a @@ -143,6 +186,10 @@ class ThreadRunCancelled(BaseModel): class ThreadRunExpired(BaseModel): + """ + Occurs when a [run](https://platform.openai.com/docs/api-reference/runs/object) expires. + """ + data: Run """ Represents an execution run on a @@ -153,6 +200,10 @@ class ThreadRunExpired(BaseModel): class ThreadRunStepCreated(BaseModel): + """ + Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) is created. + """ + data: RunStep """Represents a step in execution of a run.""" @@ -160,6 +211,10 @@ class ThreadRunStepCreated(BaseModel): class ThreadRunStepInProgress(BaseModel): + """ + Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) moves to an `in_progress` state. + """ + data: RunStep """Represents a step in execution of a run.""" @@ -167,6 +222,10 @@ class ThreadRunStepInProgress(BaseModel): class ThreadRunStepDelta(BaseModel): + """ + Occurs when parts of a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) are being streamed. + """ + data: RunStepDeltaEvent """Represents a run step delta i.e. @@ -177,6 +236,10 @@ class ThreadRunStepDelta(BaseModel): class ThreadRunStepCompleted(BaseModel): + """ + Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) is completed. + """ + data: RunStep """Represents a step in execution of a run.""" @@ -184,6 +247,10 @@ class ThreadRunStepCompleted(BaseModel): class ThreadRunStepFailed(BaseModel): + """ + Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) fails. + """ + data: RunStep """Represents a step in execution of a run.""" @@ -191,6 +258,10 @@ class ThreadRunStepFailed(BaseModel): class ThreadRunStepCancelled(BaseModel): + """ + Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) is cancelled. + """ + data: RunStep """Represents a step in execution of a run.""" @@ -198,6 +269,10 @@ class ThreadRunStepCancelled(BaseModel): class ThreadRunStepExpired(BaseModel): + """ + Occurs when a [run step](https://platform.openai.com/docs/api-reference/run-steps/step-object) expires. + """ + data: RunStep """Represents a step in execution of a run.""" @@ -205,6 +280,10 @@ class ThreadRunStepExpired(BaseModel): class ThreadMessageCreated(BaseModel): + """ + Occurs when a [message](https://platform.openai.com/docs/api-reference/messages/object) is created. + """ + data: Message """ Represents a message within a @@ -215,6 +294,10 @@ class ThreadMessageCreated(BaseModel): class ThreadMessageInProgress(BaseModel): + """ + Occurs when a [message](https://platform.openai.com/docs/api-reference/messages/object) moves to an `in_progress` state. + """ + data: Message """ Represents a message within a @@ -225,6 +308,10 @@ class ThreadMessageInProgress(BaseModel): class ThreadMessageDelta(BaseModel): + """ + Occurs when parts of a [Message](https://platform.openai.com/docs/api-reference/messages/object) are being streamed. + """ + data: MessageDeltaEvent """Represents a message delta i.e. @@ -235,6 +322,10 @@ class ThreadMessageDelta(BaseModel): class ThreadMessageCompleted(BaseModel): + """ + Occurs when a [message](https://platform.openai.com/docs/api-reference/messages/object) is completed. + """ + data: Message """ Represents a message within a @@ -245,6 +336,10 @@ class ThreadMessageCompleted(BaseModel): class ThreadMessageIncomplete(BaseModel): + """ + Occurs when a [message](https://platform.openai.com/docs/api-reference/messages/object) ends before it is completed. + """ + data: Message """ Represents a message within a @@ -255,6 +350,10 @@ class ThreadMessageIncomplete(BaseModel): class ErrorEvent(BaseModel): + """ + Occurs when an [error](https://platform.openai.com/docs/guides/error-codes#api-errors) occurs. This can happen due to an internal server error or a timeout. + """ + data: ErrorObject event: Literal["error"] diff --git a/src/openai/types/beta/assistant_tool_choice.py b/src/openai/types/beta/assistant_tool_choice.py index d73439f006..cabded0b3c 100644 --- a/src/openai/types/beta/assistant_tool_choice.py +++ b/src/openai/types/beta/assistant_tool_choice.py @@ -10,6 +10,11 @@ class AssistantToolChoice(BaseModel): + """Specifies a tool the model should use. + + Use to force the model to call a specific tool. + """ + type: Literal["function", "code_interpreter", "file_search"] """The type of the tool. If type is `function`, the function name must be set""" diff --git a/src/openai/types/beta/assistant_tool_choice_function.py b/src/openai/types/beta/assistant_tool_choice_function.py index 0c896d8087..87f38310ca 100644 --- a/src/openai/types/beta/assistant_tool_choice_function.py +++ b/src/openai/types/beta/assistant_tool_choice_function.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["AssistantToolChoiceFunction"] diff --git a/src/openai/types/beta/assistant_tool_choice_param.py b/src/openai/types/beta/assistant_tool_choice_param.py index 904f489e26..05916bb668 100644 --- a/src/openai/types/beta/assistant_tool_choice_param.py +++ b/src/openai/types/beta/assistant_tool_choice_param.py @@ -10,6 +10,11 @@ class AssistantToolChoiceParam(TypedDict, total=False): + """Specifies a tool the model should use. + + Use to force the model to call a specific tool. + """ + type: Required[Literal["function", "code_interpreter", "file_search"]] """The type of the tool. If type is `function`, the function name must be set""" diff --git a/src/openai/types/beta/assistant_update_params.py b/src/openai/types/beta/assistant_update_params.py index 5396233937..7896fcd9c6 100644 --- a/src/openai/types/beta/assistant_update_params.py +++ b/src/openai/types/beta/assistant_update_params.py @@ -2,10 +2,13 @@ from __future__ import annotations -from typing import List, Iterable, Optional -from typing_extensions import TypedDict +from typing import Union, Iterable, Optional +from typing_extensions import Literal, TypedDict +from ..._types import SequenceNotStr from .assistant_tool_param import AssistantToolParam +from ..shared_params.metadata import Metadata +from ..shared.reasoning_effort import ReasoningEffort from .assistant_response_format_option_param import AssistantResponseFormatOptionParam __all__ = ["AssistantUpdateParams", "ToolResources", "ToolResourcesCodeInterpreter", "ToolResourcesFileSearch"] @@ -21,32 +24,97 @@ class AssistantUpdateParams(TypedDict, total=False): The maximum length is 256,000 characters. """ - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ - model: str + model: Union[ + str, + Literal[ + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4.5-preview", + "gpt-4.5-preview-2025-02-27", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613", + ], + ] """ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. """ name: Optional[str] """The name of the assistant. The maximum length is 256 characters.""" + reasoning_effort: Optional[ReasoningEffort] + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + response_format: Optional[AssistantResponseFormatOptionParam] """Specifies the format that the model must output. - Compatible with [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + Compatible with [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -99,7 +167,7 @@ class AssistantUpdateParams(TypedDict, total=False): class ToolResourcesCodeInterpreter(TypedDict, total=False): - file_ids: List[str] + file_ids: SequenceNotStr[str] """ Overrides the list of [file](https://platform.openai.com/docs/api-reference/files) IDs made available @@ -109,7 +177,7 @@ class ToolResourcesCodeInterpreter(TypedDict, total=False): class ToolResourcesFileSearch(TypedDict, total=False): - vector_store_ids: List[str] + vector_store_ids: SequenceNotStr[str] """ Overrides the [vector store](https://platform.openai.com/docs/api-reference/vector-stores/object) @@ -119,6 +187,11 @@ class ToolResourcesFileSearch(TypedDict, total=False): class ToolResources(TypedDict, total=False): + """A set of resources that are used by the assistant's tools. + + The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs. + """ + code_interpreter: ToolResourcesCodeInterpreter file_search: ToolResourcesFileSearch diff --git a/src/openai/types/beta/chatkit/__init__.py b/src/openai/types/beta/chatkit/__init__.py new file mode 100644 index 0000000000..eafed9dd99 --- /dev/null +++ b/src/openai/types/beta/chatkit/__init__.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .chat_session import ChatSession as ChatSession +from .chatkit_thread import ChatKitThread as ChatKitThread +from .chatkit_attachment import ChatKitAttachment as ChatKitAttachment +from .thread_list_params import ThreadListParams as ThreadListParams +from .chat_session_status import ChatSessionStatus as ChatSessionStatus +from .chatkit_widget_item import ChatKitWidgetItem as ChatKitWidgetItem +from .chat_session_history import ChatSessionHistory as ChatSessionHistory +from .session_create_params import SessionCreateParams as SessionCreateParams +from .thread_delete_response import ThreadDeleteResponse as ThreadDeleteResponse +from .chat_session_file_upload import ChatSessionFileUpload as ChatSessionFileUpload +from .chat_session_rate_limits import ChatSessionRateLimits as ChatSessionRateLimits +from .chatkit_thread_item_list import ChatKitThreadItemList as ChatKitThreadItemList +from .thread_list_items_params import ThreadListItemsParams as ThreadListItemsParams +from .chat_session_workflow_param import ChatSessionWorkflowParam as ChatSessionWorkflowParam +from .chatkit_response_output_text import ChatKitResponseOutputText as ChatKitResponseOutputText +from .chat_session_rate_limits_param import ChatSessionRateLimitsParam as ChatSessionRateLimitsParam +from .chat_session_expires_after_param import ChatSessionExpiresAfterParam as ChatSessionExpiresAfterParam +from .chatkit_thread_user_message_item import ChatKitThreadUserMessageItem as ChatKitThreadUserMessageItem +from .chat_session_chatkit_configuration import ChatSessionChatKitConfiguration as ChatSessionChatKitConfiguration +from .chat_session_automatic_thread_titling import ( + ChatSessionAutomaticThreadTitling as ChatSessionAutomaticThreadTitling, +) +from .chatkit_thread_assistant_message_item import ( + ChatKitThreadAssistantMessageItem as ChatKitThreadAssistantMessageItem, +) +from .chat_session_chatkit_configuration_param import ( + ChatSessionChatKitConfigurationParam as ChatSessionChatKitConfigurationParam, +) diff --git a/src/openai/types/beta/chatkit/chat_session.py b/src/openai/types/beta/chatkit/chat_session.py new file mode 100644 index 0000000000..9db9fc93a0 --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel +from ..chatkit_workflow import ChatKitWorkflow +from .chat_session_status import ChatSessionStatus +from .chat_session_rate_limits import ChatSessionRateLimits +from .chat_session_chatkit_configuration import ChatSessionChatKitConfiguration + +__all__ = ["ChatSession"] + + +class ChatSession(BaseModel): + """Represents a ChatKit session and its resolved configuration.""" + + id: str + """Identifier for the ChatKit session.""" + + chatkit_configuration: ChatSessionChatKitConfiguration + """Resolved ChatKit feature configuration for the session.""" + + client_secret: str + """Ephemeral client secret that authenticates session requests.""" + + expires_at: int + """Unix timestamp (in seconds) for when the session expires.""" + + max_requests_per_1_minute: int + """Convenience copy of the per-minute request limit.""" + + object: Literal["chatkit.session"] + """Type discriminator that is always `chatkit.session`.""" + + rate_limits: ChatSessionRateLimits + """Resolved rate limit values.""" + + status: ChatSessionStatus + """Current lifecycle state of the session.""" + + user: str + """User identifier associated with the session.""" + + workflow: ChatKitWorkflow + """Workflow metadata for the session.""" diff --git a/src/openai/types/beta/chatkit/chat_session_automatic_thread_titling.py b/src/openai/types/beta/chatkit/chat_session_automatic_thread_titling.py new file mode 100644 index 0000000000..1d95255e06 --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session_automatic_thread_titling.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ...._models import BaseModel + +__all__ = ["ChatSessionAutomaticThreadTitling"] + + +class ChatSessionAutomaticThreadTitling(BaseModel): + """Automatic thread title preferences for the session.""" + + enabled: bool + """Whether automatic thread titling is enabled.""" diff --git a/src/openai/types/beta/chatkit/chat_session_chatkit_configuration.py b/src/openai/types/beta/chatkit/chat_session_chatkit_configuration.py new file mode 100644 index 0000000000..f9fa0ceff5 --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session_chatkit_configuration.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ...._models import BaseModel +from .chat_session_history import ChatSessionHistory +from .chat_session_file_upload import ChatSessionFileUpload +from .chat_session_automatic_thread_titling import ChatSessionAutomaticThreadTitling + +__all__ = ["ChatSessionChatKitConfiguration"] + + +class ChatSessionChatKitConfiguration(BaseModel): + """ChatKit configuration for the session.""" + + automatic_thread_titling: ChatSessionAutomaticThreadTitling + """Automatic thread titling preferences.""" + + file_upload: ChatSessionFileUpload + """Upload settings for the session.""" + + history: ChatSessionHistory + """History retention configuration.""" diff --git a/src/openai/types/beta/chatkit/chat_session_chatkit_configuration_param.py b/src/openai/types/beta/chatkit/chat_session_chatkit_configuration_param.py new file mode 100644 index 0000000000..834de71e71 --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session_chatkit_configuration_param.py @@ -0,0 +1,76 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ChatSessionChatKitConfigurationParam", "AutomaticThreadTitling", "FileUpload", "History"] + + +class AutomaticThreadTitling(TypedDict, total=False): + """Configuration for automatic thread titling. + + When omitted, automatic thread titling is enabled by default. + """ + + enabled: bool + """Enable automatic thread title generation. Defaults to true.""" + + +class FileUpload(TypedDict, total=False): + """Configuration for upload enablement and limits. + + When omitted, uploads are disabled by default (max_files 10, max_file_size 512 MB). + """ + + enabled: bool + """Enable uploads for this session. Defaults to false.""" + + max_file_size: int + """Maximum size in megabytes for each uploaded file. + + Defaults to 512 MB, which is the maximum allowable size. + """ + + max_files: int + """Maximum number of files that can be uploaded to the session. Defaults to 10.""" + + +class History(TypedDict, total=False): + """Configuration for chat history retention. + + When omitted, history is enabled by default with no limit on recent_threads (null). + """ + + enabled: bool + """Enables chat users to access previous ChatKit threads. Defaults to true.""" + + recent_threads: int + """Number of recent ChatKit threads users have access to. + + Defaults to unlimited when unset. + """ + + +class ChatSessionChatKitConfigurationParam(TypedDict, total=False): + """Optional per-session configuration settings for ChatKit behavior.""" + + automatic_thread_titling: AutomaticThreadTitling + """Configuration for automatic thread titling. + + When omitted, automatic thread titling is enabled by default. + """ + + file_upload: FileUpload + """Configuration for upload enablement and limits. + + When omitted, uploads are disabled by default (max_files 10, max_file_size 512 + MB). + """ + + history: History + """Configuration for chat history retention. + + When omitted, history is enabled by default with no limit on recent_threads + (null). + """ diff --git a/src/openai/types/beta/chatkit/chat_session_expires_after_param.py b/src/openai/types/beta/chatkit/chat_session_expires_after_param.py new file mode 100644 index 0000000000..c1de8a767a --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session_expires_after_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatSessionExpiresAfterParam"] + + +class ChatSessionExpiresAfterParam(TypedDict, total=False): + """Controls when the session expires relative to an anchor timestamp.""" + + anchor: Required[Literal["created_at"]] + """Base timestamp used to calculate expiration. Currently fixed to `created_at`.""" + + seconds: Required[int] + """Number of seconds after the anchor when the session expires.""" diff --git a/src/openai/types/beta/chatkit/chat_session_file_upload.py b/src/openai/types/beta/chatkit/chat_session_file_upload.py new file mode 100644 index 0000000000..0275859d27 --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session_file_upload.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ...._models import BaseModel + +__all__ = ["ChatSessionFileUpload"] + + +class ChatSessionFileUpload(BaseModel): + """Upload permissions and limits applied to the session.""" + + enabled: bool + """Indicates if uploads are enabled for the session.""" + + max_file_size: Optional[int] = None + """Maximum upload size in megabytes.""" + + max_files: Optional[int] = None + """Maximum number of uploads allowed during the session.""" diff --git a/src/openai/types/beta/chatkit/chat_session_history.py b/src/openai/types/beta/chatkit/chat_session_history.py new file mode 100644 index 0000000000..54690009c2 --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session_history.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ...._models import BaseModel + +__all__ = ["ChatSessionHistory"] + + +class ChatSessionHistory(BaseModel): + """History retention preferences returned for the session.""" + + enabled: bool + """Indicates if chat history is persisted for the session.""" + + recent_threads: Optional[int] = None + """Number of prior threads surfaced in history views. + + Defaults to null when all history is retained. + """ diff --git a/src/openai/types/beta/chatkit/chat_session_rate_limits.py b/src/openai/types/beta/chatkit/chat_session_rate_limits.py new file mode 100644 index 0000000000..7c5bd94e76 --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session_rate_limits.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ...._models import BaseModel + +__all__ = ["ChatSessionRateLimits"] + + +class ChatSessionRateLimits(BaseModel): + """Active per-minute request limit for the session.""" + + max_requests_per_1_minute: int + """Maximum allowed requests per one-minute window.""" diff --git a/src/openai/types/beta/chatkit/chat_session_rate_limits_param.py b/src/openai/types/beta/chatkit/chat_session_rate_limits_param.py new file mode 100644 index 0000000000..578f20b0c3 --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session_rate_limits_param.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ChatSessionRateLimitsParam"] + + +class ChatSessionRateLimitsParam(TypedDict, total=False): + """Controls request rate limits for the session.""" + + max_requests_per_1_minute: int + """Maximum number of requests allowed per minute for the session. Defaults to 10.""" diff --git a/src/openai/types/beta/chatkit/chat_session_status.py b/src/openai/types/beta/chatkit/chat_session_status.py new file mode 100644 index 0000000000..a483099c6c --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session_status.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ChatSessionStatus"] + +ChatSessionStatus: TypeAlias = Literal["active", "expired", "cancelled"] diff --git a/src/openai/types/beta/chatkit/chat_session_workflow_param.py b/src/openai/types/beta/chatkit/chat_session_workflow_param.py new file mode 100644 index 0000000000..abf52de526 --- /dev/null +++ b/src/openai/types/beta/chatkit/chat_session_workflow_param.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union +from typing_extensions import Required, TypedDict + +__all__ = ["ChatSessionWorkflowParam", "Tracing"] + + +class Tracing(TypedDict, total=False): + """Optional tracing overrides for the workflow invocation. + + When omitted, tracing is enabled by default. + """ + + enabled: bool + """Whether tracing is enabled during the session. Defaults to true.""" + + +class ChatSessionWorkflowParam(TypedDict, total=False): + """Workflow reference and overrides applied to the chat session.""" + + id: Required[str] + """Identifier for the workflow invoked by the session.""" + + state_variables: Dict[str, Union[str, bool, float]] + """State variables forwarded to the workflow. + + Keys may be up to 64 characters, values must be primitive types, and the map + defaults to an empty object. + """ + + tracing: Tracing + """Optional tracing overrides for the workflow invocation. + + When omitted, tracing is enabled by default. + """ + + version: str + """Specific workflow version to run. Defaults to the latest deployed version.""" diff --git a/src/openai/types/beta/chatkit/chatkit_attachment.py b/src/openai/types/beta/chatkit/chatkit_attachment.py new file mode 100644 index 0000000000..7750925e03 --- /dev/null +++ b/src/openai/types/beta/chatkit/chatkit_attachment.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ChatKitAttachment"] + + +class ChatKitAttachment(BaseModel): + """Attachment metadata included on thread items.""" + + id: str + """Identifier for the attachment.""" + + mime_type: str + """MIME type of the attachment.""" + + name: str + """Original display name for the attachment.""" + + preview_url: Optional[str] = None + """Preview URL for rendering the attachment inline.""" + + type: Literal["image", "file"] + """Attachment discriminator.""" diff --git a/src/openai/types/beta/chatkit/chatkit_response_output_text.py b/src/openai/types/beta/chatkit/chatkit_response_output_text.py new file mode 100644 index 0000000000..1348fed2b2 --- /dev/null +++ b/src/openai/types/beta/chatkit/chatkit_response_output_text.py @@ -0,0 +1,72 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = [ + "ChatKitResponseOutputText", + "Annotation", + "AnnotationFile", + "AnnotationFileSource", + "AnnotationURL", + "AnnotationURLSource", +] + + +class AnnotationFileSource(BaseModel): + """File attachment referenced by the annotation.""" + + filename: str + """Filename referenced by the annotation.""" + + type: Literal["file"] + """Type discriminator that is always `file`.""" + + +class AnnotationFile(BaseModel): + """Annotation that references an uploaded file.""" + + source: AnnotationFileSource + """File attachment referenced by the annotation.""" + + type: Literal["file"] + """Type discriminator that is always `file` for this annotation.""" + + +class AnnotationURLSource(BaseModel): + """URL referenced by the annotation.""" + + type: Literal["url"] + """Type discriminator that is always `url`.""" + + url: str + """URL referenced by the annotation.""" + + +class AnnotationURL(BaseModel): + """Annotation that references a URL.""" + + source: AnnotationURLSource + """URL referenced by the annotation.""" + + type: Literal["url"] + """Type discriminator that is always `url` for this annotation.""" + + +Annotation: TypeAlias = Annotated[Union[AnnotationFile, AnnotationURL], PropertyInfo(discriminator="type")] + + +class ChatKitResponseOutputText(BaseModel): + """Assistant response text accompanied by optional annotations.""" + + annotations: List[Annotation] + """Ordered list of annotations attached to the response text.""" + + text: str + """Assistant generated text.""" + + type: Literal["output_text"] + """Type discriminator that is always `output_text`.""" diff --git a/src/openai/types/beta/chatkit/chatkit_thread.py b/src/openai/types/beta/chatkit/chatkit_thread.py new file mode 100644 index 0000000000..32075233d8 --- /dev/null +++ b/src/openai/types/beta/chatkit/chatkit_thread.py @@ -0,0 +1,64 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel + +__all__ = ["ChatKitThread", "Status", "StatusActive", "StatusLocked", "StatusClosed"] + + +class StatusActive(BaseModel): + """Indicates that a thread is active.""" + + type: Literal["active"] + """Status discriminator that is always `active`.""" + + +class StatusLocked(BaseModel): + """Indicates that a thread is locked and cannot accept new input.""" + + reason: Optional[str] = None + """Reason that the thread was locked. Defaults to null when no reason is recorded.""" + + type: Literal["locked"] + """Status discriminator that is always `locked`.""" + + +class StatusClosed(BaseModel): + """Indicates that a thread has been closed.""" + + reason: Optional[str] = None + """Reason that the thread was closed. Defaults to null when no reason is recorded.""" + + type: Literal["closed"] + """Status discriminator that is always `closed`.""" + + +Status: TypeAlias = Annotated[Union[StatusActive, StatusLocked, StatusClosed], PropertyInfo(discriminator="type")] + + +class ChatKitThread(BaseModel): + """Represents a ChatKit thread and its current status.""" + + id: str + """Identifier of the thread.""" + + created_at: int + """Unix timestamp (in seconds) for when the thread was created.""" + + object: Literal["chatkit.thread"] + """Type discriminator that is always `chatkit.thread`.""" + + status: Status + """Current status for the thread. Defaults to `active` for newly created threads.""" + + title: Optional[str] = None + """Optional human-readable title for the thread. + + Defaults to null when no title has been generated. + """ + + user: str + """Free-form string that identifies your end user who owns the thread.""" diff --git a/src/openai/types/beta/chatkit/chatkit_thread_assistant_message_item.py b/src/openai/types/beta/chatkit/chatkit_thread_assistant_message_item.py new file mode 100644 index 0000000000..337f53a83d --- /dev/null +++ b/src/openai/types/beta/chatkit/chatkit_thread_assistant_message_item.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import Literal + +from ...._models import BaseModel +from .chatkit_response_output_text import ChatKitResponseOutputText + +__all__ = ["ChatKitThreadAssistantMessageItem"] + + +class ChatKitThreadAssistantMessageItem(BaseModel): + """Assistant-authored message within a thread.""" + + id: str + """Identifier of the thread item.""" + + content: List[ChatKitResponseOutputText] + """Ordered assistant response segments.""" + + created_at: int + """Unix timestamp (in seconds) for when the item was created.""" + + object: Literal["chatkit.thread_item"] + """Type discriminator that is always `chatkit.thread_item`.""" + + thread_id: str + """Identifier of the parent thread.""" + + type: Literal["chatkit.assistant_message"] + """Type discriminator that is always `chatkit.assistant_message`.""" diff --git a/src/openai/types/beta/chatkit/chatkit_thread_item_list.py b/src/openai/types/beta/chatkit/chatkit_thread_item_list.py new file mode 100644 index 0000000000..049ca54429 --- /dev/null +++ b/src/openai/types/beta/chatkit/chatkit_thread_item_list.py @@ -0,0 +1,154 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel +from .chatkit_widget_item import ChatKitWidgetItem +from .chatkit_thread_user_message_item import ChatKitThreadUserMessageItem +from .chatkit_thread_assistant_message_item import ChatKitThreadAssistantMessageItem + +__all__ = [ + "ChatKitThreadItemList", + "Data", + "DataChatKitClientToolCall", + "DataChatKitTask", + "DataChatKitTaskGroup", + "DataChatKitTaskGroupTask", +] + + +class DataChatKitClientToolCall(BaseModel): + """Record of a client side tool invocation initiated by the assistant.""" + + id: str + """Identifier of the thread item.""" + + arguments: str + """JSON-encoded arguments that were sent to the tool.""" + + call_id: str + """Identifier for the client tool call.""" + + created_at: int + """Unix timestamp (in seconds) for when the item was created.""" + + name: str + """Tool name that was invoked.""" + + object: Literal["chatkit.thread_item"] + """Type discriminator that is always `chatkit.thread_item`.""" + + output: Optional[str] = None + """JSON-encoded output captured from the tool. + + Defaults to null while execution is in progress. + """ + + status: Literal["in_progress", "completed"] + """Execution status for the tool call.""" + + thread_id: str + """Identifier of the parent thread.""" + + type: Literal["chatkit.client_tool_call"] + """Type discriminator that is always `chatkit.client_tool_call`.""" + + +class DataChatKitTask(BaseModel): + """Task emitted by the workflow to show progress and status updates.""" + + id: str + """Identifier of the thread item.""" + + created_at: int + """Unix timestamp (in seconds) for when the item was created.""" + + heading: Optional[str] = None + """Optional heading for the task. Defaults to null when not provided.""" + + object: Literal["chatkit.thread_item"] + """Type discriminator that is always `chatkit.thread_item`.""" + + summary: Optional[str] = None + """Optional summary that describes the task. Defaults to null when omitted.""" + + task_type: Literal["custom", "thought"] + """Subtype for the task.""" + + thread_id: str + """Identifier of the parent thread.""" + + type: Literal["chatkit.task"] + """Type discriminator that is always `chatkit.task`.""" + + +class DataChatKitTaskGroupTask(BaseModel): + """Task entry that appears within a TaskGroup.""" + + heading: Optional[str] = None + """Optional heading for the grouped task. Defaults to null when not provided.""" + + summary: Optional[str] = None + """Optional summary that describes the grouped task. + + Defaults to null when omitted. + """ + + type: Literal["custom", "thought"] + """Subtype for the grouped task.""" + + +class DataChatKitTaskGroup(BaseModel): + """Collection of workflow tasks grouped together in the thread.""" + + id: str + """Identifier of the thread item.""" + + created_at: int + """Unix timestamp (in seconds) for when the item was created.""" + + object: Literal["chatkit.thread_item"] + """Type discriminator that is always `chatkit.thread_item`.""" + + tasks: List[DataChatKitTaskGroupTask] + """Tasks included in the group.""" + + thread_id: str + """Identifier of the parent thread.""" + + type: Literal["chatkit.task_group"] + """Type discriminator that is always `chatkit.task_group`.""" + + +Data: TypeAlias = Annotated[ + Union[ + ChatKitThreadUserMessageItem, + ChatKitThreadAssistantMessageItem, + ChatKitWidgetItem, + DataChatKitClientToolCall, + DataChatKitTask, + DataChatKitTaskGroup, + ], + PropertyInfo(discriminator="type"), +] + + +class ChatKitThreadItemList(BaseModel): + """A paginated list of thread items rendered for the ChatKit API.""" + + data: List[Data] + """A list of items""" + + first_id: Optional[str] = None + """The ID of the first item in the list.""" + + has_more: bool + """Whether there are more items available.""" + + last_id: Optional[str] = None + """The ID of the last item in the list.""" + + object: Literal["list"] + """The type of object returned, must be `list`.""" diff --git a/src/openai/types/beta/chatkit/chatkit_thread_user_message_item.py b/src/openai/types/beta/chatkit/chatkit_thread_user_message_item.py new file mode 100644 index 0000000000..d7552c4f2e --- /dev/null +++ b/src/openai/types/beta/chatkit/chatkit_thread_user_message_item.py @@ -0,0 +1,87 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel +from .chatkit_attachment import ChatKitAttachment + +__all__ = [ + "ChatKitThreadUserMessageItem", + "Content", + "ContentInputText", + "ContentQuotedText", + "InferenceOptions", + "InferenceOptionsToolChoice", +] + + +class ContentInputText(BaseModel): + """Text block that a user contributed to the thread.""" + + text: str + """Plain-text content supplied by the user.""" + + type: Literal["input_text"] + """Type discriminator that is always `input_text`.""" + + +class ContentQuotedText(BaseModel): + """Quoted snippet that the user referenced in their message.""" + + text: str + """Quoted text content.""" + + type: Literal["quoted_text"] + """Type discriminator that is always `quoted_text`.""" + + +Content: TypeAlias = Annotated[Union[ContentInputText, ContentQuotedText], PropertyInfo(discriminator="type")] + + +class InferenceOptionsToolChoice(BaseModel): + """Preferred tool to invoke. Defaults to null when ChatKit should auto-select.""" + + id: str + """Identifier of the requested tool.""" + + +class InferenceOptions(BaseModel): + """Inference overrides applied to the message. Defaults to null when unset.""" + + model: Optional[str] = None + """Model name that generated the response. + + Defaults to null when using the session default. + """ + + tool_choice: Optional[InferenceOptionsToolChoice] = None + """Preferred tool to invoke. Defaults to null when ChatKit should auto-select.""" + + +class ChatKitThreadUserMessageItem(BaseModel): + """User-authored messages within a thread.""" + + id: str + """Identifier of the thread item.""" + + attachments: List[ChatKitAttachment] + """Attachments associated with the user message. Defaults to an empty list.""" + + content: List[Content] + """Ordered content elements supplied by the user.""" + + created_at: int + """Unix timestamp (in seconds) for when the item was created.""" + + inference_options: Optional[InferenceOptions] = None + """Inference overrides applied to the message. Defaults to null when unset.""" + + object: Literal["chatkit.thread_item"] + """Type discriminator that is always `chatkit.thread_item`.""" + + thread_id: str + """Identifier of the parent thread.""" + + type: Literal["chatkit.user_message"] diff --git a/src/openai/types/beta/chatkit/chatkit_widget_item.py b/src/openai/types/beta/chatkit/chatkit_widget_item.py new file mode 100644 index 0000000000..a269c736fb --- /dev/null +++ b/src/openai/types/beta/chatkit/chatkit_widget_item.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ChatKitWidgetItem"] + + +class ChatKitWidgetItem(BaseModel): + """Thread item that renders a widget payload.""" + + id: str + """Identifier of the thread item.""" + + created_at: int + """Unix timestamp (in seconds) for when the item was created.""" + + object: Literal["chatkit.thread_item"] + """Type discriminator that is always `chatkit.thread_item`.""" + + thread_id: str + """Identifier of the parent thread.""" + + type: Literal["chatkit.widget"] + """Type discriminator that is always `chatkit.widget`.""" + + widget: str + """Serialized widget payload rendered in the UI.""" diff --git a/src/openai/types/beta/chatkit/session_create_params.py b/src/openai/types/beta/chatkit/session_create_params.py new file mode 100644 index 0000000000..1803d18cf6 --- /dev/null +++ b/src/openai/types/beta/chatkit/session_create_params.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from .chat_session_workflow_param import ChatSessionWorkflowParam +from .chat_session_rate_limits_param import ChatSessionRateLimitsParam +from .chat_session_expires_after_param import ChatSessionExpiresAfterParam +from .chat_session_chatkit_configuration_param import ChatSessionChatKitConfigurationParam + +__all__ = ["SessionCreateParams"] + + +class SessionCreateParams(TypedDict, total=False): + user: Required[str] + """ + A free-form string that identifies your end user; ensures this Session can + access other objects that have the same `user` scope. + """ + + workflow: Required[ChatSessionWorkflowParam] + """Workflow that powers the session.""" + + chatkit_configuration: ChatSessionChatKitConfigurationParam + """Optional overrides for ChatKit runtime configuration features""" + + expires_after: ChatSessionExpiresAfterParam + """Optional override for session expiration timing in seconds from creation. + + Defaults to 10 minutes. + """ + + rate_limits: ChatSessionRateLimitsParam + """Optional override for per-minute request limits. When omitted, defaults to 10.""" diff --git a/src/openai/types/beta/chatkit/thread_delete_response.py b/src/openai/types/beta/chatkit/thread_delete_response.py new file mode 100644 index 0000000000..45b686bf8b --- /dev/null +++ b/src/openai/types/beta/chatkit/thread_delete_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ThreadDeleteResponse"] + + +class ThreadDeleteResponse(BaseModel): + """Confirmation payload returned after deleting a thread.""" + + id: str + """Identifier of the deleted thread.""" + + deleted: bool + """Indicates that the thread has been deleted.""" + + object: Literal["chatkit.thread.deleted"] + """Type discriminator that is always `chatkit.thread.deleted`.""" diff --git a/src/openai/types/beta/chatkit/thread_list_items_params.py b/src/openai/types/beta/chatkit/thread_list_items_params.py new file mode 100644 index 0000000000..95c959d719 --- /dev/null +++ b/src/openai/types/beta/chatkit/thread_list_items_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["ThreadListItemsParams"] + + +class ThreadListItemsParams(TypedDict, total=False): + after: str + """List items created after this thread item ID. + + Defaults to null for the first page. + """ + + before: str + """List items created before this thread item ID. + + Defaults to null for the newest results. + """ + + limit: int + """Maximum number of thread items to return. Defaults to 20.""" + + order: Literal["asc", "desc"] + """Sort order for results by creation time. Defaults to `desc`.""" diff --git a/src/openai/types/beta/chatkit/thread_list_params.py b/src/openai/types/beta/chatkit/thread_list_params.py new file mode 100644 index 0000000000..bb759c7ea3 --- /dev/null +++ b/src/openai/types/beta/chatkit/thread_list_params.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["ThreadListParams"] + + +class ThreadListParams(TypedDict, total=False): + after: str + """List items created after this thread item ID. + + Defaults to null for the first page. + """ + + before: str + """List items created before this thread item ID. + + Defaults to null for the newest results. + """ + + limit: int + """Maximum number of thread items to return. Defaults to 20.""" + + order: Literal["asc", "desc"] + """Sort order for results by creation time. Defaults to `desc`.""" + + user: str + """Filter threads that belong to this user identifier. + + Defaults to null to return all users. + """ diff --git a/src/openai/types/beta/chatkit_workflow.py b/src/openai/types/beta/chatkit_workflow.py new file mode 100644 index 0000000000..b6f5b55b4a --- /dev/null +++ b/src/openai/types/beta/chatkit_workflow.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Union, Optional + +from ..._models import BaseModel + +__all__ = ["ChatKitWorkflow", "Tracing"] + + +class Tracing(BaseModel): + """Tracing settings applied to the workflow.""" + + enabled: bool + """Indicates whether tracing is enabled.""" + + +class ChatKitWorkflow(BaseModel): + """Workflow metadata and state returned for the session.""" + + id: str + """Identifier of the workflow backing the session.""" + + state_variables: Optional[Dict[str, Union[str, bool, float]]] = None + """State variable key-value pairs applied when invoking the workflow. + + Defaults to null when no overrides were provided. + """ + + tracing: Tracing + """Tracing settings applied to the workflow.""" + + version: Optional[str] = None + """Specific workflow version used for the session. + + Defaults to null when using the latest deployment. + """ diff --git a/src/openai/types/beta/file_search_tool.py b/src/openai/types/beta/file_search_tool.py index aee6593e89..9e33249e0b 100644 --- a/src/openai/types/beta/file_search_tool.py +++ b/src/openai/types/beta/file_search_tool.py @@ -9,6 +9,13 @@ class FileSearchRankingOptions(BaseModel): + """The ranking options for the file search. + + If not specified, the file search tool will use the `auto` ranker and a score_threshold of 0. + + See the [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. + """ + score_threshold: float """The score threshold for the file search. @@ -23,6 +30,8 @@ class FileSearchRankingOptions(BaseModel): class FileSearch(BaseModel): + """Overrides for the file search tool.""" + max_num_results: Optional[int] = None """The maximum number of results the file search tool should output. @@ -31,7 +40,7 @@ class FileSearch(BaseModel): Note that the file search tool may output fewer than `max_num_results` results. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. """ @@ -42,7 +51,7 @@ class FileSearch(BaseModel): score_threshold of 0. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. """ diff --git a/src/openai/types/beta/file_search_tool_param.py b/src/openai/types/beta/file_search_tool_param.py index 5ce91207ba..9906b4b2a4 100644 --- a/src/openai/types/beta/file_search_tool_param.py +++ b/src/openai/types/beta/file_search_tool_param.py @@ -8,6 +8,13 @@ class FileSearchRankingOptions(TypedDict, total=False): + """The ranking options for the file search. + + If not specified, the file search tool will use the `auto` ranker and a score_threshold of 0. + + See the [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. + """ + score_threshold: Required[float] """The score threshold for the file search. @@ -22,6 +29,8 @@ class FileSearchRankingOptions(TypedDict, total=False): class FileSearch(TypedDict, total=False): + """Overrides for the file search tool.""" + max_num_results: int """The maximum number of results the file search tool should output. @@ -30,7 +39,7 @@ class FileSearch(TypedDict, total=False): Note that the file search tool may output fewer than `max_num_results` results. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. """ @@ -41,7 +50,7 @@ class FileSearch(TypedDict, total=False): score_threshold of 0. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. """ diff --git a/src/openai/types/beta/realtime/__init__.py b/src/openai/types/beta/realtime/__init__.py new file mode 100644 index 0000000000..0374b9b457 --- /dev/null +++ b/src/openai/types/beta/realtime/__init__.py @@ -0,0 +1,96 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .session import Session as Session +from .error_event import ErrorEvent as ErrorEvent +from .conversation_item import ConversationItem as ConversationItem +from .realtime_response import RealtimeResponse as RealtimeResponse +from .response_done_event import ResponseDoneEvent as ResponseDoneEvent +from .session_update_event import SessionUpdateEvent as SessionUpdateEvent +from .realtime_client_event import RealtimeClientEvent as RealtimeClientEvent +from .realtime_server_event import RealtimeServerEvent as RealtimeServerEvent +from .response_cancel_event import ResponseCancelEvent as ResponseCancelEvent +from .response_create_event import ResponseCreateEvent as ResponseCreateEvent +from .session_create_params import SessionCreateParams as SessionCreateParams +from .session_created_event import SessionCreatedEvent as SessionCreatedEvent +from .session_updated_event import SessionUpdatedEvent as SessionUpdatedEvent +from .transcription_session import TranscriptionSession as TranscriptionSession +from .response_created_event import ResponseCreatedEvent as ResponseCreatedEvent +from .conversation_item_param import ConversationItemParam as ConversationItemParam +from .realtime_connect_params import RealtimeConnectParams as RealtimeConnectParams +from .realtime_response_usage import RealtimeResponseUsage as RealtimeResponseUsage +from .session_create_response import SessionCreateResponse as SessionCreateResponse +from .realtime_response_status import RealtimeResponseStatus as RealtimeResponseStatus +from .response_text_done_event import ResponseTextDoneEvent as ResponseTextDoneEvent +from .conversation_item_content import ConversationItemContent as ConversationItemContent +from .rate_limits_updated_event import RateLimitsUpdatedEvent as RateLimitsUpdatedEvent +from .response_audio_done_event import ResponseAudioDoneEvent as ResponseAudioDoneEvent +from .response_text_delta_event import ResponseTextDeltaEvent as ResponseTextDeltaEvent +from .conversation_created_event import ConversationCreatedEvent as ConversationCreatedEvent +from .response_audio_delta_event import ResponseAudioDeltaEvent as ResponseAudioDeltaEvent +from .session_update_event_param import SessionUpdateEventParam as SessionUpdateEventParam +from .realtime_client_event_param import RealtimeClientEventParam as RealtimeClientEventParam +from .response_cancel_event_param import ResponseCancelEventParam as ResponseCancelEventParam +from .response_create_event_param import ResponseCreateEventParam as ResponseCreateEventParam +from .transcription_session_update import TranscriptionSessionUpdate as TranscriptionSessionUpdate +from .conversation_item_create_event import ConversationItemCreateEvent as ConversationItemCreateEvent +from .conversation_item_delete_event import ConversationItemDeleteEvent as ConversationItemDeleteEvent +from .input_audio_buffer_clear_event import InputAudioBufferClearEvent as InputAudioBufferClearEvent +from .conversation_item_content_param import ConversationItemContentParam as ConversationItemContentParam +from .conversation_item_created_event import ConversationItemCreatedEvent as ConversationItemCreatedEvent +from .conversation_item_deleted_event import ConversationItemDeletedEvent as ConversationItemDeletedEvent +from .input_audio_buffer_append_event import InputAudioBufferAppendEvent as InputAudioBufferAppendEvent +from .input_audio_buffer_commit_event import InputAudioBufferCommitEvent as InputAudioBufferCommitEvent +from .response_output_item_done_event import ResponseOutputItemDoneEvent as ResponseOutputItemDoneEvent +from .conversation_item_retrieve_event import ConversationItemRetrieveEvent as ConversationItemRetrieveEvent +from .conversation_item_truncate_event import ConversationItemTruncateEvent as ConversationItemTruncateEvent +from .conversation_item_with_reference import ConversationItemWithReference as ConversationItemWithReference +from .input_audio_buffer_cleared_event import InputAudioBufferClearedEvent as InputAudioBufferClearedEvent +from .response_content_part_done_event import ResponseContentPartDoneEvent as ResponseContentPartDoneEvent +from .response_output_item_added_event import ResponseOutputItemAddedEvent as ResponseOutputItemAddedEvent +from .conversation_item_truncated_event import ConversationItemTruncatedEvent as ConversationItemTruncatedEvent +from .response_content_part_added_event import ResponseContentPartAddedEvent as ResponseContentPartAddedEvent +from .input_audio_buffer_committed_event import InputAudioBufferCommittedEvent as InputAudioBufferCommittedEvent +from .transcription_session_update_param import TranscriptionSessionUpdateParam as TranscriptionSessionUpdateParam +from .transcription_session_create_params import TranscriptionSessionCreateParams as TranscriptionSessionCreateParams +from .transcription_session_updated_event import TranscriptionSessionUpdatedEvent as TranscriptionSessionUpdatedEvent +from .conversation_item_create_event_param import ConversationItemCreateEventParam as ConversationItemCreateEventParam +from .conversation_item_delete_event_param import ConversationItemDeleteEventParam as ConversationItemDeleteEventParam +from .input_audio_buffer_clear_event_param import InputAudioBufferClearEventParam as InputAudioBufferClearEventParam +from .response_audio_transcript_done_event import ResponseAudioTranscriptDoneEvent as ResponseAudioTranscriptDoneEvent +from .input_audio_buffer_append_event_param import InputAudioBufferAppendEventParam as InputAudioBufferAppendEventParam +from .input_audio_buffer_commit_event_param import InputAudioBufferCommitEventParam as InputAudioBufferCommitEventParam +from .response_audio_transcript_delta_event import ( + ResponseAudioTranscriptDeltaEvent as ResponseAudioTranscriptDeltaEvent, +) +from .conversation_item_retrieve_event_param import ( + ConversationItemRetrieveEventParam as ConversationItemRetrieveEventParam, +) +from .conversation_item_truncate_event_param import ( + ConversationItemTruncateEventParam as ConversationItemTruncateEventParam, +) +from .conversation_item_with_reference_param import ( + ConversationItemWithReferenceParam as ConversationItemWithReferenceParam, +) +from .input_audio_buffer_speech_started_event import ( + InputAudioBufferSpeechStartedEvent as InputAudioBufferSpeechStartedEvent, +) +from .input_audio_buffer_speech_stopped_event import ( + InputAudioBufferSpeechStoppedEvent as InputAudioBufferSpeechStoppedEvent, +) +from .response_function_call_arguments_done_event import ( + ResponseFunctionCallArgumentsDoneEvent as ResponseFunctionCallArgumentsDoneEvent, +) +from .response_function_call_arguments_delta_event import ( + ResponseFunctionCallArgumentsDeltaEvent as ResponseFunctionCallArgumentsDeltaEvent, +) +from .conversation_item_input_audio_transcription_delta_event import ( + ConversationItemInputAudioTranscriptionDeltaEvent as ConversationItemInputAudioTranscriptionDeltaEvent, +) +from .conversation_item_input_audio_transcription_failed_event import ( + ConversationItemInputAudioTranscriptionFailedEvent as ConversationItemInputAudioTranscriptionFailedEvent, +) +from .conversation_item_input_audio_transcription_completed_event import ( + ConversationItemInputAudioTranscriptionCompletedEvent as ConversationItemInputAudioTranscriptionCompletedEvent, +) diff --git a/src/openai/types/beta/realtime/conversation_created_event.py b/src/openai/types/beta/realtime/conversation_created_event.py new file mode 100644 index 0000000000..4ba0540867 --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_created_event.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ConversationCreatedEvent", "Conversation"] + + +class Conversation(BaseModel): + id: Optional[str] = None + """The unique ID of the conversation.""" + + object: Optional[Literal["realtime.conversation"]] = None + """The object type, must be `realtime.conversation`.""" + + +class ConversationCreatedEvent(BaseModel): + conversation: Conversation + """The conversation resource.""" + + event_id: str + """The unique ID of the server event.""" + + type: Literal["conversation.created"] + """The event type, must be `conversation.created`.""" diff --git a/src/openai/types/beta/realtime/conversation_item.py b/src/openai/types/beta/realtime/conversation_item.py new file mode 100644 index 0000000000..21b7a8ac1f --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item.py @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel +from .conversation_item_content import ConversationItemContent + +__all__ = ["ConversationItem"] + + +class ConversationItem(BaseModel): + id: Optional[str] = None + """ + The unique ID of the item, this can be generated by the client to help manage + server-side context, but is not required because the server will generate one if + not provided. + """ + + arguments: Optional[str] = None + """The arguments of the function call (for `function_call` items).""" + + call_id: Optional[str] = None + """ + The ID of the function call (for `function_call` and `function_call_output` + items). If passed on a `function_call_output` item, the server will check that a + `function_call` item with the same ID exists in the conversation history. + """ + + content: Optional[List[ConversationItemContent]] = None + """The content of the message, applicable for `message` items. + + - Message items of role `system` support only `input_text` content + - Message items of role `user` support `input_text` and `input_audio` content + - Message items of role `assistant` support `text` content. + """ + + name: Optional[str] = None + """The name of the function being called (for `function_call` items).""" + + object: Optional[Literal["realtime.item"]] = None + """Identifier for the API object being returned - always `realtime.item`.""" + + output: Optional[str] = None + """The output of the function call (for `function_call_output` items).""" + + role: Optional[Literal["user", "assistant", "system"]] = None + """ + The role of the message sender (`user`, `assistant`, `system`), only applicable + for `message` items. + """ + + status: Optional[Literal["completed", "incomplete", "in_progress"]] = None + """The status of the item (`completed`, `incomplete`, `in_progress`). + + These have no effect on the conversation, but are accepted for consistency with + the `conversation.item.created` event. + """ + + type: Optional[Literal["message", "function_call", "function_call_output"]] = None + """The type of the item (`message`, `function_call`, `function_call_output`).""" diff --git a/src/openai/types/beta/realtime/conversation_item_content.py b/src/openai/types/beta/realtime/conversation_item_content.py new file mode 100644 index 0000000000..fe9cef80e3 --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_content.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ConversationItemContent"] + + +class ConversationItemContent(BaseModel): + id: Optional[str] = None + """ + ID of a previous conversation item to reference (for `item_reference` content + types in `response.create` events). These can reference both client and server + created items. + """ + + audio: Optional[str] = None + """Base64-encoded audio bytes, used for `input_audio` content type.""" + + text: Optional[str] = None + """The text content, used for `input_text` and `text` content types.""" + + transcript: Optional[str] = None + """The transcript of the audio, used for `input_audio` and `audio` content types.""" + + type: Optional[Literal["input_text", "input_audio", "item_reference", "text", "audio"]] = None + """ + The content type (`input_text`, `input_audio`, `item_reference`, `text`, + `audio`). + """ diff --git a/src/openai/types/beta/realtime/conversation_item_content_param.py b/src/openai/types/beta/realtime/conversation_item_content_param.py new file mode 100644 index 0000000000..6042e7f90f --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_content_param.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["ConversationItemContentParam"] + + +class ConversationItemContentParam(TypedDict, total=False): + id: str + """ + ID of a previous conversation item to reference (for `item_reference` content + types in `response.create` events). These can reference both client and server + created items. + """ + + audio: str + """Base64-encoded audio bytes, used for `input_audio` content type.""" + + text: str + """The text content, used for `input_text` and `text` content types.""" + + transcript: str + """The transcript of the audio, used for `input_audio` and `audio` content types.""" + + type: Literal["input_text", "input_audio", "item_reference", "text", "audio"] + """ + The content type (`input_text`, `input_audio`, `item_reference`, `text`, + `audio`). + """ diff --git a/src/openai/types/beta/realtime/conversation_item_create_event.py b/src/openai/types/beta/realtime/conversation_item_create_event.py new file mode 100644 index 0000000000..f19d552a92 --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_create_event.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ConversationItemCreateEvent"] + + +class ConversationItemCreateEvent(BaseModel): + item: ConversationItem + """The item to add to the conversation.""" + + type: Literal["conversation.item.create"] + """The event type, must be `conversation.item.create`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" + + previous_item_id: Optional[str] = None + """The ID of the preceding item after which the new item will be inserted. + + If not set, the new item will be appended to the end of the conversation. If set + to `root`, the new item will be added to the beginning of the conversation. If + set to an existing ID, it allows an item to be inserted mid-conversation. If the + ID cannot be found, an error will be returned and the item will not be added. + """ diff --git a/src/openai/types/beta/realtime/conversation_item_create_event_param.py b/src/openai/types/beta/realtime/conversation_item_create_event_param.py new file mode 100644 index 0000000000..693d0fd54d --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_create_event_param.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .conversation_item_param import ConversationItemParam + +__all__ = ["ConversationItemCreateEventParam"] + + +class ConversationItemCreateEventParam(TypedDict, total=False): + item: Required[ConversationItemParam] + """The item to add to the conversation.""" + + type: Required[Literal["conversation.item.create"]] + """The event type, must be `conversation.item.create`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" + + previous_item_id: str + """The ID of the preceding item after which the new item will be inserted. + + If not set, the new item will be appended to the end of the conversation. If set + to `root`, the new item will be added to the beginning of the conversation. If + set to an existing ID, it allows an item to be inserted mid-conversation. If the + ID cannot be found, an error will be returned and the item will not be added. + """ diff --git a/src/openai/types/beta/realtime/conversation_item_created_event.py b/src/openai/types/beta/realtime/conversation_item_created_event.py new file mode 100644 index 0000000000..aea7ad5b4b --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_created_event.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ConversationItemCreatedEvent"] + + +class ConversationItemCreatedEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + item: ConversationItem + """The item to add to the conversation.""" + + type: Literal["conversation.item.created"] + """The event type, must be `conversation.item.created`.""" + + previous_item_id: Optional[str] = None + """ + The ID of the preceding item in the Conversation context, allows the client to + understand the order of the conversation. Can be `null` if the item has no + predecessor. + """ diff --git a/src/openai/types/beta/realtime/conversation_item_delete_event.py b/src/openai/types/beta/realtime/conversation_item_delete_event.py new file mode 100644 index 0000000000..02ca8250ce --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_delete_event.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ConversationItemDeleteEvent"] + + +class ConversationItemDeleteEvent(BaseModel): + item_id: str + """The ID of the item to delete.""" + + type: Literal["conversation.item.delete"] + """The event type, must be `conversation.item.delete`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/conversation_item_delete_event_param.py b/src/openai/types/beta/realtime/conversation_item_delete_event_param.py new file mode 100644 index 0000000000..c3f88d6627 --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_delete_event_param.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ConversationItemDeleteEventParam"] + + +class ConversationItemDeleteEventParam(TypedDict, total=False): + item_id: Required[str] + """The ID of the item to delete.""" + + type: Required[Literal["conversation.item.delete"]] + """The event type, must be `conversation.item.delete`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/conversation_item_deleted_event.py b/src/openai/types/beta/realtime/conversation_item_deleted_event.py new file mode 100644 index 0000000000..a35a97817a --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_deleted_event.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ConversationItemDeletedEvent"] + + +class ConversationItemDeletedEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item that was deleted.""" + + type: Literal["conversation.item.deleted"] + """The event type, must be `conversation.item.deleted`.""" diff --git a/src/openai/types/beta/realtime/conversation_item_input_audio_transcription_completed_event.py b/src/openai/types/beta/realtime/conversation_item_input_audio_transcription_completed_event.py new file mode 100644 index 0000000000..e7c457d4b2 --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_input_audio_transcription_completed_event.py @@ -0,0 +1,87 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ...._models import BaseModel + +__all__ = [ + "ConversationItemInputAudioTranscriptionCompletedEvent", + "Usage", + "UsageTranscriptTextUsageTokens", + "UsageTranscriptTextUsageTokensInputTokenDetails", + "UsageTranscriptTextUsageDuration", + "Logprob", +] + + +class UsageTranscriptTextUsageTokensInputTokenDetails(BaseModel): + audio_tokens: Optional[int] = None + """Number of audio tokens billed for this request.""" + + text_tokens: Optional[int] = None + """Number of text tokens billed for this request.""" + + +class UsageTranscriptTextUsageTokens(BaseModel): + input_tokens: int + """Number of input tokens billed for this request.""" + + output_tokens: int + """Number of output tokens generated.""" + + total_tokens: int + """Total number of tokens used (input + output).""" + + type: Literal["tokens"] + """The type of the usage object. Always `tokens` for this variant.""" + + input_token_details: Optional[UsageTranscriptTextUsageTokensInputTokenDetails] = None + """Details about the input tokens billed for this request.""" + + +class UsageTranscriptTextUsageDuration(BaseModel): + seconds: float + """Duration of the input audio in seconds.""" + + type: Literal["duration"] + """The type of the usage object. Always `duration` for this variant.""" + + +Usage: TypeAlias = Union[UsageTranscriptTextUsageTokens, UsageTranscriptTextUsageDuration] + + +class Logprob(BaseModel): + token: str + """The token that was used to generate the log probability.""" + + bytes: List[int] + """The bytes that were used to generate the log probability.""" + + logprob: float + """The log probability of the token.""" + + +class ConversationItemInputAudioTranscriptionCompletedEvent(BaseModel): + content_index: int + """The index of the content part containing the audio.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the user message item containing the audio.""" + + transcript: str + """The transcribed text.""" + + type: Literal["conversation.item.input_audio_transcription.completed"] + """ + The event type, must be `conversation.item.input_audio_transcription.completed`. + """ + + usage: Usage + """Usage statistics for the transcription.""" + + logprobs: Optional[List[Logprob]] = None + """The log probabilities of the transcription.""" diff --git a/src/openai/types/beta/realtime/conversation_item_input_audio_transcription_delta_event.py b/src/openai/types/beta/realtime/conversation_item_input_audio_transcription_delta_event.py new file mode 100644 index 0000000000..924d06d98a --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_input_audio_transcription_delta_event.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ConversationItemInputAudioTranscriptionDeltaEvent", "Logprob"] + + +class Logprob(BaseModel): + token: str + """The token that was used to generate the log probability.""" + + bytes: List[int] + """The bytes that were used to generate the log probability.""" + + logprob: float + """The log probability of the token.""" + + +class ConversationItemInputAudioTranscriptionDeltaEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + type: Literal["conversation.item.input_audio_transcription.delta"] + """The event type, must be `conversation.item.input_audio_transcription.delta`.""" + + content_index: Optional[int] = None + """The index of the content part in the item's content array.""" + + delta: Optional[str] = None + """The text delta.""" + + logprobs: Optional[List[Logprob]] = None + """The log probabilities of the transcription.""" diff --git a/src/openai/types/beta/realtime/conversation_item_input_audio_transcription_failed_event.py b/src/openai/types/beta/realtime/conversation_item_input_audio_transcription_failed_event.py new file mode 100644 index 0000000000..cecac93e64 --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_input_audio_transcription_failed_event.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ConversationItemInputAudioTranscriptionFailedEvent", "Error"] + + +class Error(BaseModel): + code: Optional[str] = None + """Error code, if any.""" + + message: Optional[str] = None + """A human-readable error message.""" + + param: Optional[str] = None + """Parameter related to the error, if any.""" + + type: Optional[str] = None + """The type of error.""" + + +class ConversationItemInputAudioTranscriptionFailedEvent(BaseModel): + content_index: int + """The index of the content part containing the audio.""" + + error: Error + """Details of the transcription error.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the user message item.""" + + type: Literal["conversation.item.input_audio_transcription.failed"] + """The event type, must be `conversation.item.input_audio_transcription.failed`.""" diff --git a/src/openai/types/beta/realtime/conversation_item_param.py b/src/openai/types/beta/realtime/conversation_item_param.py new file mode 100644 index 0000000000..8bbd539c0c --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_param.py @@ -0,0 +1,62 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, TypedDict + +from .conversation_item_content_param import ConversationItemContentParam + +__all__ = ["ConversationItemParam"] + + +class ConversationItemParam(TypedDict, total=False): + id: str + """ + The unique ID of the item, this can be generated by the client to help manage + server-side context, but is not required because the server will generate one if + not provided. + """ + + arguments: str + """The arguments of the function call (for `function_call` items).""" + + call_id: str + """ + The ID of the function call (for `function_call` and `function_call_output` + items). If passed on a `function_call_output` item, the server will check that a + `function_call` item with the same ID exists in the conversation history. + """ + + content: Iterable[ConversationItemContentParam] + """The content of the message, applicable for `message` items. + + - Message items of role `system` support only `input_text` content + - Message items of role `user` support `input_text` and `input_audio` content + - Message items of role `assistant` support `text` content. + """ + + name: str + """The name of the function being called (for `function_call` items).""" + + object: Literal["realtime.item"] + """Identifier for the API object being returned - always `realtime.item`.""" + + output: str + """The output of the function call (for `function_call_output` items).""" + + role: Literal["user", "assistant", "system"] + """ + The role of the message sender (`user`, `assistant`, `system`), only applicable + for `message` items. + """ + + status: Literal["completed", "incomplete", "in_progress"] + """The status of the item (`completed`, `incomplete`, `in_progress`). + + These have no effect on the conversation, but are accepted for consistency with + the `conversation.item.created` event. + """ + + type: Literal["message", "function_call", "function_call_output"] + """The type of the item (`message`, `function_call`, `function_call_output`).""" diff --git a/src/openai/types/beta/realtime/conversation_item_retrieve_event.py b/src/openai/types/beta/realtime/conversation_item_retrieve_event.py new file mode 100644 index 0000000000..822386055c --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_retrieve_event.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ConversationItemRetrieveEvent"] + + +class ConversationItemRetrieveEvent(BaseModel): + item_id: str + """The ID of the item to retrieve.""" + + type: Literal["conversation.item.retrieve"] + """The event type, must be `conversation.item.retrieve`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/conversation_item_retrieve_event_param.py b/src/openai/types/beta/realtime/conversation_item_retrieve_event_param.py new file mode 100644 index 0000000000..71b3ffa499 --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_retrieve_event_param.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ConversationItemRetrieveEventParam"] + + +class ConversationItemRetrieveEventParam(TypedDict, total=False): + item_id: Required[str] + """The ID of the item to retrieve.""" + + type: Required[Literal["conversation.item.retrieve"]] + """The event type, must be `conversation.item.retrieve`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/conversation_item_truncate_event.py b/src/openai/types/beta/realtime/conversation_item_truncate_event.py new file mode 100644 index 0000000000..cb336bba2c --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_truncate_event.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ConversationItemTruncateEvent"] + + +class ConversationItemTruncateEvent(BaseModel): + audio_end_ms: int + """Inclusive duration up to which audio is truncated, in milliseconds. + + If the audio_end_ms is greater than the actual audio duration, the server will + respond with an error. + """ + + content_index: int + """The index of the content part to truncate. Set this to 0.""" + + item_id: str + """The ID of the assistant message item to truncate. + + Only assistant message items can be truncated. + """ + + type: Literal["conversation.item.truncate"] + """The event type, must be `conversation.item.truncate`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/conversation_item_truncate_event_param.py b/src/openai/types/beta/realtime/conversation_item_truncate_event_param.py new file mode 100644 index 0000000000..d3ad1e1e25 --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_truncate_event_param.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ConversationItemTruncateEventParam"] + + +class ConversationItemTruncateEventParam(TypedDict, total=False): + audio_end_ms: Required[int] + """Inclusive duration up to which audio is truncated, in milliseconds. + + If the audio_end_ms is greater than the actual audio duration, the server will + respond with an error. + """ + + content_index: Required[int] + """The index of the content part to truncate. Set this to 0.""" + + item_id: Required[str] + """The ID of the assistant message item to truncate. + + Only assistant message items can be truncated. + """ + + type: Required[Literal["conversation.item.truncate"]] + """The event type, must be `conversation.item.truncate`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/conversation_item_truncated_event.py b/src/openai/types/beta/realtime/conversation_item_truncated_event.py new file mode 100644 index 0000000000..36368fa28f --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_truncated_event.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ConversationItemTruncatedEvent"] + + +class ConversationItemTruncatedEvent(BaseModel): + audio_end_ms: int + """The duration up to which the audio was truncated, in milliseconds.""" + + content_index: int + """The index of the content part that was truncated.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the assistant message item that was truncated.""" + + type: Literal["conversation.item.truncated"] + """The event type, must be `conversation.item.truncated`.""" diff --git a/src/openai/types/beta/realtime/conversation_item_with_reference.py b/src/openai/types/beta/realtime/conversation_item_with_reference.py new file mode 100644 index 0000000000..0edcfc76b6 --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_with_reference.py @@ -0,0 +1,87 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ConversationItemWithReference", "Content"] + + +class Content(BaseModel): + id: Optional[str] = None + """ + ID of a previous conversation item to reference (for `item_reference` content + types in `response.create` events). These can reference both client and server + created items. + """ + + audio: Optional[str] = None + """Base64-encoded audio bytes, used for `input_audio` content type.""" + + text: Optional[str] = None + """The text content, used for `input_text` and `text` content types.""" + + transcript: Optional[str] = None + """The transcript of the audio, used for `input_audio` content type.""" + + type: Optional[Literal["input_text", "input_audio", "item_reference", "text"]] = None + """The content type (`input_text`, `input_audio`, `item_reference`, `text`).""" + + +class ConversationItemWithReference(BaseModel): + id: Optional[str] = None + """ + For an item of type (`message` | `function_call` | `function_call_output`) this + field allows the client to assign the unique ID of the item. It is not required + because the server will generate one if not provided. + + For an item of type `item_reference`, this field is required and is a reference + to any item that has previously existed in the conversation. + """ + + arguments: Optional[str] = None + """The arguments of the function call (for `function_call` items).""" + + call_id: Optional[str] = None + """ + The ID of the function call (for `function_call` and `function_call_output` + items). If passed on a `function_call_output` item, the server will check that a + `function_call` item with the same ID exists in the conversation history. + """ + + content: Optional[List[Content]] = None + """The content of the message, applicable for `message` items. + + - Message items of role `system` support only `input_text` content + - Message items of role `user` support `input_text` and `input_audio` content + - Message items of role `assistant` support `text` content. + """ + + name: Optional[str] = None + """The name of the function being called (for `function_call` items).""" + + object: Optional[Literal["realtime.item"]] = None + """Identifier for the API object being returned - always `realtime.item`.""" + + output: Optional[str] = None + """The output of the function call (for `function_call_output` items).""" + + role: Optional[Literal["user", "assistant", "system"]] = None + """ + The role of the message sender (`user`, `assistant`, `system`), only applicable + for `message` items. + """ + + status: Optional[Literal["completed", "incomplete", "in_progress"]] = None + """The status of the item (`completed`, `incomplete`, `in_progress`). + + These have no effect on the conversation, but are accepted for consistency with + the `conversation.item.created` event. + """ + + type: Optional[Literal["message", "function_call", "function_call_output", "item_reference"]] = None + """ + The type of the item (`message`, `function_call`, `function_call_output`, + `item_reference`). + """ diff --git a/src/openai/types/beta/realtime/conversation_item_with_reference_param.py b/src/openai/types/beta/realtime/conversation_item_with_reference_param.py new file mode 100644 index 0000000000..c83dc92ab7 --- /dev/null +++ b/src/openai/types/beta/realtime/conversation_item_with_reference_param.py @@ -0,0 +1,87 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, TypedDict + +__all__ = ["ConversationItemWithReferenceParam", "Content"] + + +class Content(TypedDict, total=False): + id: str + """ + ID of a previous conversation item to reference (for `item_reference` content + types in `response.create` events). These can reference both client and server + created items. + """ + + audio: str + """Base64-encoded audio bytes, used for `input_audio` content type.""" + + text: str + """The text content, used for `input_text` and `text` content types.""" + + transcript: str + """The transcript of the audio, used for `input_audio` content type.""" + + type: Literal["input_text", "input_audio", "item_reference", "text"] + """The content type (`input_text`, `input_audio`, `item_reference`, `text`).""" + + +class ConversationItemWithReferenceParam(TypedDict, total=False): + id: str + """ + For an item of type (`message` | `function_call` | `function_call_output`) this + field allows the client to assign the unique ID of the item. It is not required + because the server will generate one if not provided. + + For an item of type `item_reference`, this field is required and is a reference + to any item that has previously existed in the conversation. + """ + + arguments: str + """The arguments of the function call (for `function_call` items).""" + + call_id: str + """ + The ID of the function call (for `function_call` and `function_call_output` + items). If passed on a `function_call_output` item, the server will check that a + `function_call` item with the same ID exists in the conversation history. + """ + + content: Iterable[Content] + """The content of the message, applicable for `message` items. + + - Message items of role `system` support only `input_text` content + - Message items of role `user` support `input_text` and `input_audio` content + - Message items of role `assistant` support `text` content. + """ + + name: str + """The name of the function being called (for `function_call` items).""" + + object: Literal["realtime.item"] + """Identifier for the API object being returned - always `realtime.item`.""" + + output: str + """The output of the function call (for `function_call_output` items).""" + + role: Literal["user", "assistant", "system"] + """ + The role of the message sender (`user`, `assistant`, `system`), only applicable + for `message` items. + """ + + status: Literal["completed", "incomplete", "in_progress"] + """The status of the item (`completed`, `incomplete`, `in_progress`). + + These have no effect on the conversation, but are accepted for consistency with + the `conversation.item.created` event. + """ + + type: Literal["message", "function_call", "function_call_output", "item_reference"] + """ + The type of the item (`message`, `function_call`, `function_call_output`, + `item_reference`). + """ diff --git a/src/openai/types/beta/realtime/error_event.py b/src/openai/types/beta/realtime/error_event.py new file mode 100644 index 0000000000..e020fc3848 --- /dev/null +++ b/src/openai/types/beta/realtime/error_event.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ErrorEvent", "Error"] + + +class Error(BaseModel): + message: str + """A human-readable error message.""" + + type: str + """The type of error (e.g., "invalid_request_error", "server_error").""" + + code: Optional[str] = None + """Error code, if any.""" + + event_id: Optional[str] = None + """The event_id of the client event that caused the error, if applicable.""" + + param: Optional[str] = None + """Parameter related to the error, if any.""" + + +class ErrorEvent(BaseModel): + error: Error + """Details of the error.""" + + event_id: str + """The unique ID of the server event.""" + + type: Literal["error"] + """The event type, must be `error`.""" diff --git a/src/openai/types/beta/realtime/input_audio_buffer_append_event.py b/src/openai/types/beta/realtime/input_audio_buffer_append_event.py new file mode 100644 index 0000000000..a253a6488c --- /dev/null +++ b/src/openai/types/beta/realtime/input_audio_buffer_append_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["InputAudioBufferAppendEvent"] + + +class InputAudioBufferAppendEvent(BaseModel): + audio: str + """Base64-encoded audio bytes. + + This must be in the format specified by the `input_audio_format` field in the + session configuration. + """ + + type: Literal["input_audio_buffer.append"] + """The event type, must be `input_audio_buffer.append`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/input_audio_buffer_append_event_param.py b/src/openai/types/beta/realtime/input_audio_buffer_append_event_param.py new file mode 100644 index 0000000000..3ad0bc737d --- /dev/null +++ b/src/openai/types/beta/realtime/input_audio_buffer_append_event_param.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["InputAudioBufferAppendEventParam"] + + +class InputAudioBufferAppendEventParam(TypedDict, total=False): + audio: Required[str] + """Base64-encoded audio bytes. + + This must be in the format specified by the `input_audio_format` field in the + session configuration. + """ + + type: Required[Literal["input_audio_buffer.append"]] + """The event type, must be `input_audio_buffer.append`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/input_audio_buffer_clear_event.py b/src/openai/types/beta/realtime/input_audio_buffer_clear_event.py new file mode 100644 index 0000000000..b0624d34df --- /dev/null +++ b/src/openai/types/beta/realtime/input_audio_buffer_clear_event.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["InputAudioBufferClearEvent"] + + +class InputAudioBufferClearEvent(BaseModel): + type: Literal["input_audio_buffer.clear"] + """The event type, must be `input_audio_buffer.clear`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/input_audio_buffer_clear_event_param.py b/src/openai/types/beta/realtime/input_audio_buffer_clear_event_param.py new file mode 100644 index 0000000000..2bd6bc5a02 --- /dev/null +++ b/src/openai/types/beta/realtime/input_audio_buffer_clear_event_param.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["InputAudioBufferClearEventParam"] + + +class InputAudioBufferClearEventParam(TypedDict, total=False): + type: Required[Literal["input_audio_buffer.clear"]] + """The event type, must be `input_audio_buffer.clear`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/input_audio_buffer_cleared_event.py b/src/openai/types/beta/realtime/input_audio_buffer_cleared_event.py new file mode 100644 index 0000000000..632e1b94bc --- /dev/null +++ b/src/openai/types/beta/realtime/input_audio_buffer_cleared_event.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["InputAudioBufferClearedEvent"] + + +class InputAudioBufferClearedEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + type: Literal["input_audio_buffer.cleared"] + """The event type, must be `input_audio_buffer.cleared`.""" diff --git a/src/openai/types/beta/realtime/input_audio_buffer_commit_event.py b/src/openai/types/beta/realtime/input_audio_buffer_commit_event.py new file mode 100644 index 0000000000..7b6f5e46b7 --- /dev/null +++ b/src/openai/types/beta/realtime/input_audio_buffer_commit_event.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["InputAudioBufferCommitEvent"] + + +class InputAudioBufferCommitEvent(BaseModel): + type: Literal["input_audio_buffer.commit"] + """The event type, must be `input_audio_buffer.commit`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/input_audio_buffer_commit_event_param.py b/src/openai/types/beta/realtime/input_audio_buffer_commit_event_param.py new file mode 100644 index 0000000000..c9c927ab98 --- /dev/null +++ b/src/openai/types/beta/realtime/input_audio_buffer_commit_event_param.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["InputAudioBufferCommitEventParam"] + + +class InputAudioBufferCommitEventParam(TypedDict, total=False): + type: Required[Literal["input_audio_buffer.commit"]] + """The event type, must be `input_audio_buffer.commit`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/input_audio_buffer_committed_event.py b/src/openai/types/beta/realtime/input_audio_buffer_committed_event.py new file mode 100644 index 0000000000..22eb53b117 --- /dev/null +++ b/src/openai/types/beta/realtime/input_audio_buffer_committed_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["InputAudioBufferCommittedEvent"] + + +class InputAudioBufferCommittedEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the user message item that will be created.""" + + type: Literal["input_audio_buffer.committed"] + """The event type, must be `input_audio_buffer.committed`.""" + + previous_item_id: Optional[str] = None + """ + The ID of the preceding item after which the new item will be inserted. Can be + `null` if the item has no predecessor. + """ diff --git a/src/openai/types/beta/realtime/input_audio_buffer_speech_started_event.py b/src/openai/types/beta/realtime/input_audio_buffer_speech_started_event.py new file mode 100644 index 0000000000..4f3ab082c4 --- /dev/null +++ b/src/openai/types/beta/realtime/input_audio_buffer_speech_started_event.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["InputAudioBufferSpeechStartedEvent"] + + +class InputAudioBufferSpeechStartedEvent(BaseModel): + audio_start_ms: int + """ + Milliseconds from the start of all audio written to the buffer during the + session when speech was first detected. This will correspond to the beginning of + audio sent to the model, and thus includes the `prefix_padding_ms` configured in + the Session. + """ + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the user message item that will be created when speech stops.""" + + type: Literal["input_audio_buffer.speech_started"] + """The event type, must be `input_audio_buffer.speech_started`.""" diff --git a/src/openai/types/beta/realtime/input_audio_buffer_speech_stopped_event.py b/src/openai/types/beta/realtime/input_audio_buffer_speech_stopped_event.py new file mode 100644 index 0000000000..40568170f2 --- /dev/null +++ b/src/openai/types/beta/realtime/input_audio_buffer_speech_stopped_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["InputAudioBufferSpeechStoppedEvent"] + + +class InputAudioBufferSpeechStoppedEvent(BaseModel): + audio_end_ms: int + """Milliseconds since the session started when speech stopped. + + This will correspond to the end of audio sent to the model, and thus includes + the `min_silence_duration_ms` configured in the Session. + """ + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the user message item that will be created.""" + + type: Literal["input_audio_buffer.speech_stopped"] + """The event type, must be `input_audio_buffer.speech_stopped`.""" diff --git a/src/openai/types/beta/realtime/rate_limits_updated_event.py b/src/openai/types/beta/realtime/rate_limits_updated_event.py new file mode 100644 index 0000000000..7e12283c46 --- /dev/null +++ b/src/openai/types/beta/realtime/rate_limits_updated_event.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["RateLimitsUpdatedEvent", "RateLimit"] + + +class RateLimit(BaseModel): + limit: Optional[int] = None + """The maximum allowed value for the rate limit.""" + + name: Optional[Literal["requests", "tokens"]] = None + """The name of the rate limit (`requests`, `tokens`).""" + + remaining: Optional[int] = None + """The remaining value before the limit is reached.""" + + reset_seconds: Optional[float] = None + """Seconds until the rate limit resets.""" + + +class RateLimitsUpdatedEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + rate_limits: List[RateLimit] + """List of rate limit information.""" + + type: Literal["rate_limits.updated"] + """The event type, must be `rate_limits.updated`.""" diff --git a/src/openai/types/beta/realtime/realtime_client_event.py b/src/openai/types/beta/realtime/realtime_client_event.py new file mode 100644 index 0000000000..5f4858d688 --- /dev/null +++ b/src/openai/types/beta/realtime/realtime_client_event.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel +from .session_update_event import SessionUpdateEvent +from .response_cancel_event import ResponseCancelEvent +from .response_create_event import ResponseCreateEvent +from .transcription_session_update import TranscriptionSessionUpdate +from .conversation_item_create_event import ConversationItemCreateEvent +from .conversation_item_delete_event import ConversationItemDeleteEvent +from .input_audio_buffer_clear_event import InputAudioBufferClearEvent +from .input_audio_buffer_append_event import InputAudioBufferAppendEvent +from .input_audio_buffer_commit_event import InputAudioBufferCommitEvent +from .conversation_item_retrieve_event import ConversationItemRetrieveEvent +from .conversation_item_truncate_event import ConversationItemTruncateEvent + +__all__ = ["RealtimeClientEvent", "OutputAudioBufferClear"] + + +class OutputAudioBufferClear(BaseModel): + type: Literal["output_audio_buffer.clear"] + """The event type, must be `output_audio_buffer.clear`.""" + + event_id: Optional[str] = None + """The unique ID of the client event used for error handling.""" + + +RealtimeClientEvent: TypeAlias = Annotated[ + Union[ + ConversationItemCreateEvent, + ConversationItemDeleteEvent, + ConversationItemRetrieveEvent, + ConversationItemTruncateEvent, + InputAudioBufferAppendEvent, + InputAudioBufferClearEvent, + OutputAudioBufferClear, + InputAudioBufferCommitEvent, + ResponseCancelEvent, + ResponseCreateEvent, + SessionUpdateEvent, + TranscriptionSessionUpdate, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/beta/realtime/realtime_client_event_param.py b/src/openai/types/beta/realtime/realtime_client_event_param.py new file mode 100644 index 0000000000..e7dfba241e --- /dev/null +++ b/src/openai/types/beta/realtime/realtime_client_event_param.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .session_update_event_param import SessionUpdateEventParam +from .response_cancel_event_param import ResponseCancelEventParam +from .response_create_event_param import ResponseCreateEventParam +from .transcription_session_update_param import TranscriptionSessionUpdateParam +from .conversation_item_create_event_param import ConversationItemCreateEventParam +from .conversation_item_delete_event_param import ConversationItemDeleteEventParam +from .input_audio_buffer_clear_event_param import InputAudioBufferClearEventParam +from .input_audio_buffer_append_event_param import InputAudioBufferAppendEventParam +from .input_audio_buffer_commit_event_param import InputAudioBufferCommitEventParam +from .conversation_item_retrieve_event_param import ConversationItemRetrieveEventParam +from .conversation_item_truncate_event_param import ConversationItemTruncateEventParam + +__all__ = ["RealtimeClientEventParam", "OutputAudioBufferClear"] + + +class OutputAudioBufferClear(TypedDict, total=False): + type: Required[Literal["output_audio_buffer.clear"]] + """The event type, must be `output_audio_buffer.clear`.""" + + event_id: str + """The unique ID of the client event used for error handling.""" + + +RealtimeClientEventParam: TypeAlias = Union[ + ConversationItemCreateEventParam, + ConversationItemDeleteEventParam, + ConversationItemRetrieveEventParam, + ConversationItemTruncateEventParam, + InputAudioBufferAppendEventParam, + InputAudioBufferClearEventParam, + OutputAudioBufferClear, + InputAudioBufferCommitEventParam, + ResponseCancelEventParam, + ResponseCreateEventParam, + SessionUpdateEventParam, + TranscriptionSessionUpdateParam, +] diff --git a/src/openai/types/beta/realtime/realtime_connect_params.py b/src/openai/types/beta/realtime/realtime_connect_params.py new file mode 100644 index 0000000000..76474f3de4 --- /dev/null +++ b/src/openai/types/beta/realtime/realtime_connect_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["RealtimeConnectParams"] + + +class RealtimeConnectParams(TypedDict, total=False): + model: Required[str] diff --git a/src/openai/types/beta/realtime/realtime_response.py b/src/openai/types/beta/realtime/realtime_response.py new file mode 100644 index 0000000000..ccc97c5d22 --- /dev/null +++ b/src/openai/types/beta/realtime/realtime_response.py @@ -0,0 +1,87 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal + +from ...._models import BaseModel +from ...shared.metadata import Metadata +from .conversation_item import ConversationItem +from .realtime_response_usage import RealtimeResponseUsage +from .realtime_response_status import RealtimeResponseStatus + +__all__ = ["RealtimeResponse"] + + +class RealtimeResponse(BaseModel): + id: Optional[str] = None + """The unique ID of the response.""" + + conversation_id: Optional[str] = None + """ + Which conversation the response is added to, determined by the `conversation` + field in the `response.create` event. If `auto`, the response will be added to + the default conversation and the value of `conversation_id` will be an id like + `conv_1234`. If `none`, the response will not be added to any conversation and + the value of `conversation_id` will be `null`. If responses are being triggered + by server VAD, the response will be added to the default conversation, thus the + `conversation_id` will be an id like `conv_1234`. + """ + + max_output_tokens: Union[int, Literal["inf"], None] = None + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls, that was used in this response. + """ + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + modalities: Optional[List[Literal["text", "audio"]]] = None + """The set of modalities the model used to respond. + + If there are multiple modalities, the model will pick one, for example if + `modalities` is `["text", "audio"]`, the model could be responding in either + text or audio. + """ + + object: Optional[Literal["realtime.response"]] = None + """The object type, must be `realtime.response`.""" + + output: Optional[List[ConversationItem]] = None + """The list of output items generated by the response.""" + + output_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None + """The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.""" + + status: Optional[Literal["completed", "cancelled", "failed", "incomplete", "in_progress"]] = None + """ + The final status of the response (`completed`, `cancelled`, `failed`, or + `incomplete`, `in_progress`). + """ + + status_details: Optional[RealtimeResponseStatus] = None + """Additional details about the status.""" + + temperature: Optional[float] = None + """Sampling temperature for the model, limited to [0.6, 1.2]. Defaults to 0.8.""" + + usage: Optional[RealtimeResponseUsage] = None + """Usage statistics for the Response, this will correspond to billing. + + A Realtime API session will maintain a conversation context and append new Items + to the Conversation, thus output from previous turns (text and audio tokens) + will become the input for later turns. + """ + + voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"], None] = None + """ + The voice the model used to respond. Current voice options are `alloy`, `ash`, + `ballad`, `coral`, `echo`, `sage`, `shimmer`, and `verse`. + """ diff --git a/src/openai/types/beta/realtime/realtime_response_status.py b/src/openai/types/beta/realtime/realtime_response_status.py new file mode 100644 index 0000000000..7189cd58a1 --- /dev/null +++ b/src/openai/types/beta/realtime/realtime_response_status.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["RealtimeResponseStatus", "Error"] + + +class Error(BaseModel): + code: Optional[str] = None + """Error code, if any.""" + + type: Optional[str] = None + """The type of error.""" + + +class RealtimeResponseStatus(BaseModel): + error: Optional[Error] = None + """ + A description of the error that caused the response to fail, populated when the + `status` is `failed`. + """ + + reason: Optional[Literal["turn_detected", "client_cancelled", "max_output_tokens", "content_filter"]] = None + """The reason the Response did not complete. + + For a `cancelled` Response, one of `turn_detected` (the server VAD detected a + new start of speech) or `client_cancelled` (the client sent a cancel event). For + an `incomplete` Response, one of `max_output_tokens` or `content_filter` (the + server-side safety filter activated and cut off the response). + """ + + type: Optional[Literal["completed", "cancelled", "incomplete", "failed"]] = None + """ + The type of error that caused the response to fail, corresponding with the + `status` field (`completed`, `cancelled`, `incomplete`, `failed`). + """ diff --git a/src/openai/types/beta/realtime/realtime_response_usage.py b/src/openai/types/beta/realtime/realtime_response_usage.py new file mode 100644 index 0000000000..7ca822e25e --- /dev/null +++ b/src/openai/types/beta/realtime/realtime_response_usage.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ...._models import BaseModel + +__all__ = ["RealtimeResponseUsage", "InputTokenDetails", "OutputTokenDetails"] + + +class InputTokenDetails(BaseModel): + audio_tokens: Optional[int] = None + """The number of audio tokens used in the Response.""" + + cached_tokens: Optional[int] = None + """The number of cached tokens used in the Response.""" + + text_tokens: Optional[int] = None + """The number of text tokens used in the Response.""" + + +class OutputTokenDetails(BaseModel): + audio_tokens: Optional[int] = None + """The number of audio tokens used in the Response.""" + + text_tokens: Optional[int] = None + """The number of text tokens used in the Response.""" + + +class RealtimeResponseUsage(BaseModel): + input_token_details: Optional[InputTokenDetails] = None + """Details about the input tokens used in the Response.""" + + input_tokens: Optional[int] = None + """ + The number of input tokens used in the Response, including text and audio + tokens. + """ + + output_token_details: Optional[OutputTokenDetails] = None + """Details about the output tokens used in the Response.""" + + output_tokens: Optional[int] = None + """ + The number of output tokens sent in the Response, including text and audio + tokens. + """ + + total_tokens: Optional[int] = None + """ + The total number of tokens in the Response including input and output text and + audio tokens. + """ diff --git a/src/openai/types/beta/realtime/realtime_server_event.py b/src/openai/types/beta/realtime/realtime_server_event.py new file mode 100644 index 0000000000..c12f5df977 --- /dev/null +++ b/src/openai/types/beta/realtime/realtime_server_event.py @@ -0,0 +1,133 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, Annotated, TypeAlias + +from ...._utils import PropertyInfo +from ...._models import BaseModel +from .error_event import ErrorEvent +from .conversation_item import ConversationItem +from .response_done_event import ResponseDoneEvent +from .session_created_event import SessionCreatedEvent +from .session_updated_event import SessionUpdatedEvent +from .response_created_event import ResponseCreatedEvent +from .response_text_done_event import ResponseTextDoneEvent +from .rate_limits_updated_event import RateLimitsUpdatedEvent +from .response_audio_done_event import ResponseAudioDoneEvent +from .response_text_delta_event import ResponseTextDeltaEvent +from .conversation_created_event import ConversationCreatedEvent +from .response_audio_delta_event import ResponseAudioDeltaEvent +from .conversation_item_created_event import ConversationItemCreatedEvent +from .conversation_item_deleted_event import ConversationItemDeletedEvent +from .response_output_item_done_event import ResponseOutputItemDoneEvent +from .input_audio_buffer_cleared_event import InputAudioBufferClearedEvent +from .response_content_part_done_event import ResponseContentPartDoneEvent +from .response_output_item_added_event import ResponseOutputItemAddedEvent +from .conversation_item_truncated_event import ConversationItemTruncatedEvent +from .response_content_part_added_event import ResponseContentPartAddedEvent +from .input_audio_buffer_committed_event import InputAudioBufferCommittedEvent +from .transcription_session_updated_event import TranscriptionSessionUpdatedEvent +from .response_audio_transcript_done_event import ResponseAudioTranscriptDoneEvent +from .response_audio_transcript_delta_event import ResponseAudioTranscriptDeltaEvent +from .input_audio_buffer_speech_started_event import InputAudioBufferSpeechStartedEvent +from .input_audio_buffer_speech_stopped_event import InputAudioBufferSpeechStoppedEvent +from .response_function_call_arguments_done_event import ResponseFunctionCallArgumentsDoneEvent +from .response_function_call_arguments_delta_event import ResponseFunctionCallArgumentsDeltaEvent +from .conversation_item_input_audio_transcription_delta_event import ConversationItemInputAudioTranscriptionDeltaEvent +from .conversation_item_input_audio_transcription_failed_event import ConversationItemInputAudioTranscriptionFailedEvent +from .conversation_item_input_audio_transcription_completed_event import ( + ConversationItemInputAudioTranscriptionCompletedEvent, +) + +__all__ = [ + "RealtimeServerEvent", + "ConversationItemRetrieved", + "OutputAudioBufferStarted", + "OutputAudioBufferStopped", + "OutputAudioBufferCleared", +] + + +class ConversationItemRetrieved(BaseModel): + event_id: str + """The unique ID of the server event.""" + + item: ConversationItem + """The item to add to the conversation.""" + + type: Literal["conversation.item.retrieved"] + """The event type, must be `conversation.item.retrieved`.""" + + +class OutputAudioBufferStarted(BaseModel): + event_id: str + """The unique ID of the server event.""" + + response_id: str + """The unique ID of the response that produced the audio.""" + + type: Literal["output_audio_buffer.started"] + """The event type, must be `output_audio_buffer.started`.""" + + +class OutputAudioBufferStopped(BaseModel): + event_id: str + """The unique ID of the server event.""" + + response_id: str + """The unique ID of the response that produced the audio.""" + + type: Literal["output_audio_buffer.stopped"] + """The event type, must be `output_audio_buffer.stopped`.""" + + +class OutputAudioBufferCleared(BaseModel): + event_id: str + """The unique ID of the server event.""" + + response_id: str + """The unique ID of the response that produced the audio.""" + + type: Literal["output_audio_buffer.cleared"] + """The event type, must be `output_audio_buffer.cleared`.""" + + +RealtimeServerEvent: TypeAlias = Annotated[ + Union[ + ConversationCreatedEvent, + ConversationItemCreatedEvent, + ConversationItemDeletedEvent, + ConversationItemInputAudioTranscriptionCompletedEvent, + ConversationItemInputAudioTranscriptionDeltaEvent, + ConversationItemInputAudioTranscriptionFailedEvent, + ConversationItemRetrieved, + ConversationItemTruncatedEvent, + ErrorEvent, + InputAudioBufferClearedEvent, + InputAudioBufferCommittedEvent, + InputAudioBufferSpeechStartedEvent, + InputAudioBufferSpeechStoppedEvent, + RateLimitsUpdatedEvent, + ResponseAudioDeltaEvent, + ResponseAudioDoneEvent, + ResponseAudioTranscriptDeltaEvent, + ResponseAudioTranscriptDoneEvent, + ResponseContentPartAddedEvent, + ResponseContentPartDoneEvent, + ResponseCreatedEvent, + ResponseDoneEvent, + ResponseFunctionCallArgumentsDeltaEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseOutputItemAddedEvent, + ResponseOutputItemDoneEvent, + ResponseTextDeltaEvent, + ResponseTextDoneEvent, + SessionCreatedEvent, + SessionUpdatedEvent, + TranscriptionSessionUpdatedEvent, + OutputAudioBufferStarted, + OutputAudioBufferStopped, + OutputAudioBufferCleared, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/beta/realtime/response_audio_delta_event.py b/src/openai/types/beta/realtime/response_audio_delta_event.py new file mode 100644 index 0000000000..8e0128d942 --- /dev/null +++ b/src/openai/types/beta/realtime/response_audio_delta_event.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseAudioDeltaEvent"] + + +class ResponseAudioDeltaEvent(BaseModel): + content_index: int + """The index of the content part in the item's content array.""" + + delta: str + """Base64-encoded audio data delta.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.audio.delta"] + """The event type, must be `response.audio.delta`.""" diff --git a/src/openai/types/beta/realtime/response_audio_done_event.py b/src/openai/types/beta/realtime/response_audio_done_event.py new file mode 100644 index 0000000000..68e78bc778 --- /dev/null +++ b/src/openai/types/beta/realtime/response_audio_done_event.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseAudioDoneEvent"] + + +class ResponseAudioDoneEvent(BaseModel): + content_index: int + """The index of the content part in the item's content array.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.audio.done"] + """The event type, must be `response.audio.done`.""" diff --git a/src/openai/types/beta/realtime/response_audio_transcript_delta_event.py b/src/openai/types/beta/realtime/response_audio_transcript_delta_event.py new file mode 100644 index 0000000000..3609948d10 --- /dev/null +++ b/src/openai/types/beta/realtime/response_audio_transcript_delta_event.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseAudioTranscriptDeltaEvent"] + + +class ResponseAudioTranscriptDeltaEvent(BaseModel): + content_index: int + """The index of the content part in the item's content array.""" + + delta: str + """The transcript delta.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.audio_transcript.delta"] + """The event type, must be `response.audio_transcript.delta`.""" diff --git a/src/openai/types/beta/realtime/response_audio_transcript_done_event.py b/src/openai/types/beta/realtime/response_audio_transcript_done_event.py new file mode 100644 index 0000000000..4e4436a95f --- /dev/null +++ b/src/openai/types/beta/realtime/response_audio_transcript_done_event.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseAudioTranscriptDoneEvent"] + + +class ResponseAudioTranscriptDoneEvent(BaseModel): + content_index: int + """The index of the content part in the item's content array.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + transcript: str + """The final transcript of the audio.""" + + type: Literal["response.audio_transcript.done"] + """The event type, must be `response.audio_transcript.done`.""" diff --git a/src/openai/types/beta/realtime/response_cancel_event.py b/src/openai/types/beta/realtime/response_cancel_event.py new file mode 100644 index 0000000000..c5ff991e9a --- /dev/null +++ b/src/openai/types/beta/realtime/response_cancel_event.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseCancelEvent"] + + +class ResponseCancelEvent(BaseModel): + type: Literal["response.cancel"] + """The event type, must be `response.cancel`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" + + response_id: Optional[str] = None + """ + A specific response ID to cancel - if not provided, will cancel an in-progress + response in the default conversation. + """ diff --git a/src/openai/types/beta/realtime/response_cancel_event_param.py b/src/openai/types/beta/realtime/response_cancel_event_param.py new file mode 100644 index 0000000000..f33740730a --- /dev/null +++ b/src/openai/types/beta/realtime/response_cancel_event_param.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseCancelEventParam"] + + +class ResponseCancelEventParam(TypedDict, total=False): + type: Required[Literal["response.cancel"]] + """The event type, must be `response.cancel`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" + + response_id: str + """ + A specific response ID to cancel - if not provided, will cancel an in-progress + response in the default conversation. + """ diff --git a/src/openai/types/beta/realtime/response_content_part_added_event.py b/src/openai/types/beta/realtime/response_content_part_added_event.py new file mode 100644 index 0000000000..45c8f20f97 --- /dev/null +++ b/src/openai/types/beta/realtime/response_content_part_added_event.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseContentPartAddedEvent", "Part"] + + +class Part(BaseModel): + audio: Optional[str] = None + """Base64-encoded audio data (if type is "audio").""" + + text: Optional[str] = None + """The text content (if type is "text").""" + + transcript: Optional[str] = None + """The transcript of the audio (if type is "audio").""" + + type: Optional[Literal["text", "audio"]] = None + """The content type ("text", "audio").""" + + +class ResponseContentPartAddedEvent(BaseModel): + content_index: int + """The index of the content part in the item's content array.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item to which the content part was added.""" + + output_index: int + """The index of the output item in the response.""" + + part: Part + """The content part that was added.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.content_part.added"] + """The event type, must be `response.content_part.added`.""" diff --git a/src/openai/types/beta/realtime/response_content_part_done_event.py b/src/openai/types/beta/realtime/response_content_part_done_event.py new file mode 100644 index 0000000000..3d16116106 --- /dev/null +++ b/src/openai/types/beta/realtime/response_content_part_done_event.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseContentPartDoneEvent", "Part"] + + +class Part(BaseModel): + audio: Optional[str] = None + """Base64-encoded audio data (if type is "audio").""" + + text: Optional[str] = None + """The text content (if type is "text").""" + + transcript: Optional[str] = None + """The transcript of the audio (if type is "audio").""" + + type: Optional[Literal["text", "audio"]] = None + """The content type ("text", "audio").""" + + +class ResponseContentPartDoneEvent(BaseModel): + content_index: int + """The index of the content part in the item's content array.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + part: Part + """The content part that is done.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.content_part.done"] + """The event type, must be `response.content_part.done`.""" diff --git a/src/openai/types/beta/realtime/response_create_event.py b/src/openai/types/beta/realtime/response_create_event.py new file mode 100644 index 0000000000..7219cedbf3 --- /dev/null +++ b/src/openai/types/beta/realtime/response_create_event.py @@ -0,0 +1,121 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal + +from ...._models import BaseModel +from ...shared.metadata import Metadata +from .conversation_item_with_reference import ConversationItemWithReference + +__all__ = ["ResponseCreateEvent", "Response", "ResponseTool"] + + +class ResponseTool(BaseModel): + description: Optional[str] = None + """ + The description of the function, including guidance on when and how to call it, + and guidance about what to tell the user when calling (if anything). + """ + + name: Optional[str] = None + """The name of the function.""" + + parameters: Optional[object] = None + """Parameters of the function in JSON Schema.""" + + type: Optional[Literal["function"]] = None + """The type of the tool, i.e. `function`.""" + + +class Response(BaseModel): + conversation: Union[str, Literal["auto", "none"], None] = None + """Controls which conversation the response is added to. + + Currently supports `auto` and `none`, with `auto` as the default value. The + `auto` value means that the contents of the response will be added to the + default conversation. Set this to `none` to create an out-of-band response which + will not add items to default conversation. + """ + + input: Optional[List[ConversationItemWithReference]] = None + """Input items to include in the prompt for the model. + + Using this field creates a new context for this Response instead of using the + default conversation. An empty array `[]` will clear the context for this + Response. Note that this can include references to items from the default + conversation. + """ + + instructions: Optional[str] = None + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_response_output_tokens: Union[int, Literal["inf"], None] = None + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + modalities: Optional[List[Literal["text", "audio"]]] = None + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + output_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None + """The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.""" + + temperature: Optional[float] = None + """Sampling temperature for the model, limited to [0.6, 1.2]. Defaults to 0.8.""" + + tool_choice: Optional[str] = None + """How the model chooses tools. + + Options are `auto`, `none`, `required`, or specify a function, like + `{"type": "function", "function": {"name": "my_function"}}`. + """ + + tools: Optional[List[ResponseTool]] = None + """Tools (functions) available to the model.""" + + voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"], None] = None + """The voice the model uses to respond. + + Voice cannot be changed during the session once the model has responded with + audio at least once. Current voice options are `alloy`, `ash`, `ballad`, + `coral`, `echo`, `sage`, `shimmer`, and `verse`. + """ + + +class ResponseCreateEvent(BaseModel): + type: Literal["response.create"] + """The event type, must be `response.create`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" + + response: Optional[Response] = None + """Create a new Realtime response with these parameters""" diff --git a/src/openai/types/beta/realtime/response_create_event_param.py b/src/openai/types/beta/realtime/response_create_event_param.py new file mode 100644 index 0000000000..b4d54bba92 --- /dev/null +++ b/src/openai/types/beta/realtime/response_create_event_param.py @@ -0,0 +1,122 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from ...shared_params.metadata import Metadata +from .conversation_item_with_reference_param import ConversationItemWithReferenceParam + +__all__ = ["ResponseCreateEventParam", "Response", "ResponseTool"] + + +class ResponseTool(TypedDict, total=False): + description: str + """ + The description of the function, including guidance on when and how to call it, + and guidance about what to tell the user when calling (if anything). + """ + + name: str + """The name of the function.""" + + parameters: object + """Parameters of the function in JSON Schema.""" + + type: Literal["function"] + """The type of the tool, i.e. `function`.""" + + +class Response(TypedDict, total=False): + conversation: Union[str, Literal["auto", "none"]] + """Controls which conversation the response is added to. + + Currently supports `auto` and `none`, with `auto` as the default value. The + `auto` value means that the contents of the response will be added to the + default conversation. Set this to `none` to create an out-of-band response which + will not add items to default conversation. + """ + + input: Iterable[ConversationItemWithReferenceParam] + """Input items to include in the prompt for the model. + + Using this field creates a new context for this Response instead of using the + default conversation. An empty array `[]` will clear the context for this + Response. Note that this can include references to items from the default + conversation. + """ + + instructions: str + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_response_output_tokens: Union[int, Literal["inf"]] + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + modalities: List[Literal["text", "audio"]] + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + output_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] + """The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.""" + + temperature: float + """Sampling temperature for the model, limited to [0.6, 1.2]. Defaults to 0.8.""" + + tool_choice: str + """How the model chooses tools. + + Options are `auto`, `none`, `required`, or specify a function, like + `{"type": "function", "function": {"name": "my_function"}}`. + """ + + tools: Iterable[ResponseTool] + """Tools (functions) available to the model.""" + + voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"]] + """The voice the model uses to respond. + + Voice cannot be changed during the session once the model has responded with + audio at least once. Current voice options are `alloy`, `ash`, `ballad`, + `coral`, `echo`, `sage`, `shimmer`, and `verse`. + """ + + +class ResponseCreateEventParam(TypedDict, total=False): + type: Required[Literal["response.create"]] + """The event type, must be `response.create`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" + + response: Response + """Create a new Realtime response with these parameters""" diff --git a/src/openai/types/beta/realtime/response_created_event.py b/src/openai/types/beta/realtime/response_created_event.py new file mode 100644 index 0000000000..a4990cf095 --- /dev/null +++ b/src/openai/types/beta/realtime/response_created_event.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel +from .realtime_response import RealtimeResponse + +__all__ = ["ResponseCreatedEvent"] + + +class ResponseCreatedEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + response: RealtimeResponse + """The response resource.""" + + type: Literal["response.created"] + """The event type, must be `response.created`.""" diff --git a/src/openai/types/beta/realtime/response_done_event.py b/src/openai/types/beta/realtime/response_done_event.py new file mode 100644 index 0000000000..9e655184b6 --- /dev/null +++ b/src/openai/types/beta/realtime/response_done_event.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel +from .realtime_response import RealtimeResponse + +__all__ = ["ResponseDoneEvent"] + + +class ResponseDoneEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + response: RealtimeResponse + """The response resource.""" + + type: Literal["response.done"] + """The event type, must be `response.done`.""" diff --git a/src/openai/types/beta/realtime/response_function_call_arguments_delta_event.py b/src/openai/types/beta/realtime/response_function_call_arguments_delta_event.py new file mode 100644 index 0000000000..cdbb64e658 --- /dev/null +++ b/src/openai/types/beta/realtime/response_function_call_arguments_delta_event.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseFunctionCallArgumentsDeltaEvent"] + + +class ResponseFunctionCallArgumentsDeltaEvent(BaseModel): + call_id: str + """The ID of the function call.""" + + delta: str + """The arguments delta as a JSON string.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the function call item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.function_call_arguments.delta"] + """The event type, must be `response.function_call_arguments.delta`.""" diff --git a/src/openai/types/beta/realtime/response_function_call_arguments_done_event.py b/src/openai/types/beta/realtime/response_function_call_arguments_done_event.py new file mode 100644 index 0000000000..0a5db53323 --- /dev/null +++ b/src/openai/types/beta/realtime/response_function_call_arguments_done_event.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseFunctionCallArgumentsDoneEvent"] + + +class ResponseFunctionCallArgumentsDoneEvent(BaseModel): + arguments: str + """The final arguments as a JSON string.""" + + call_id: str + """The ID of the function call.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the function call item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.function_call_arguments.done"] + """The event type, must be `response.function_call_arguments.done`.""" diff --git a/src/openai/types/beta/realtime/response_output_item_added_event.py b/src/openai/types/beta/realtime/response_output_item_added_event.py new file mode 100644 index 0000000000..c89bfdc3be --- /dev/null +++ b/src/openai/types/beta/realtime/response_output_item_added_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ResponseOutputItemAddedEvent"] + + +class ResponseOutputItemAddedEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + item: ConversationItem + """The item to add to the conversation.""" + + output_index: int + """The index of the output item in the Response.""" + + response_id: str + """The ID of the Response to which the item belongs.""" + + type: Literal["response.output_item.added"] + """The event type, must be `response.output_item.added`.""" diff --git a/src/openai/types/beta/realtime/response_output_item_done_event.py b/src/openai/types/beta/realtime/response_output_item_done_event.py new file mode 100644 index 0000000000..b5910e22aa --- /dev/null +++ b/src/openai/types/beta/realtime/response_output_item_done_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ResponseOutputItemDoneEvent"] + + +class ResponseOutputItemDoneEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + item: ConversationItem + """The item to add to the conversation.""" + + output_index: int + """The index of the output item in the Response.""" + + response_id: str + """The ID of the Response to which the item belongs.""" + + type: Literal["response.output_item.done"] + """The event type, must be `response.output_item.done`.""" diff --git a/src/openai/types/beta/realtime/response_text_delta_event.py b/src/openai/types/beta/realtime/response_text_delta_event.py new file mode 100644 index 0000000000..c463b3c3d0 --- /dev/null +++ b/src/openai/types/beta/realtime/response_text_delta_event.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseTextDeltaEvent"] + + +class ResponseTextDeltaEvent(BaseModel): + content_index: int + """The index of the content part in the item's content array.""" + + delta: str + """The text delta.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.text.delta"] + """The event type, must be `response.text.delta`.""" diff --git a/src/openai/types/beta/realtime/response_text_done_event.py b/src/openai/types/beta/realtime/response_text_done_event.py new file mode 100644 index 0000000000..020ff41d58 --- /dev/null +++ b/src/openai/types/beta/realtime/response_text_done_event.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ResponseTextDoneEvent"] + + +class ResponseTextDoneEvent(BaseModel): + content_index: int + """The index of the content part in the item's content array.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + text: str + """The final text content.""" + + type: Literal["response.text.done"] + """The event type, must be `response.text.done`.""" diff --git a/src/openai/types/beta/realtime/session.py b/src/openai/types/beta/realtime/session.py new file mode 100644 index 0000000000..2e099a2e98 --- /dev/null +++ b/src/openai/types/beta/realtime/session.py @@ -0,0 +1,279 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ...._models import BaseModel + +__all__ = [ + "Session", + "InputAudioNoiseReduction", + "InputAudioTranscription", + "Tool", + "Tracing", + "TracingTracingConfiguration", + "TurnDetection", +] + + +class InputAudioNoiseReduction(BaseModel): + type: Optional[Literal["near_field", "far_field"]] = None + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class InputAudioTranscription(BaseModel): + language: Optional[str] = None + """The language of the input audio. + + Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + """ + + model: Optional[str] = None + """ + The model to use for transcription, current options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `whisper-1`. + """ + + prompt: Optional[str] = None + """ + An optional text to guide the model's style or continue a previous audio + segment. For `whisper-1`, the + [prompt is a list of keywords](https://platform.openai.com/docs/guides/speech-to-text#prompting). + For `gpt-4o-transcribe` models, the prompt is a free text string, for example + "expect words related to technology". + """ + + +class Tool(BaseModel): + description: Optional[str] = None + """ + The description of the function, including guidance on when and how to call it, + and guidance about what to tell the user when calling (if anything). + """ + + name: Optional[str] = None + """The name of the function.""" + + parameters: Optional[object] = None + """Parameters of the function in JSON Schema.""" + + type: Optional[Literal["function"]] = None + """The type of the tool, i.e. `function`.""" + + +class TracingTracingConfiguration(BaseModel): + group_id: Optional[str] = None + """ + The group id to attach to this trace to enable filtering and grouping in the + traces dashboard. + """ + + metadata: Optional[object] = None + """ + The arbitrary metadata to attach to this trace to enable filtering in the traces + dashboard. + """ + + workflow_name: Optional[str] = None + """The name of the workflow to attach to this trace. + + This is used to name the trace in the traces dashboard. + """ + + +Tracing: TypeAlias = Union[Literal["auto"], TracingTracingConfiguration] + + +class TurnDetection(BaseModel): + create_response: Optional[bool] = None + """ + Whether or not to automatically generate a response when a VAD stop event + occurs. + """ + + eagerness: Optional[Literal["low", "medium", "high", "auto"]] = None + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. + """ + + interrupt_response: Optional[bool] = None + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. + """ + + prefix_padding_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: Optional[float] = None + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + type: Optional[Literal["server_vad", "semantic_vad"]] = None + """Type of turn detection.""" + + +class Session(BaseModel): + id: Optional[str] = None + """Unique identifier for the session that looks like `sess_1234567890abcdef`.""" + + input_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None + """The format of input audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, input audio must + be 16-bit PCM at a 24kHz sample rate, single channel (mono), and little-endian + byte order. + """ + + input_audio_noise_reduction: Optional[InputAudioNoiseReduction] = None + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + input_audio_transcription: Optional[InputAudioTranscription] = None + """ + Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously through + [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) + and should be treated as guidance of input audio content rather than precisely + what the model heard. The client can optionally set the language and prompt for + transcription, these offer additional guidance to the transcription service. + """ + + instructions: Optional[str] = None + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_response_output_tokens: Union[int, Literal["inf"], None] = None + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + modalities: Optional[List[Literal["text", "audio"]]] = None + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + model: Optional[ + Literal[ + "gpt-realtime", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + ] + ] = None + """The Realtime model used for this session.""" + + output_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None + """The format of output audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, output audio is + sampled at a rate of 24kHz. + """ + + speed: Optional[float] = None + """The speed of the model's spoken response. + + 1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. + This value can only be changed in between model turns, not while a response is + in progress. + """ + + temperature: Optional[float] = None + """Sampling temperature for the model, limited to [0.6, 1.2]. + + For audio models a temperature of 0.8 is highly recommended for best + performance. + """ + + tool_choice: Optional[str] = None + """How the model chooses tools. + + Options are `auto`, `none`, `required`, or specify a function. + """ + + tools: Optional[List[Tool]] = None + """Tools (functions) available to the model.""" + + tracing: Optional[Tracing] = None + """Configuration options for tracing. + + Set to null to disable tracing. Once tracing is enabled for a session, the + configuration cannot be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + """ + + turn_detection: Optional[TurnDetection] = None + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. Server VAD means that the model will detect the start + and end of speech based on audio volume and respond at the end of user speech. + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + """ + + voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"], None] = None + """The voice the model uses to respond. + + Voice cannot be changed during the session once the model has responded with + audio at least once. Current voice options are `alloy`, `ash`, `ballad`, + `coral`, `echo`, `sage`, `shimmer`, and `verse`. + """ diff --git a/src/openai/types/beta/realtime/session_create_params.py b/src/openai/types/beta/realtime/session_create_params.py new file mode 100644 index 0000000000..38465a56c3 --- /dev/null +++ b/src/openai/types/beta/realtime/session_create_params.py @@ -0,0 +1,298 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = [ + "SessionCreateParams", + "ClientSecret", + "ClientSecretExpiresAfter", + "InputAudioNoiseReduction", + "InputAudioTranscription", + "Tool", + "Tracing", + "TracingTracingConfiguration", + "TurnDetection", +] + + +class SessionCreateParams(TypedDict, total=False): + client_secret: ClientSecret + """Configuration options for the generated client secret.""" + + input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] + """The format of input audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, input audio must + be 16-bit PCM at a 24kHz sample rate, single channel (mono), and little-endian + byte order. + """ + + input_audio_noise_reduction: InputAudioNoiseReduction + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + input_audio_transcription: InputAudioTranscription + """ + Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously through + [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) + and should be treated as guidance of input audio content rather than precisely + what the model heard. The client can optionally set the language and prompt for + transcription, these offer additional guidance to the transcription service. + """ + + instructions: str + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_response_output_tokens: Union[int, Literal["inf"]] + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + modalities: List[Literal["text", "audio"]] + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + model: Literal[ + "gpt-realtime", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + ] + """The Realtime model used for this session.""" + + output_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] + """The format of output audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, output audio is + sampled at a rate of 24kHz. + """ + + speed: float + """The speed of the model's spoken response. + + 1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. + This value can only be changed in between model turns, not while a response is + in progress. + """ + + temperature: float + """Sampling temperature for the model, limited to [0.6, 1.2]. + + For audio models a temperature of 0.8 is highly recommended for best + performance. + """ + + tool_choice: str + """How the model chooses tools. + + Options are `auto`, `none`, `required`, or specify a function. + """ + + tools: Iterable[Tool] + """Tools (functions) available to the model.""" + + tracing: Tracing + """Configuration options for tracing. + + Set to null to disable tracing. Once tracing is enabled for a session, the + configuration cannot be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + """ + + turn_detection: TurnDetection + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. Server VAD means that the model will detect the start + and end of speech based on audio volume and respond at the end of user speech. + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + """ + + voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"]] + """The voice the model uses to respond. + + Voice cannot be changed during the session once the model has responded with + audio at least once. Current voice options are `alloy`, `ash`, `ballad`, + `coral`, `echo`, `sage`, `shimmer`, and `verse`. + """ + + +class ClientSecretExpiresAfter(TypedDict, total=False): + anchor: Required[Literal["created_at"]] + """The anchor point for the ephemeral token expiration. + + Only `created_at` is currently supported. + """ + + seconds: int + """The number of seconds from the anchor point to the expiration. + + Select a value between `10` and `7200`. + """ + + +class ClientSecret(TypedDict, total=False): + expires_after: ClientSecretExpiresAfter + """Configuration for the ephemeral token expiration.""" + + +class InputAudioNoiseReduction(TypedDict, total=False): + type: Literal["near_field", "far_field"] + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class InputAudioTranscription(TypedDict, total=False): + language: str + """The language of the input audio. + + Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + """ + + model: str + """ + The model to use for transcription, current options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `whisper-1`. + """ + + prompt: str + """ + An optional text to guide the model's style or continue a previous audio + segment. For `whisper-1`, the + [prompt is a list of keywords](https://platform.openai.com/docs/guides/speech-to-text#prompting). + For `gpt-4o-transcribe` models, the prompt is a free text string, for example + "expect words related to technology". + """ + + +class Tool(TypedDict, total=False): + description: str + """ + The description of the function, including guidance on when and how to call it, + and guidance about what to tell the user when calling (if anything). + """ + + name: str + """The name of the function.""" + + parameters: object + """Parameters of the function in JSON Schema.""" + + type: Literal["function"] + """The type of the tool, i.e. `function`.""" + + +class TracingTracingConfiguration(TypedDict, total=False): + group_id: str + """ + The group id to attach to this trace to enable filtering and grouping in the + traces dashboard. + """ + + metadata: object + """ + The arbitrary metadata to attach to this trace to enable filtering in the traces + dashboard. + """ + + workflow_name: str + """The name of the workflow to attach to this trace. + + This is used to name the trace in the traces dashboard. + """ + + +Tracing: TypeAlias = Union[Literal["auto"], TracingTracingConfiguration] + + +class TurnDetection(TypedDict, total=False): + create_response: bool + """ + Whether or not to automatically generate a response when a VAD stop event + occurs. + """ + + eagerness: Literal["low", "medium", "high", "auto"] + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. + """ + + interrupt_response: bool + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. + """ + + prefix_padding_ms: int + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: int + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: float + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + type: Literal["server_vad", "semantic_vad"] + """Type of turn detection.""" diff --git a/src/openai/types/beta/realtime/session_create_response.py b/src/openai/types/beta/realtime/session_create_response.py new file mode 100644 index 0000000000..471da03691 --- /dev/null +++ b/src/openai/types/beta/realtime/session_create_response.py @@ -0,0 +1,196 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ...._models import BaseModel + +__all__ = [ + "SessionCreateResponse", + "ClientSecret", + "InputAudioTranscription", + "Tool", + "Tracing", + "TracingTracingConfiguration", + "TurnDetection", +] + + +class ClientSecret(BaseModel): + expires_at: int + """Timestamp for when the token expires. + + Currently, all tokens expire after one minute. + """ + + value: str + """ + Ephemeral key usable in client environments to authenticate connections to the + Realtime API. Use this in client-side environments rather than a standard API + token, which should only be used server-side. + """ + + +class InputAudioTranscription(BaseModel): + model: Optional[str] = None + """The model to use for transcription.""" + + +class Tool(BaseModel): + description: Optional[str] = None + """ + The description of the function, including guidance on when and how to call it, + and guidance about what to tell the user when calling (if anything). + """ + + name: Optional[str] = None + """The name of the function.""" + + parameters: Optional[object] = None + """Parameters of the function in JSON Schema.""" + + type: Optional[Literal["function"]] = None + """The type of the tool, i.e. `function`.""" + + +class TracingTracingConfiguration(BaseModel): + group_id: Optional[str] = None + """ + The group id to attach to this trace to enable filtering and grouping in the + traces dashboard. + """ + + metadata: Optional[object] = None + """ + The arbitrary metadata to attach to this trace to enable filtering in the traces + dashboard. + """ + + workflow_name: Optional[str] = None + """The name of the workflow to attach to this trace. + + This is used to name the trace in the traces dashboard. + """ + + +Tracing: TypeAlias = Union[Literal["auto"], TracingTracingConfiguration] + + +class TurnDetection(BaseModel): + prefix_padding_ms: Optional[int] = None + """Amount of audio to include before the VAD detected speech (in milliseconds). + + Defaults to 300ms. + """ + + silence_duration_ms: Optional[int] = None + """Duration of silence to detect speech stop (in milliseconds). + + Defaults to 500ms. With shorter values the model will respond more quickly, but + may jump in on short pauses from the user. + """ + + threshold: Optional[float] = None + """Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. + + A higher threshold will require louder audio to activate the model, and thus + might perform better in noisy environments. + """ + + type: Optional[str] = None + """Type of turn detection, only `server_vad` is currently supported.""" + + +class SessionCreateResponse(BaseModel): + client_secret: ClientSecret + """Ephemeral key returned by the API.""" + + input_audio_format: Optional[str] = None + """The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.""" + + input_audio_transcription: Optional[InputAudioTranscription] = None + """ + Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously and should be treated as rough guidance rather than the + representation understood by the model. + """ + + instructions: Optional[str] = None + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_response_output_tokens: Union[int, Literal["inf"], None] = None + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + modalities: Optional[List[Literal["text", "audio"]]] = None + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + output_audio_format: Optional[str] = None + """The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.""" + + speed: Optional[float] = None + """The speed of the model's spoken response. + + 1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. + This value can only be changed in between model turns, not while a response is + in progress. + """ + + temperature: Optional[float] = None + """Sampling temperature for the model, limited to [0.6, 1.2]. Defaults to 0.8.""" + + tool_choice: Optional[str] = None + """How the model chooses tools. + + Options are `auto`, `none`, `required`, or specify a function. + """ + + tools: Optional[List[Tool]] = None + """Tools (functions) available to the model.""" + + tracing: Optional[Tracing] = None + """Configuration options for tracing. + + Set to null to disable tracing. Once tracing is enabled for a session, the + configuration cannot be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + """ + + turn_detection: Optional[TurnDetection] = None + """Configuration for turn detection. + + Can be set to `null` to turn off. Server VAD means that the model will detect + the start and end of speech based on audio volume and respond at the end of user + speech. + """ + + voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"], None] = None + """The voice the model uses to respond. + + Voice cannot be changed during the session once the model has responded with + audio at least once. Current voice options are `alloy`, `ash`, `ballad`, + `coral`, `echo`, `sage`, `shimmer`, and `verse`. + """ diff --git a/src/openai/types/beta/realtime/session_created_event.py b/src/openai/types/beta/realtime/session_created_event.py new file mode 100644 index 0000000000..baf6af388b --- /dev/null +++ b/src/openai/types/beta/realtime/session_created_event.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .session import Session +from ...._models import BaseModel + +__all__ = ["SessionCreatedEvent"] + + +class SessionCreatedEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + session: Session + """Realtime session object configuration.""" + + type: Literal["session.created"] + """The event type, must be `session.created`.""" diff --git a/src/openai/types/beta/realtime/session_update_event.py b/src/openai/types/beta/realtime/session_update_event.py new file mode 100644 index 0000000000..78d2e4bb18 --- /dev/null +++ b/src/openai/types/beta/realtime/session_update_event.py @@ -0,0 +1,312 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ...._models import BaseModel + +__all__ = [ + "SessionUpdateEvent", + "Session", + "SessionClientSecret", + "SessionClientSecretExpiresAfter", + "SessionInputAudioNoiseReduction", + "SessionInputAudioTranscription", + "SessionTool", + "SessionTracing", + "SessionTracingTracingConfiguration", + "SessionTurnDetection", +] + + +class SessionClientSecretExpiresAfter(BaseModel): + anchor: Literal["created_at"] + """The anchor point for the ephemeral token expiration. + + Only `created_at` is currently supported. + """ + + seconds: Optional[int] = None + """The number of seconds from the anchor point to the expiration. + + Select a value between `10` and `7200`. + """ + + +class SessionClientSecret(BaseModel): + expires_after: Optional[SessionClientSecretExpiresAfter] = None + """Configuration for the ephemeral token expiration.""" + + +class SessionInputAudioNoiseReduction(BaseModel): + type: Optional[Literal["near_field", "far_field"]] = None + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class SessionInputAudioTranscription(BaseModel): + language: Optional[str] = None + """The language of the input audio. + + Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + """ + + model: Optional[str] = None + """ + The model to use for transcription, current options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `whisper-1`. + """ + + prompt: Optional[str] = None + """ + An optional text to guide the model's style or continue a previous audio + segment. For `whisper-1`, the + [prompt is a list of keywords](https://platform.openai.com/docs/guides/speech-to-text#prompting). + For `gpt-4o-transcribe` models, the prompt is a free text string, for example + "expect words related to technology". + """ + + +class SessionTool(BaseModel): + description: Optional[str] = None + """ + The description of the function, including guidance on when and how to call it, + and guidance about what to tell the user when calling (if anything). + """ + + name: Optional[str] = None + """The name of the function.""" + + parameters: Optional[object] = None + """Parameters of the function in JSON Schema.""" + + type: Optional[Literal["function"]] = None + """The type of the tool, i.e. `function`.""" + + +class SessionTracingTracingConfiguration(BaseModel): + group_id: Optional[str] = None + """ + The group id to attach to this trace to enable filtering and grouping in the + traces dashboard. + """ + + metadata: Optional[object] = None + """ + The arbitrary metadata to attach to this trace to enable filtering in the traces + dashboard. + """ + + workflow_name: Optional[str] = None + """The name of the workflow to attach to this trace. + + This is used to name the trace in the traces dashboard. + """ + + +SessionTracing: TypeAlias = Union[Literal["auto"], SessionTracingTracingConfiguration] + + +class SessionTurnDetection(BaseModel): + create_response: Optional[bool] = None + """ + Whether or not to automatically generate a response when a VAD stop event + occurs. + """ + + eagerness: Optional[Literal["low", "medium", "high", "auto"]] = None + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. + """ + + interrupt_response: Optional[bool] = None + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. + """ + + prefix_padding_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: Optional[float] = None + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + type: Optional[Literal["server_vad", "semantic_vad"]] = None + """Type of turn detection.""" + + +class Session(BaseModel): + client_secret: Optional[SessionClientSecret] = None + """Configuration options for the generated client secret.""" + + input_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None + """The format of input audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, input audio must + be 16-bit PCM at a 24kHz sample rate, single channel (mono), and little-endian + byte order. + """ + + input_audio_noise_reduction: Optional[SessionInputAudioNoiseReduction] = None + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + input_audio_transcription: Optional[SessionInputAudioTranscription] = None + """ + Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously through + [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) + and should be treated as guidance of input audio content rather than precisely + what the model heard. The client can optionally set the language and prompt for + transcription, these offer additional guidance to the transcription service. + """ + + instructions: Optional[str] = None + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_response_output_tokens: Union[int, Literal["inf"], None] = None + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + modalities: Optional[List[Literal["text", "audio"]]] = None + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + model: Optional[ + Literal[ + "gpt-realtime", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + ] + ] = None + """The Realtime model used for this session.""" + + output_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None + """The format of output audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, output audio is + sampled at a rate of 24kHz. + """ + + speed: Optional[float] = None + """The speed of the model's spoken response. + + 1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. + This value can only be changed in between model turns, not while a response is + in progress. + """ + + temperature: Optional[float] = None + """Sampling temperature for the model, limited to [0.6, 1.2]. + + For audio models a temperature of 0.8 is highly recommended for best + performance. + """ + + tool_choice: Optional[str] = None + """How the model chooses tools. + + Options are `auto`, `none`, `required`, or specify a function. + """ + + tools: Optional[List[SessionTool]] = None + """Tools (functions) available to the model.""" + + tracing: Optional[SessionTracing] = None + """Configuration options for tracing. + + Set to null to disable tracing. Once tracing is enabled for a session, the + configuration cannot be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + """ + + turn_detection: Optional[SessionTurnDetection] = None + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. Server VAD means that the model will detect the start + and end of speech based on audio volume and respond at the end of user speech. + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + """ + + voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"], None] = None + """The voice the model uses to respond. + + Voice cannot be changed during the session once the model has responded with + audio at least once. Current voice options are `alloy`, `ash`, `ballad`, + `coral`, `echo`, `sage`, `shimmer`, and `verse`. + """ + + +class SessionUpdateEvent(BaseModel): + session: Session + """Realtime session object configuration.""" + + type: Literal["session.update"] + """The event type, must be `session.update`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/session_update_event_param.py b/src/openai/types/beta/realtime/session_update_event_param.py new file mode 100644 index 0000000000..c58b202a71 --- /dev/null +++ b/src/openai/types/beta/realtime/session_update_event_param.py @@ -0,0 +1,310 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = [ + "SessionUpdateEventParam", + "Session", + "SessionClientSecret", + "SessionClientSecretExpiresAfter", + "SessionInputAudioNoiseReduction", + "SessionInputAudioTranscription", + "SessionTool", + "SessionTracing", + "SessionTracingTracingConfiguration", + "SessionTurnDetection", +] + + +class SessionClientSecretExpiresAfter(TypedDict, total=False): + anchor: Required[Literal["created_at"]] + """The anchor point for the ephemeral token expiration. + + Only `created_at` is currently supported. + """ + + seconds: int + """The number of seconds from the anchor point to the expiration. + + Select a value between `10` and `7200`. + """ + + +class SessionClientSecret(TypedDict, total=False): + expires_after: SessionClientSecretExpiresAfter + """Configuration for the ephemeral token expiration.""" + + +class SessionInputAudioNoiseReduction(TypedDict, total=False): + type: Literal["near_field", "far_field"] + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class SessionInputAudioTranscription(TypedDict, total=False): + language: str + """The language of the input audio. + + Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + """ + + model: str + """ + The model to use for transcription, current options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `whisper-1`. + """ + + prompt: str + """ + An optional text to guide the model's style or continue a previous audio + segment. For `whisper-1`, the + [prompt is a list of keywords](https://platform.openai.com/docs/guides/speech-to-text#prompting). + For `gpt-4o-transcribe` models, the prompt is a free text string, for example + "expect words related to technology". + """ + + +class SessionTool(TypedDict, total=False): + description: str + """ + The description of the function, including guidance on when and how to call it, + and guidance about what to tell the user when calling (if anything). + """ + + name: str + """The name of the function.""" + + parameters: object + """Parameters of the function in JSON Schema.""" + + type: Literal["function"] + """The type of the tool, i.e. `function`.""" + + +class SessionTracingTracingConfiguration(TypedDict, total=False): + group_id: str + """ + The group id to attach to this trace to enable filtering and grouping in the + traces dashboard. + """ + + metadata: object + """ + The arbitrary metadata to attach to this trace to enable filtering in the traces + dashboard. + """ + + workflow_name: str + """The name of the workflow to attach to this trace. + + This is used to name the trace in the traces dashboard. + """ + + +SessionTracing: TypeAlias = Union[Literal["auto"], SessionTracingTracingConfiguration] + + +class SessionTurnDetection(TypedDict, total=False): + create_response: bool + """ + Whether or not to automatically generate a response when a VAD stop event + occurs. + """ + + eagerness: Literal["low", "medium", "high", "auto"] + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. + """ + + interrupt_response: bool + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. + """ + + prefix_padding_ms: int + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: int + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: float + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + type: Literal["server_vad", "semantic_vad"] + """Type of turn detection.""" + + +class Session(TypedDict, total=False): + client_secret: SessionClientSecret + """Configuration options for the generated client secret.""" + + input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] + """The format of input audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, input audio must + be 16-bit PCM at a 24kHz sample rate, single channel (mono), and little-endian + byte order. + """ + + input_audio_noise_reduction: SessionInputAudioNoiseReduction + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + input_audio_transcription: SessionInputAudioTranscription + """ + Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously through + [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) + and should be treated as guidance of input audio content rather than precisely + what the model heard. The client can optionally set the language and prompt for + transcription, these offer additional guidance to the transcription service. + """ + + instructions: str + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_response_output_tokens: Union[int, Literal["inf"]] + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + modalities: List[Literal["text", "audio"]] + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + model: Literal[ + "gpt-realtime", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + ] + """The Realtime model used for this session.""" + + output_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] + """The format of output audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, output audio is + sampled at a rate of 24kHz. + """ + + speed: float + """The speed of the model's spoken response. + + 1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. + This value can only be changed in between model turns, not while a response is + in progress. + """ + + temperature: float + """Sampling temperature for the model, limited to [0.6, 1.2]. + + For audio models a temperature of 0.8 is highly recommended for best + performance. + """ + + tool_choice: str + """How the model chooses tools. + + Options are `auto`, `none`, `required`, or specify a function. + """ + + tools: Iterable[SessionTool] + """Tools (functions) available to the model.""" + + tracing: SessionTracing + """Configuration options for tracing. + + Set to null to disable tracing. Once tracing is enabled for a session, the + configuration cannot be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + """ + + turn_detection: SessionTurnDetection + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. Server VAD means that the model will detect the start + and end of speech based on audio volume and respond at the end of user speech. + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + """ + + voice: Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"]] + """The voice the model uses to respond. + + Voice cannot be changed during the session once the model has responded with + audio at least once. Current voice options are `alloy`, `ash`, `ballad`, + `coral`, `echo`, `sage`, `shimmer`, and `verse`. + """ + + +class SessionUpdateEventParam(TypedDict, total=False): + session: Required[Session] + """Realtime session object configuration.""" + + type: Required[Literal["session.update"]] + """The event type, must be `session.update`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/session_updated_event.py b/src/openai/types/beta/realtime/session_updated_event.py new file mode 100644 index 0000000000..b9b6488eb3 --- /dev/null +++ b/src/openai/types/beta/realtime/session_updated_event.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .session import Session +from ...._models import BaseModel + +__all__ = ["SessionUpdatedEvent"] + + +class SessionUpdatedEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + session: Session + """Realtime session object configuration.""" + + type: Literal["session.updated"] + """The event type, must be `session.updated`.""" diff --git a/src/openai/types/beta/realtime/transcription_session.py b/src/openai/types/beta/realtime/transcription_session.py new file mode 100644 index 0000000000..7c7abf37b6 --- /dev/null +++ b/src/openai/types/beta/realtime/transcription_session.py @@ -0,0 +1,100 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["TranscriptionSession", "ClientSecret", "InputAudioTranscription", "TurnDetection"] + + +class ClientSecret(BaseModel): + expires_at: int + """Timestamp for when the token expires. + + Currently, all tokens expire after one minute. + """ + + value: str + """ + Ephemeral key usable in client environments to authenticate connections to the + Realtime API. Use this in client-side environments rather than a standard API + token, which should only be used server-side. + """ + + +class InputAudioTranscription(BaseModel): + language: Optional[str] = None + """The language of the input audio. + + Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + """ + + model: Optional[Literal["gpt-4o-transcribe", "gpt-4o-mini-transcribe", "whisper-1"]] = None + """The model to use for transcription. + + Can be `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, or `whisper-1`. + """ + + prompt: Optional[str] = None + """An optional text to guide the model's style or continue a previous audio + segment. + + The [prompt](https://platform.openai.com/docs/guides/speech-to-text#prompting) + should match the audio language. + """ + + +class TurnDetection(BaseModel): + prefix_padding_ms: Optional[int] = None + """Amount of audio to include before the VAD detected speech (in milliseconds). + + Defaults to 300ms. + """ + + silence_duration_ms: Optional[int] = None + """Duration of silence to detect speech stop (in milliseconds). + + Defaults to 500ms. With shorter values the model will respond more quickly, but + may jump in on short pauses from the user. + """ + + threshold: Optional[float] = None + """Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. + + A higher threshold will require louder audio to activate the model, and thus + might perform better in noisy environments. + """ + + type: Optional[str] = None + """Type of turn detection, only `server_vad` is currently supported.""" + + +class TranscriptionSession(BaseModel): + client_secret: ClientSecret + """Ephemeral key returned by the API. + + Only present when the session is created on the server via REST API. + """ + + input_audio_format: Optional[str] = None + """The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.""" + + input_audio_transcription: Optional[InputAudioTranscription] = None + """Configuration of the transcription model.""" + + modalities: Optional[List[Literal["text", "audio"]]] = None + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + turn_detection: Optional[TurnDetection] = None + """Configuration for turn detection. + + Can be set to `null` to turn off. Server VAD means that the model will detect + the start and end of speech based on audio volume and respond at the end of user + speech. + """ diff --git a/src/openai/types/beta/realtime/transcription_session_create_params.py b/src/openai/types/beta/realtime/transcription_session_create_params.py new file mode 100644 index 0000000000..3ac3af4fa9 --- /dev/null +++ b/src/openai/types/beta/realtime/transcription_session_create_params.py @@ -0,0 +1,173 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, TypedDict + +__all__ = [ + "TranscriptionSessionCreateParams", + "ClientSecret", + "ClientSecretExpiresAt", + "InputAudioNoiseReduction", + "InputAudioTranscription", + "TurnDetection", +] + + +class TranscriptionSessionCreateParams(TypedDict, total=False): + client_secret: ClientSecret + """Configuration options for the generated client secret.""" + + include: List[str] + """The set of items to include in the transcription. Current available items are: + + - `item.input_audio_transcription.logprobs` + """ + + input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] + """The format of input audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, input audio must + be 16-bit PCM at a 24kHz sample rate, single channel (mono), and little-endian + byte order. + """ + + input_audio_noise_reduction: InputAudioNoiseReduction + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + input_audio_transcription: InputAudioTranscription + """Configuration for input audio transcription. + + The client can optionally set the language and prompt for transcription, these + offer additional guidance to the transcription service. + """ + + modalities: List[Literal["text", "audio"]] + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + turn_detection: TurnDetection + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. Server VAD means that the model will detect the start + and end of speech based on audio volume and respond at the end of user speech. + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + """ + + +class ClientSecretExpiresAt(TypedDict, total=False): + anchor: Literal["created_at"] + """The anchor point for the ephemeral token expiration. + + Only `created_at` is currently supported. + """ + + seconds: int + """The number of seconds from the anchor point to the expiration. + + Select a value between `10` and `7200`. + """ + + +class ClientSecret(TypedDict, total=False): + expires_at: ClientSecretExpiresAt + """Configuration for the ephemeral token expiration.""" + + +class InputAudioNoiseReduction(TypedDict, total=False): + type: Literal["near_field", "far_field"] + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class InputAudioTranscription(TypedDict, total=False): + language: str + """The language of the input audio. + + Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + """ + + model: Literal["gpt-4o-transcribe", "gpt-4o-mini-transcribe", "whisper-1"] + """ + The model to use for transcription, current options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `whisper-1`. + """ + + prompt: str + """ + An optional text to guide the model's style or continue a previous audio + segment. For `whisper-1`, the + [prompt is a list of keywords](https://platform.openai.com/docs/guides/speech-to-text#prompting). + For `gpt-4o-transcribe` models, the prompt is a free text string, for example + "expect words related to technology". + """ + + +class TurnDetection(TypedDict, total=False): + create_response: bool + """Whether or not to automatically generate a response when a VAD stop event + occurs. + + Not available for transcription sessions. + """ + + eagerness: Literal["low", "medium", "high", "auto"] + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. + """ + + interrupt_response: bool + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. Not available for transcription sessions. + """ + + prefix_padding_ms: int + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: int + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: float + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + type: Literal["server_vad", "semantic_vad"] + """Type of turn detection.""" diff --git a/src/openai/types/beta/realtime/transcription_session_update.py b/src/openai/types/beta/realtime/transcription_session_update.py new file mode 100644 index 0000000000..5ae1ad226d --- /dev/null +++ b/src/openai/types/beta/realtime/transcription_session_update.py @@ -0,0 +1,185 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = [ + "TranscriptionSessionUpdate", + "Session", + "SessionClientSecret", + "SessionClientSecretExpiresAt", + "SessionInputAudioNoiseReduction", + "SessionInputAudioTranscription", + "SessionTurnDetection", +] + + +class SessionClientSecretExpiresAt(BaseModel): + anchor: Optional[Literal["created_at"]] = None + """The anchor point for the ephemeral token expiration. + + Only `created_at` is currently supported. + """ + + seconds: Optional[int] = None + """The number of seconds from the anchor point to the expiration. + + Select a value between `10` and `7200`. + """ + + +class SessionClientSecret(BaseModel): + expires_at: Optional[SessionClientSecretExpiresAt] = None + """Configuration for the ephemeral token expiration.""" + + +class SessionInputAudioNoiseReduction(BaseModel): + type: Optional[Literal["near_field", "far_field"]] = None + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class SessionInputAudioTranscription(BaseModel): + language: Optional[str] = None + """The language of the input audio. + + Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + """ + + model: Optional[Literal["gpt-4o-transcribe", "gpt-4o-mini-transcribe", "whisper-1"]] = None + """ + The model to use for transcription, current options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `whisper-1`. + """ + + prompt: Optional[str] = None + """ + An optional text to guide the model's style or continue a previous audio + segment. For `whisper-1`, the + [prompt is a list of keywords](https://platform.openai.com/docs/guides/speech-to-text#prompting). + For `gpt-4o-transcribe` models, the prompt is a free text string, for example + "expect words related to technology". + """ + + +class SessionTurnDetection(BaseModel): + create_response: Optional[bool] = None + """Whether or not to automatically generate a response when a VAD stop event + occurs. + + Not available for transcription sessions. + """ + + eagerness: Optional[Literal["low", "medium", "high", "auto"]] = None + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. + """ + + interrupt_response: Optional[bool] = None + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. Not available for transcription sessions. + """ + + prefix_padding_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: Optional[float] = None + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + type: Optional[Literal["server_vad", "semantic_vad"]] = None + """Type of turn detection.""" + + +class Session(BaseModel): + client_secret: Optional[SessionClientSecret] = None + """Configuration options for the generated client secret.""" + + include: Optional[List[str]] = None + """The set of items to include in the transcription. Current available items are: + + - `item.input_audio_transcription.logprobs` + """ + + input_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None + """The format of input audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, input audio must + be 16-bit PCM at a 24kHz sample rate, single channel (mono), and little-endian + byte order. + """ + + input_audio_noise_reduction: Optional[SessionInputAudioNoiseReduction] = None + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + input_audio_transcription: Optional[SessionInputAudioTranscription] = None + """Configuration for input audio transcription. + + The client can optionally set the language and prompt for transcription, these + offer additional guidance to the transcription service. + """ + + modalities: Optional[List[Literal["text", "audio"]]] = None + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + turn_detection: Optional[SessionTurnDetection] = None + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. Server VAD means that the model will detect the start + and end of speech based on audio volume and respond at the end of user speech. + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + """ + + +class TranscriptionSessionUpdate(BaseModel): + session: Session + """Realtime transcription session object configuration.""" + + type: Literal["transcription_session.update"] + """The event type, must be `transcription_session.update`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/transcription_session_update_param.py b/src/openai/types/beta/realtime/transcription_session_update_param.py new file mode 100644 index 0000000000..d7065f61c7 --- /dev/null +++ b/src/openai/types/beta/realtime/transcription_session_update_param.py @@ -0,0 +1,185 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +__all__ = [ + "TranscriptionSessionUpdateParam", + "Session", + "SessionClientSecret", + "SessionClientSecretExpiresAt", + "SessionInputAudioNoiseReduction", + "SessionInputAudioTranscription", + "SessionTurnDetection", +] + + +class SessionClientSecretExpiresAt(TypedDict, total=False): + anchor: Literal["created_at"] + """The anchor point for the ephemeral token expiration. + + Only `created_at` is currently supported. + """ + + seconds: int + """The number of seconds from the anchor point to the expiration. + + Select a value between `10` and `7200`. + """ + + +class SessionClientSecret(TypedDict, total=False): + expires_at: SessionClientSecretExpiresAt + """Configuration for the ephemeral token expiration.""" + + +class SessionInputAudioNoiseReduction(TypedDict, total=False): + type: Literal["near_field", "far_field"] + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class SessionInputAudioTranscription(TypedDict, total=False): + language: str + """The language of the input audio. + + Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + """ + + model: Literal["gpt-4o-transcribe", "gpt-4o-mini-transcribe", "whisper-1"] + """ + The model to use for transcription, current options are `gpt-4o-transcribe`, + `gpt-4o-mini-transcribe`, and `whisper-1`. + """ + + prompt: str + """ + An optional text to guide the model's style or continue a previous audio + segment. For `whisper-1`, the + [prompt is a list of keywords](https://platform.openai.com/docs/guides/speech-to-text#prompting). + For `gpt-4o-transcribe` models, the prompt is a free text string, for example + "expect words related to technology". + """ + + +class SessionTurnDetection(TypedDict, total=False): + create_response: bool + """Whether or not to automatically generate a response when a VAD stop event + occurs. + + Not available for transcription sessions. + """ + + eagerness: Literal["low", "medium", "high", "auto"] + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. + """ + + interrupt_response: bool + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. Not available for transcription sessions. + """ + + prefix_padding_ms: int + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: int + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: float + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + type: Literal["server_vad", "semantic_vad"] + """Type of turn detection.""" + + +class Session(TypedDict, total=False): + client_secret: SessionClientSecret + """Configuration options for the generated client secret.""" + + include: List[str] + """The set of items to include in the transcription. Current available items are: + + - `item.input_audio_transcription.logprobs` + """ + + input_audio_format: Literal["pcm16", "g711_ulaw", "g711_alaw"] + """The format of input audio. + + Options are `pcm16`, `g711_ulaw`, or `g711_alaw`. For `pcm16`, input audio must + be 16-bit PCM at a 24kHz sample rate, single channel (mono), and little-endian + byte order. + """ + + input_audio_noise_reduction: SessionInputAudioNoiseReduction + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + input_audio_transcription: SessionInputAudioTranscription + """Configuration for input audio transcription. + + The client can optionally set the language and prompt for transcription, these + offer additional guidance to the transcription service. + """ + + modalities: List[Literal["text", "audio"]] + """The set of modalities the model can respond with. + + To disable audio, set this to ["text"]. + """ + + turn_detection: SessionTurnDetection + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. Server VAD means that the model will detect the start + and end of speech based on audio volume and respond at the end of user speech. + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + """ + + +class TranscriptionSessionUpdateParam(TypedDict, total=False): + session: Required[Session] + """Realtime transcription session object configuration.""" + + type: Required[Literal["transcription_session.update"]] + """The event type, must be `transcription_session.update`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/beta/realtime/transcription_session_updated_event.py b/src/openai/types/beta/realtime/transcription_session_updated_event.py new file mode 100644 index 0000000000..1f1fbdae14 --- /dev/null +++ b/src/openai/types/beta/realtime/transcription_session_updated_event.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel +from .transcription_session import TranscriptionSession + +__all__ = ["TranscriptionSessionUpdatedEvent"] + + +class TranscriptionSessionUpdatedEvent(BaseModel): + event_id: str + """The unique ID of the server event.""" + + session: TranscriptionSession + """A new Realtime transcription session configuration. + + When a session is created on the server via REST API, the session object also + contains an ephemeral key. Default TTL for keys is 10 minutes. This property is + not present when a session is updated via the WebSocket API. + """ + + type: Literal["transcription_session.updated"] + """The event type, must be `transcription_session.updated`.""" diff --git a/src/openai/types/beta/thread.py b/src/openai/types/beta/thread.py index 6f7a6c7d0c..83d9055194 100644 --- a/src/openai/types/beta/thread.py +++ b/src/openai/types/beta/thread.py @@ -4,6 +4,7 @@ from typing_extensions import Literal from ..._models import BaseModel +from ..shared.metadata import Metadata __all__ = ["Thread", "ToolResources", "ToolResourcesCodeInterpreter", "ToolResourcesFileSearch"] @@ -28,24 +29,34 @@ class ToolResourcesFileSearch(BaseModel): class ToolResources(BaseModel): + """ + A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs. + """ + code_interpreter: Optional[ToolResourcesCodeInterpreter] = None file_search: Optional[ToolResourcesFileSearch] = None class Thread(BaseModel): + """ + Represents a thread that contains [messages](https://platform.openai.com/docs/api-reference/messages). + """ + id: str """The identifier, which can be referenced in API endpoints.""" created_at: int """The Unix timestamp (in seconds) for when the thread was created.""" - metadata: Optional[object] = None + metadata: Optional[Metadata] = None """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ object: Literal["thread"] diff --git a/src/openai/types/beta/thread_create_and_run_params.py b/src/openai/types/beta/thread_create_and_run_params.py index 20d525fa1a..3d3d5d4f2e 100644 --- a/src/openai/types/beta/thread_create_and_run_params.py +++ b/src/openai/types/beta/thread_create_and_run_params.py @@ -2,14 +2,14 @@ from __future__ import annotations -from typing import List, Union, Iterable, Optional +from typing import Union, Iterable, Optional from typing_extensions import Literal, Required, TypeAlias, TypedDict -from ..chat_model import ChatModel -from .function_tool_param import FunctionToolParam -from .file_search_tool_param import FileSearchToolParam +from ..._types import SequenceNotStr +from ..shared.chat_model import ChatModel +from .assistant_tool_param import AssistantToolParam +from ..shared_params.metadata import Metadata from .code_interpreter_tool_param import CodeInterpreterToolParam -from .file_chunking_strategy_param import FileChunkingStrategyParam from .assistant_tool_choice_option_param import AssistantToolChoiceOptionParam from .threads.message_content_part_param import MessageContentPartParam from .assistant_response_format_option_param import AssistantResponseFormatOptionParam @@ -25,10 +25,13 @@ "ThreadToolResourcesCodeInterpreter", "ThreadToolResourcesFileSearch", "ThreadToolResourcesFileSearchVectorStore", + "ThreadToolResourcesFileSearchVectorStoreChunkingStrategy", + "ThreadToolResourcesFileSearchVectorStoreChunkingStrategyAuto", + "ThreadToolResourcesFileSearchVectorStoreChunkingStrategyStatic", + "ThreadToolResourcesFileSearchVectorStoreChunkingStrategyStaticStatic", "ToolResources", "ToolResourcesCodeInterpreter", "ToolResourcesFileSearch", - "Tool", "TruncationStrategy", "ThreadCreateAndRunParamsNonStreaming", "ThreadCreateAndRunParamsStreaming", @@ -67,12 +70,14 @@ class ThreadCreateAndRunParamsBase(TypedDict, total=False): `incomplete_details` for more info. """ - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ model: Union[str, ChatModel, None] @@ -86,15 +91,15 @@ class ThreadCreateAndRunParamsBase(TypedDict, total=False): parallel_tool_calls: bool """ Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. """ response_format: Optional[AssistantResponseFormatOptionParam] """Specifies the format that the model must output. - Compatible with [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + Compatible with [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -122,7 +127,11 @@ class ThreadCreateAndRunParamsBase(TypedDict, total=False): """ thread: Thread - """If no thread is provided, an empty thread will be created.""" + """Options to create a new thread. + + If no thread is provided when running a request, an empty thread will be + created. + """ tool_choice: Optional[AssistantToolChoiceOptionParam] """ @@ -143,7 +152,7 @@ class ThreadCreateAndRunParamsBase(TypedDict, total=False): tool requires a list of vector store IDs. """ - tools: Optional[Iterable[Tool]] + tools: Optional[Iterable[AssistantToolParam]] """Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis. @@ -161,7 +170,7 @@ class ThreadCreateAndRunParamsBase(TypedDict, total=False): truncation_strategy: Optional[TruncationStrategy] """Controls for how a thread will be truncated prior to the run. - Use this to control the intial context window of the run. + Use this to control the initial context window of the run. """ @@ -197,17 +206,19 @@ class ThreadMessage(TypedDict, total=False): attachments: Optional[Iterable[ThreadMessageAttachment]] """A list of files attached to the message, and the tools they should be added to.""" - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ class ThreadToolResourcesCodeInterpreter(TypedDict, total=False): - file_ids: List[str] + file_ids: SequenceNotStr[str] """ A list of [file](https://platform.openai.com/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files @@ -215,32 +226,72 @@ class ThreadToolResourcesCodeInterpreter(TypedDict, total=False): """ +class ThreadToolResourcesFileSearchVectorStoreChunkingStrategyAuto(TypedDict, total=False): + """The default strategy. + + This strategy currently uses a `max_chunk_size_tokens` of `800` and `chunk_overlap_tokens` of `400`. + """ + + type: Required[Literal["auto"]] + """Always `auto`.""" + + +class ThreadToolResourcesFileSearchVectorStoreChunkingStrategyStaticStatic(TypedDict, total=False): + chunk_overlap_tokens: Required[int] + """The number of tokens that overlap between chunks. The default value is `400`. + + Note that the overlap must not exceed half of `max_chunk_size_tokens`. + """ + + max_chunk_size_tokens: Required[int] + """The maximum number of tokens in each chunk. + + The default value is `800`. The minimum value is `100` and the maximum value is + `4096`. + """ + + +class ThreadToolResourcesFileSearchVectorStoreChunkingStrategyStatic(TypedDict, total=False): + static: Required[ThreadToolResourcesFileSearchVectorStoreChunkingStrategyStaticStatic] + + type: Required[Literal["static"]] + """Always `static`.""" + + +ThreadToolResourcesFileSearchVectorStoreChunkingStrategy: TypeAlias = Union[ + ThreadToolResourcesFileSearchVectorStoreChunkingStrategyAuto, + ThreadToolResourcesFileSearchVectorStoreChunkingStrategyStatic, +] + + class ThreadToolResourcesFileSearchVectorStore(TypedDict, total=False): - chunking_strategy: FileChunkingStrategyParam + chunking_strategy: ThreadToolResourcesFileSearchVectorStoreChunkingStrategy """The chunking strategy used to chunk the file(s). - If not set, will use the `auto` strategy. Only applicable if `file_ids` is - non-empty. + If not set, will use the `auto` strategy. """ - file_ids: List[str] + file_ids: SequenceNotStr[str] """ A list of [file](https://platform.openai.com/docs/api-reference/files) IDs to - add to the vector store. There can be a maximum of 10000 files in a vector - store. + add to the vector store. For vector stores created before Nov 2025, there can be + a maximum of 10,000 files in a vector store. For vector stores created starting + in Nov 2025, the limit is 100,000,000 files. """ - metadata: object - """Set of 16 key-value pairs that can be attached to a vector store. + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. - This can be useful for storing additional information about the vector store in - a structured format. Keys can be a maximum of 64 characters long and values can - be a maxium of 512 characters long. + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ class ThreadToolResourcesFileSearch(TypedDict, total=False): - vector_store_ids: List[str] + vector_store_ids: SequenceNotStr[str] """ The [vector store](https://platform.openai.com/docs/api-reference/vector-stores/object) @@ -258,24 +309,36 @@ class ThreadToolResourcesFileSearch(TypedDict, total=False): class ThreadToolResources(TypedDict, total=False): + """ + A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs. + """ + code_interpreter: ThreadToolResourcesCodeInterpreter file_search: ThreadToolResourcesFileSearch class Thread(TypedDict, total=False): + """Options to create a new thread. + + If no thread is provided when running a + request, an empty thread will be created. + """ + messages: Iterable[ThreadMessage] """ A list of [messages](https://platform.openai.com/docs/api-reference/messages) to start the thread with. """ - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ tool_resources: Optional[ThreadToolResources] @@ -288,7 +351,7 @@ class Thread(TypedDict, total=False): class ToolResourcesCodeInterpreter(TypedDict, total=False): - file_ids: List[str] + file_ids: SequenceNotStr[str] """ A list of [file](https://platform.openai.com/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files @@ -297,7 +360,7 @@ class ToolResourcesCodeInterpreter(TypedDict, total=False): class ToolResourcesFileSearch(TypedDict, total=False): - vector_store_ids: List[str] + vector_store_ids: SequenceNotStr[str] """ The ID of the [vector store](https://platform.openai.com/docs/api-reference/vector-stores/object) @@ -307,15 +370,22 @@ class ToolResourcesFileSearch(TypedDict, total=False): class ToolResources(TypedDict, total=False): + """A set of resources that are used by the assistant's tools. + + The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs. + """ + code_interpreter: ToolResourcesCodeInterpreter file_search: ToolResourcesFileSearch -Tool: TypeAlias = Union[CodeInterpreterToolParam, FileSearchToolParam, FunctionToolParam] +class TruncationStrategy(TypedDict, total=False): + """Controls for how a thread will be truncated prior to the run. + Use this to control the initial context window of the run. + """ -class TruncationStrategy(TypedDict, total=False): type: Required[Literal["auto", "last_messages"]] """The truncation strategy to use for the thread. diff --git a/src/openai/types/beta/thread_create_params.py b/src/openai/types/beta/thread_create_params.py index 729164b481..b823d1c263 100644 --- a/src/openai/types/beta/thread_create_params.py +++ b/src/openai/types/beta/thread_create_params.py @@ -2,11 +2,12 @@ from __future__ import annotations -from typing import List, Union, Iterable, Optional +from typing import Union, Iterable, Optional from typing_extensions import Literal, Required, TypeAlias, TypedDict +from ..._types import SequenceNotStr +from ..shared_params.metadata import Metadata from .code_interpreter_tool_param import CodeInterpreterToolParam -from .file_chunking_strategy_param import FileChunkingStrategyParam from .threads.message_content_part_param import MessageContentPartParam __all__ = [ @@ -19,6 +20,10 @@ "ToolResourcesCodeInterpreter", "ToolResourcesFileSearch", "ToolResourcesFileSearchVectorStore", + "ToolResourcesFileSearchVectorStoreChunkingStrategy", + "ToolResourcesFileSearchVectorStoreChunkingStrategyAuto", + "ToolResourcesFileSearchVectorStoreChunkingStrategyStatic", + "ToolResourcesFileSearchVectorStoreChunkingStrategyStaticStatic", ] @@ -29,12 +34,14 @@ class ThreadCreateParams(TypedDict, total=False): start the thread with. """ - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ tool_resources: Optional[ToolResources] @@ -78,17 +85,19 @@ class Message(TypedDict, total=False): attachments: Optional[Iterable[MessageAttachment]] """A list of files attached to the message, and the tools they should be added to.""" - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ class ToolResourcesCodeInterpreter(TypedDict, total=False): - file_ids: List[str] + file_ids: SequenceNotStr[str] """ A list of [file](https://platform.openai.com/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files @@ -96,32 +105,71 @@ class ToolResourcesCodeInterpreter(TypedDict, total=False): """ +class ToolResourcesFileSearchVectorStoreChunkingStrategyAuto(TypedDict, total=False): + """The default strategy. + + This strategy currently uses a `max_chunk_size_tokens` of `800` and `chunk_overlap_tokens` of `400`. + """ + + type: Required[Literal["auto"]] + """Always `auto`.""" + + +class ToolResourcesFileSearchVectorStoreChunkingStrategyStaticStatic(TypedDict, total=False): + chunk_overlap_tokens: Required[int] + """The number of tokens that overlap between chunks. The default value is `400`. + + Note that the overlap must not exceed half of `max_chunk_size_tokens`. + """ + + max_chunk_size_tokens: Required[int] + """The maximum number of tokens in each chunk. + + The default value is `800`. The minimum value is `100` and the maximum value is + `4096`. + """ + + +class ToolResourcesFileSearchVectorStoreChunkingStrategyStatic(TypedDict, total=False): + static: Required[ToolResourcesFileSearchVectorStoreChunkingStrategyStaticStatic] + + type: Required[Literal["static"]] + """Always `static`.""" + + +ToolResourcesFileSearchVectorStoreChunkingStrategy: TypeAlias = Union[ + ToolResourcesFileSearchVectorStoreChunkingStrategyAuto, ToolResourcesFileSearchVectorStoreChunkingStrategyStatic +] + + class ToolResourcesFileSearchVectorStore(TypedDict, total=False): - chunking_strategy: FileChunkingStrategyParam + chunking_strategy: ToolResourcesFileSearchVectorStoreChunkingStrategy """The chunking strategy used to chunk the file(s). - If not set, will use the `auto` strategy. Only applicable if `file_ids` is - non-empty. + If not set, will use the `auto` strategy. """ - file_ids: List[str] + file_ids: SequenceNotStr[str] """ A list of [file](https://platform.openai.com/docs/api-reference/files) IDs to - add to the vector store. There can be a maximum of 10000 files in a vector - store. + add to the vector store. For vector stores created before Nov 2025, there can be + a maximum of 10,000 files in a vector store. For vector stores created starting + in Nov 2025, the limit is 100,000,000 files. """ - metadata: object - """Set of 16 key-value pairs that can be attached to a vector store. + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. - This can be useful for storing additional information about the vector store in - a structured format. Keys can be a maximum of 64 characters long and values can - be a maxium of 512 characters long. + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ class ToolResourcesFileSearch(TypedDict, total=False): - vector_store_ids: List[str] + vector_store_ids: SequenceNotStr[str] """ The [vector store](https://platform.openai.com/docs/api-reference/vector-stores/object) @@ -139,6 +187,10 @@ class ToolResourcesFileSearch(TypedDict, total=False): class ToolResources(TypedDict, total=False): + """ + A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs. + """ + code_interpreter: ToolResourcesCodeInterpreter file_search: ToolResourcesFileSearch diff --git a/src/openai/types/beta/thread_update_params.py b/src/openai/types/beta/thread_update_params.py index 7210ab77c9..e000edc05f 100644 --- a/src/openai/types/beta/thread_update_params.py +++ b/src/openai/types/beta/thread_update_params.py @@ -2,19 +2,24 @@ from __future__ import annotations -from typing import List, Optional +from typing import Optional from typing_extensions import TypedDict +from ..._types import SequenceNotStr +from ..shared_params.metadata import Metadata + __all__ = ["ThreadUpdateParams", "ToolResources", "ToolResourcesCodeInterpreter", "ToolResourcesFileSearch"] class ThreadUpdateParams(TypedDict, total=False): - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ tool_resources: Optional[ToolResources] @@ -27,7 +32,7 @@ class ThreadUpdateParams(TypedDict, total=False): class ToolResourcesCodeInterpreter(TypedDict, total=False): - file_ids: List[str] + file_ids: SequenceNotStr[str] """ A list of [file](https://platform.openai.com/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files @@ -36,7 +41,7 @@ class ToolResourcesCodeInterpreter(TypedDict, total=False): class ToolResourcesFileSearch(TypedDict, total=False): - vector_store_ids: List[str] + vector_store_ids: SequenceNotStr[str] """ The [vector store](https://platform.openai.com/docs/api-reference/vector-stores/object) @@ -46,6 +51,10 @@ class ToolResourcesFileSearch(TypedDict, total=False): class ToolResources(TypedDict, total=False): + """ + A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs. + """ + code_interpreter: ToolResourcesCodeInterpreter file_search: ToolResourcesFileSearch diff --git a/src/openai/types/beta/threads/file_citation_annotation.py b/src/openai/types/beta/threads/file_citation_annotation.py index c3085aed9b..929da0ac56 100644 --- a/src/openai/types/beta/threads/file_citation_annotation.py +++ b/src/openai/types/beta/threads/file_citation_annotation.py @@ -13,6 +13,10 @@ class FileCitation(BaseModel): class FileCitationAnnotation(BaseModel): + """ + A citation within the message that points to a specific quote from a specific File associated with the assistant or the message. Generated when the assistant uses the "file_search" tool to search files. + """ + end_index: int file_citation: FileCitation diff --git a/src/openai/types/beta/threads/file_citation_delta_annotation.py b/src/openai/types/beta/threads/file_citation_delta_annotation.py index b40c0d123e..591e322332 100644 --- a/src/openai/types/beta/threads/file_citation_delta_annotation.py +++ b/src/openai/types/beta/threads/file_citation_delta_annotation.py @@ -17,6 +17,10 @@ class FileCitation(BaseModel): class FileCitationDeltaAnnotation(BaseModel): + """ + A citation within the message that points to a specific quote from a specific File associated with the assistant or the message. Generated when the assistant uses the "file_search" tool to search files. + """ + index: int """The index of the annotation in the text content part.""" diff --git a/src/openai/types/beta/threads/file_path_annotation.py b/src/openai/types/beta/threads/file_path_annotation.py index 9812737ece..d3c144c2fc 100644 --- a/src/openai/types/beta/threads/file_path_annotation.py +++ b/src/openai/types/beta/threads/file_path_annotation.py @@ -13,6 +13,10 @@ class FilePath(BaseModel): class FilePathAnnotation(BaseModel): + """ + A URL for the file that's generated when the assistant used the `code_interpreter` tool to generate a file. + """ + end_index: int file_path: FilePath diff --git a/src/openai/types/beta/threads/file_path_delta_annotation.py b/src/openai/types/beta/threads/file_path_delta_annotation.py index 0cbb445e48..5416874749 100644 --- a/src/openai/types/beta/threads/file_path_delta_annotation.py +++ b/src/openai/types/beta/threads/file_path_delta_annotation.py @@ -14,6 +14,10 @@ class FilePath(BaseModel): class FilePathDeltaAnnotation(BaseModel): + """ + A URL for the file that's generated when the assistant used the `code_interpreter` tool to generate a file. + """ + index: int """The index of the annotation in the text content part.""" diff --git a/src/openai/types/beta/threads/image_file_content_block.py b/src/openai/types/beta/threads/image_file_content_block.py index a909999065..5a082cd488 100644 --- a/src/openai/types/beta/threads/image_file_content_block.py +++ b/src/openai/types/beta/threads/image_file_content_block.py @@ -9,6 +9,10 @@ class ImageFileContentBlock(BaseModel): + """ + References an image [File](https://platform.openai.com/docs/api-reference/files) in the content of a message. + """ + image_file: ImageFile type: Literal["image_file"] diff --git a/src/openai/types/beta/threads/image_file_content_block_param.py b/src/openai/types/beta/threads/image_file_content_block_param.py index 48d94bee36..da095a5ff6 100644 --- a/src/openai/types/beta/threads/image_file_content_block_param.py +++ b/src/openai/types/beta/threads/image_file_content_block_param.py @@ -10,6 +10,10 @@ class ImageFileContentBlockParam(TypedDict, total=False): + """ + References an image [File](https://platform.openai.com/docs/api-reference/files) in the content of a message. + """ + image_file: Required[ImageFileParam] type: Required[Literal["image_file"]] diff --git a/src/openai/types/beta/threads/image_file_delta_block.py b/src/openai/types/beta/threads/image_file_delta_block.py index 0a5a2e8a5f..ed17f7ff3b 100644 --- a/src/openai/types/beta/threads/image_file_delta_block.py +++ b/src/openai/types/beta/threads/image_file_delta_block.py @@ -10,6 +10,10 @@ class ImageFileDeltaBlock(BaseModel): + """ + References an image [File](https://platform.openai.com/docs/api-reference/files) in the content of a message. + """ + index: int """The index of the content part in the message.""" diff --git a/src/openai/types/beta/threads/image_url_content_block.py b/src/openai/types/beta/threads/image_url_content_block.py index 40a16c1df8..8dc1f16a7a 100644 --- a/src/openai/types/beta/threads/image_url_content_block.py +++ b/src/openai/types/beta/threads/image_url_content_block.py @@ -9,6 +9,8 @@ class ImageURLContentBlock(BaseModel): + """References an image URL in the content of a message.""" + image_url: ImageURL type: Literal["image_url"] diff --git a/src/openai/types/beta/threads/image_url_content_block_param.py b/src/openai/types/beta/threads/image_url_content_block_param.py index 585b926c58..a5c59e02c2 100644 --- a/src/openai/types/beta/threads/image_url_content_block_param.py +++ b/src/openai/types/beta/threads/image_url_content_block_param.py @@ -10,6 +10,8 @@ class ImageURLContentBlockParam(TypedDict, total=False): + """References an image URL in the content of a message.""" + image_url: Required[ImageURLParam] type: Required[Literal["image_url"]] diff --git a/src/openai/types/beta/threads/image_url_delta_block.py b/src/openai/types/beta/threads/image_url_delta_block.py index 5252da12dd..3128d8e709 100644 --- a/src/openai/types/beta/threads/image_url_delta_block.py +++ b/src/openai/types/beta/threads/image_url_delta_block.py @@ -10,6 +10,8 @@ class ImageURLDeltaBlock(BaseModel): + """References an image URL in the content of a message.""" + index: int """The index of the content part in the message.""" diff --git a/src/openai/types/beta/threads/message.py b/src/openai/types/beta/threads/message.py index 298a1d4273..fc7f73f091 100644 --- a/src/openai/types/beta/threads/message.py +++ b/src/openai/types/beta/threads/message.py @@ -5,6 +5,7 @@ from ...._models import BaseModel from .message_content import MessageContent +from ...shared.metadata import Metadata from ..code_interpreter_tool import CodeInterpreterTool __all__ = [ @@ -33,11 +34,17 @@ class Attachment(BaseModel): class IncompleteDetails(BaseModel): + """On an incomplete message, details about why the message is incomplete.""" + reason: Literal["content_filter", "max_tokens", "run_cancelled", "run_expired", "run_failed"] """The reason the message is incomplete.""" class Message(BaseModel): + """ + Represents a message within a [thread](https://platform.openai.com/docs/api-reference/threads). + """ + id: str """The identifier, which can be referenced in API endpoints.""" @@ -66,12 +73,14 @@ class Message(BaseModel): incomplete_details: Optional[IncompleteDetails] = None """On an incomplete message, details about why the message is incomplete.""" - metadata: Optional[object] = None + metadata: Optional[Metadata] = None """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ object: Literal["thread.message"] diff --git a/src/openai/types/beta/threads/message_create_params.py b/src/openai/types/beta/threads/message_create_params.py index 2b450deb5d..b52386824a 100644 --- a/src/openai/types/beta/threads/message_create_params.py +++ b/src/openai/types/beta/threads/message_create_params.py @@ -5,6 +5,7 @@ from typing import Union, Iterable, Optional from typing_extensions import Literal, Required, TypeAlias, TypedDict +from ...shared_params.metadata import Metadata from .message_content_part_param import MessageContentPartParam from ..code_interpreter_tool_param import CodeInterpreterToolParam @@ -27,12 +28,14 @@ class MessageCreateParams(TypedDict, total=False): attachments: Optional[Iterable[Attachment]] """A list of files attached to the message, and the tools they should be added to.""" - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ diff --git a/src/openai/types/beta/threads/message_delta.py b/src/openai/types/beta/threads/message_delta.py index ecd0dfe319..fdeebb3a12 100644 --- a/src/openai/types/beta/threads/message_delta.py +++ b/src/openai/types/beta/threads/message_delta.py @@ -10,6 +10,8 @@ class MessageDelta(BaseModel): + """The delta containing the fields that have changed on the Message.""" + content: Optional[List[MessageContentDelta]] = None """The content of the message in array of text and/or images.""" diff --git a/src/openai/types/beta/threads/message_delta_event.py b/src/openai/types/beta/threads/message_delta_event.py index 3811cef679..d5ba1e172d 100644 --- a/src/openai/types/beta/threads/message_delta_event.py +++ b/src/openai/types/beta/threads/message_delta_event.py @@ -9,6 +9,11 @@ class MessageDeltaEvent(BaseModel): + """Represents a message delta i.e. + + any changed fields on a message during streaming. + """ + id: str """The identifier of the message, which can be referenced in API endpoints.""" diff --git a/src/openai/types/beta/threads/message_list_params.py b/src/openai/types/beta/threads/message_list_params.py index 18c2442fb5..a7c22a66fb 100644 --- a/src/openai/types/beta/threads/message_list_params.py +++ b/src/openai/types/beta/threads/message_list_params.py @@ -21,7 +21,7 @@ class MessageListParams(TypedDict, total=False): """A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if - you make a list request and receive 100 objects, ending with obj_foo, your + you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list. """ diff --git a/src/openai/types/beta/threads/message_update_params.py b/src/openai/types/beta/threads/message_update_params.py index 7000f33122..bb078281e6 100644 --- a/src/openai/types/beta/threads/message_update_params.py +++ b/src/openai/types/beta/threads/message_update_params.py @@ -5,16 +5,20 @@ from typing import Optional from typing_extensions import Required, TypedDict +from ...shared_params.metadata import Metadata + __all__ = ["MessageUpdateParams"] class MessageUpdateParams(TypedDict, total=False): thread_id: Required[str] - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ diff --git a/src/openai/types/beta/threads/refusal_content_block.py b/src/openai/types/beta/threads/refusal_content_block.py index d54f948554..b4512b3ccb 100644 --- a/src/openai/types/beta/threads/refusal_content_block.py +++ b/src/openai/types/beta/threads/refusal_content_block.py @@ -8,6 +8,8 @@ class RefusalContentBlock(BaseModel): + """The refusal content generated by the assistant.""" + refusal: str type: Literal["refusal"] diff --git a/src/openai/types/beta/threads/refusal_delta_block.py b/src/openai/types/beta/threads/refusal_delta_block.py index dbd8e62697..85a1f08db1 100644 --- a/src/openai/types/beta/threads/refusal_delta_block.py +++ b/src/openai/types/beta/threads/refusal_delta_block.py @@ -9,6 +9,8 @@ class RefusalDeltaBlock(BaseModel): + """The refusal content that is part of a message.""" + index: int """The index of the refusal part in the message.""" diff --git a/src/openai/types/beta/threads/required_action_function_tool_call.py b/src/openai/types/beta/threads/required_action_function_tool_call.py index a24dfd068b..3cec8514ca 100644 --- a/src/openai/types/beta/threads/required_action_function_tool_call.py +++ b/src/openai/types/beta/threads/required_action_function_tool_call.py @@ -8,6 +8,8 @@ class Function(BaseModel): + """The function definition.""" + arguments: str """The arguments that the model expects you to pass to the function.""" @@ -16,6 +18,8 @@ class Function(BaseModel): class RequiredActionFunctionToolCall(BaseModel): + """Tool call objects""" + id: str """The ID of the tool call. diff --git a/src/openai/types/beta/threads/run.py b/src/openai/types/beta/threads/run.py index 5abc1de295..8a88fa1673 100644 --- a/src/openai/types/beta/threads/run.py +++ b/src/openai/types/beta/threads/run.py @@ -6,6 +6,7 @@ from ...._models import BaseModel from .run_status import RunStatus from ..assistant_tool import AssistantTool +from ...shared.metadata import Metadata from ..assistant_tool_choice_option import AssistantToolChoiceOption from ..assistant_response_format_option import AssistantResponseFormatOption from .required_action_function_tool_call import RequiredActionFunctionToolCall @@ -22,6 +23,11 @@ class IncompleteDetails(BaseModel): + """Details on why the run is incomplete. + + Will be `null` if the run is not incomplete. + """ + reason: Optional[Literal["max_completion_tokens", "max_prompt_tokens"]] = None """The reason why the run is incomplete. @@ -31,6 +37,8 @@ class IncompleteDetails(BaseModel): class LastError(BaseModel): + """The last error associated with this run. Will be `null` if there are no errors.""" + code: Literal["server_error", "rate_limit_exceeded", "invalid_prompt"] """One of `server_error`, `rate_limit_exceeded`, or `invalid_prompt`.""" @@ -39,11 +47,18 @@ class LastError(BaseModel): class RequiredActionSubmitToolOutputs(BaseModel): + """Details on the tool outputs needed for this run to continue.""" + tool_calls: List[RequiredActionFunctionToolCall] """A list of the relevant tool calls.""" class RequiredAction(BaseModel): + """Details on the action required to continue the run. + + Will be `null` if no action is required. + """ + submit_tool_outputs: RequiredActionSubmitToolOutputs """Details on the tool outputs needed for this run to continue.""" @@ -52,6 +67,11 @@ class RequiredAction(BaseModel): class TruncationStrategy(BaseModel): + """Controls for how a thread will be truncated prior to the run. + + Use this to control the initial context window of the run. + """ + type: Literal["auto", "last_messages"] """The truncation strategy to use for the thread. @@ -69,6 +89,11 @@ class TruncationStrategy(BaseModel): class Usage(BaseModel): + """Usage statistics related to the run. + + This value will be `null` if the run is not in a terminal state (i.e. `in_progress`, `queued`, etc.). + """ + completion_tokens: int """Number of completion tokens used over the course of the run.""" @@ -80,6 +105,10 @@ class Usage(BaseModel): class Run(BaseModel): + """ + Represents an execution run on a [thread](https://platform.openai.com/docs/api-reference/threads). + """ + id: str """The identifier, which can be referenced in API endpoints.""" @@ -133,12 +162,14 @@ class Run(BaseModel): of the run. """ - metadata: Optional[object] = None + metadata: Optional[Metadata] = None """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ model: str @@ -154,7 +185,7 @@ class Run(BaseModel): parallel_tool_calls: bool """ Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. """ @@ -167,8 +198,8 @@ class Run(BaseModel): response_format: Optional[AssistantResponseFormatOption] = None """Specifies the format that the model must output. - Compatible with [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + Compatible with [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -225,7 +256,7 @@ class Run(BaseModel): truncation_strategy: Optional[TruncationStrategy] = None """Controls for how a thread will be truncated prior to the run. - Use this to control the intial context window of the run. + Use this to control the initial context window of the run. """ usage: Optional[Usage] = None diff --git a/src/openai/types/beta/threads/run_create_params.py b/src/openai/types/beta/threads/run_create_params.py index 824cb1a041..376afc9aad 100644 --- a/src/openai/types/beta/threads/run_create_params.py +++ b/src/openai/types/beta/threads/run_create_params.py @@ -5,9 +5,11 @@ from typing import List, Union, Iterable, Optional from typing_extensions import Literal, Required, TypeAlias, TypedDict -from ...chat_model import ChatModel +from ...shared.chat_model import ChatModel from ..assistant_tool_param import AssistantToolParam from .runs.run_step_include import RunStepInclude +from ...shared_params.metadata import Metadata +from ...shared.reasoning_effort import ReasoningEffort from .message_content_part_param import MessageContentPartParam from ..code_interpreter_tool_param import CodeInterpreterToolParam from ..assistant_tool_choice_option_param import AssistantToolChoiceOptionParam @@ -41,7 +43,7 @@ class RunCreateParamsBase(TypedDict, total=False): search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. """ @@ -80,12 +82,14 @@ class RunCreateParamsBase(TypedDict, total=False): `incomplete_details` for more info. """ - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ model: Union[str, ChatModel, None] @@ -99,15 +103,32 @@ class RunCreateParamsBase(TypedDict, total=False): parallel_tool_calls: bool """ Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. """ + reasoning_effort: Optional[ReasoningEffort] + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + response_format: Optional[AssistantResponseFormatOptionParam] """Specifies the format that the model must output. - Compatible with [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4), + Compatible with [GPT-4o](https://platform.openai.com/docs/models#gpt-4o), + [GPT-4 Turbo](https://platform.openai.com/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured @@ -163,7 +184,7 @@ class RunCreateParamsBase(TypedDict, total=False): truncation_strategy: Optional[TruncationStrategy] """Controls for how a thread will be truncated prior to the run. - Use this to control the intial context window of the run. + Use this to control the initial context window of the run. """ @@ -199,16 +220,23 @@ class AdditionalMessage(TypedDict, total=False): attachments: Optional[Iterable[AdditionalMessageAttachment]] """A list of files attached to the message, and the tools they should be added to.""" - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ class TruncationStrategy(TypedDict, total=False): + """Controls for how a thread will be truncated prior to the run. + + Use this to control the initial context window of the run. + """ + type: Required[Literal["auto", "last_messages"]] """The truncation strategy to use for the thread. diff --git a/src/openai/types/beta/threads/run_list_params.py b/src/openai/types/beta/threads/run_list_params.py index 1e32bca4b4..fbea54f6f2 100644 --- a/src/openai/types/beta/threads/run_list_params.py +++ b/src/openai/types/beta/threads/run_list_params.py @@ -21,7 +21,7 @@ class RunListParams(TypedDict, total=False): """A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if - you make a list request and receive 100 objects, ending with obj_foo, your + you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list. """ diff --git a/src/openai/types/beta/threads/run_update_params.py b/src/openai/types/beta/threads/run_update_params.py index e595eac882..fbcbd3fb14 100644 --- a/src/openai/types/beta/threads/run_update_params.py +++ b/src/openai/types/beta/threads/run_update_params.py @@ -5,16 +5,20 @@ from typing import Optional from typing_extensions import Required, TypedDict +from ...shared_params.metadata import Metadata + __all__ = ["RunUpdateParams"] class RunUpdateParams(TypedDict, total=False): thread_id: Required[str] - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ diff --git a/src/openai/types/beta/threads/runs/code_interpreter_logs.py b/src/openai/types/beta/threads/runs/code_interpreter_logs.py index 0bf8c1dac2..722fd2b4c4 100644 --- a/src/openai/types/beta/threads/runs/code_interpreter_logs.py +++ b/src/openai/types/beta/threads/runs/code_interpreter_logs.py @@ -9,6 +9,8 @@ class CodeInterpreterLogs(BaseModel): + """Text output from the Code Interpreter tool call as part of a run step.""" + index: int """The index of the output in the outputs array.""" diff --git a/src/openai/types/beta/threads/runs/code_interpreter_tool_call.py b/src/openai/types/beta/threads/runs/code_interpreter_tool_call.py index e7df4e19c4..bc78b5fa3d 100644 --- a/src/openai/types/beta/threads/runs/code_interpreter_tool_call.py +++ b/src/openai/types/beta/threads/runs/code_interpreter_tool_call.py @@ -17,6 +17,8 @@ class CodeInterpreterOutputLogs(BaseModel): + """Text output from the Code Interpreter tool call as part of a run step.""" + logs: str """The text output from the Code Interpreter tool call.""" @@ -45,6 +47,8 @@ class CodeInterpreterOutputImage(BaseModel): class CodeInterpreter(BaseModel): + """The Code Interpreter tool call definition.""" + input: str """The input to the Code Interpreter tool call.""" @@ -57,6 +61,8 @@ class CodeInterpreter(BaseModel): class CodeInterpreterToolCall(BaseModel): + """Details of the Code Interpreter tool call the run step was involved in.""" + id: str """The ID of the tool call.""" diff --git a/src/openai/types/beta/threads/runs/code_interpreter_tool_call_delta.py b/src/openai/types/beta/threads/runs/code_interpreter_tool_call_delta.py index 9d7a1563cd..efedac795c 100644 --- a/src/openai/types/beta/threads/runs/code_interpreter_tool_call_delta.py +++ b/src/openai/types/beta/threads/runs/code_interpreter_tool_call_delta.py @@ -16,6 +16,8 @@ class CodeInterpreter(BaseModel): + """The Code Interpreter tool call definition.""" + input: Optional[str] = None """The input to the Code Interpreter tool call.""" @@ -28,6 +30,8 @@ class CodeInterpreter(BaseModel): class CodeInterpreterToolCallDelta(BaseModel): + """Details of the Code Interpreter tool call the run step was involved in.""" + index: int """The index of the tool call in the tool calls array.""" diff --git a/src/openai/types/beta/threads/runs/file_search_tool_call.py b/src/openai/types/beta/threads/runs/file_search_tool_call.py index da4d58dc37..291a93ec65 100644 --- a/src/openai/types/beta/threads/runs/file_search_tool_call.py +++ b/src/openai/types/beta/threads/runs/file_search_tool_call.py @@ -15,8 +15,13 @@ class FileSearchRankingOptions(BaseModel): - ranker: Literal["default_2024_08_21"] - """The ranker used for the file search.""" + """The ranking options for the file search.""" + + ranker: Literal["auto", "default_2024_08_21"] + """The ranker to use for the file search. + + If not specified will use the `auto` ranker. + """ score_threshold: float """The score threshold for the file search. @@ -34,6 +39,8 @@ class FileSearchResultContent(BaseModel): class FileSearchResult(BaseModel): + """A result instance of the file search.""" + file_id: str """The ID of the file that result was found in.""" @@ -54,6 +61,8 @@ class FileSearchResult(BaseModel): class FileSearch(BaseModel): + """For now, this is always going to be an empty object.""" + ranking_options: Optional[FileSearchRankingOptions] = None """The ranking options for the file search.""" diff --git a/src/openai/types/beta/threads/runs/function_tool_call.py b/src/openai/types/beta/threads/runs/function_tool_call.py index b1d354f894..dd0e22cfb1 100644 --- a/src/openai/types/beta/threads/runs/function_tool_call.py +++ b/src/openai/types/beta/threads/runs/function_tool_call.py @@ -9,6 +9,8 @@ class Function(BaseModel): + """The definition of the function that was called.""" + arguments: str """The arguments passed to the function.""" diff --git a/src/openai/types/beta/threads/runs/function_tool_call_delta.py b/src/openai/types/beta/threads/runs/function_tool_call_delta.py index faaf026f7f..4107e1b873 100644 --- a/src/openai/types/beta/threads/runs/function_tool_call_delta.py +++ b/src/openai/types/beta/threads/runs/function_tool_call_delta.py @@ -9,6 +9,8 @@ class Function(BaseModel): + """The definition of the function that was called.""" + arguments: Optional[str] = None """The arguments passed to the function.""" diff --git a/src/openai/types/beta/threads/runs/message_creation_step_details.py b/src/openai/types/beta/threads/runs/message_creation_step_details.py index 73439079d3..cd925b57ce 100644 --- a/src/openai/types/beta/threads/runs/message_creation_step_details.py +++ b/src/openai/types/beta/threads/runs/message_creation_step_details.py @@ -13,6 +13,8 @@ class MessageCreation(BaseModel): class MessageCreationStepDetails(BaseModel): + """Details of the message creation by the run step.""" + message_creation: MessageCreation type: Literal["message_creation"] diff --git a/src/openai/types/beta/threads/runs/run_step.py b/src/openai/types/beta/threads/runs/run_step.py index e3163c508b..97451229fc 100644 --- a/src/openai/types/beta/threads/runs/run_step.py +++ b/src/openai/types/beta/threads/runs/run_step.py @@ -5,6 +5,7 @@ from ....._utils import PropertyInfo from ....._models import BaseModel +from ....shared.metadata import Metadata from .tool_calls_step_details import ToolCallsStepDetails from .message_creation_step_details import MessageCreationStepDetails @@ -12,6 +13,11 @@ class LastError(BaseModel): + """The last error associated with this run step. + + Will be `null` if there are no errors. + """ + code: Literal["server_error", "rate_limit_exceeded"] """One of `server_error` or `rate_limit_exceeded`.""" @@ -25,6 +31,11 @@ class LastError(BaseModel): class Usage(BaseModel): + """Usage statistics related to the run step. + + This value will be `null` while the run step's status is `in_progress`. + """ + completion_tokens: int """Number of completion tokens used over the course of the run step.""" @@ -36,6 +47,8 @@ class Usage(BaseModel): class RunStep(BaseModel): + """Represents a step in execution of a run.""" + id: str """The identifier of the run step, which can be referenced in API endpoints.""" @@ -70,12 +83,14 @@ class RunStep(BaseModel): Will be `null` if there are no errors. """ - metadata: Optional[object] = None + metadata: Optional[Metadata] = None """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ object: Literal["thread.run.step"] diff --git a/src/openai/types/beta/threads/runs/run_step_delta.py b/src/openai/types/beta/threads/runs/run_step_delta.py index 1139088fb4..2ccb770d57 100644 --- a/src/openai/types/beta/threads/runs/run_step_delta.py +++ b/src/openai/types/beta/threads/runs/run_step_delta.py @@ -16,5 +16,7 @@ class RunStepDelta(BaseModel): + """The delta containing the fields that have changed on the run step.""" + step_details: Optional[StepDetails] = None """The details of the run step.""" diff --git a/src/openai/types/beta/threads/runs/run_step_delta_event.py b/src/openai/types/beta/threads/runs/run_step_delta_event.py index 7f3f92aabf..8f1c095ae4 100644 --- a/src/openai/types/beta/threads/runs/run_step_delta_event.py +++ b/src/openai/types/beta/threads/runs/run_step_delta_event.py @@ -9,6 +9,11 @@ class RunStepDeltaEvent(BaseModel): + """Represents a run step delta i.e. + + any changed fields on a run step during streaming. + """ + id: str """The identifier of the run step, which can be referenced in API endpoints.""" diff --git a/src/openai/types/beta/threads/runs/run_step_delta_message_delta.py b/src/openai/types/beta/threads/runs/run_step_delta_message_delta.py index f58ed3d96d..4b18277c18 100644 --- a/src/openai/types/beta/threads/runs/run_step_delta_message_delta.py +++ b/src/openai/types/beta/threads/runs/run_step_delta_message_delta.py @@ -14,6 +14,8 @@ class MessageCreation(BaseModel): class RunStepDeltaMessageDelta(BaseModel): + """Details of the message creation by the run step.""" + type: Literal["message_creation"] """Always `message_creation`.""" diff --git a/src/openai/types/beta/threads/runs/step_list_params.py b/src/openai/types/beta/threads/runs/step_list_params.py index 3931bd7e0c..a6be771d9f 100644 --- a/src/openai/types/beta/threads/runs/step_list_params.py +++ b/src/openai/types/beta/threads/runs/step_list_params.py @@ -26,7 +26,7 @@ class StepListParams(TypedDict, total=False): """A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if - you make a list request and receive 100 objects, ending with obj_foo, your + you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list. """ @@ -39,7 +39,7 @@ class StepListParams(TypedDict, total=False): search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. """ diff --git a/src/openai/types/beta/threads/runs/step_retrieve_params.py b/src/openai/types/beta/threads/runs/step_retrieve_params.py index 22c1c049f4..ecbb72edbd 100644 --- a/src/openai/types/beta/threads/runs/step_retrieve_params.py +++ b/src/openai/types/beta/threads/runs/step_retrieve_params.py @@ -23,6 +23,6 @@ class StepRetrieveParams(TypedDict, total=False): search result content. See the - [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search/customizing-file-search-settings) + [file search tool documentation](https://platform.openai.com/docs/assistants/tools/file-search#customizing-file-search-settings) for more information. """ diff --git a/src/openai/types/beta/threads/runs/tool_call_delta_object.py b/src/openai/types/beta/threads/runs/tool_call_delta_object.py index 189dce772c..dbd1096ad6 100644 --- a/src/openai/types/beta/threads/runs/tool_call_delta_object.py +++ b/src/openai/types/beta/threads/runs/tool_call_delta_object.py @@ -10,6 +10,8 @@ class ToolCallDeltaObject(BaseModel): + """Details of the tool call.""" + type: Literal["tool_calls"] """Always `tool_calls`.""" diff --git a/src/openai/types/beta/threads/runs/tool_calls_step_details.py b/src/openai/types/beta/threads/runs/tool_calls_step_details.py index a084d387c7..1f54a6aa71 100644 --- a/src/openai/types/beta/threads/runs/tool_calls_step_details.py +++ b/src/openai/types/beta/threads/runs/tool_calls_step_details.py @@ -10,6 +10,8 @@ class ToolCallsStepDetails(BaseModel): + """Details of the tool call.""" + tool_calls: List[ToolCall] """An array of tool calls the run step was involved in. diff --git a/src/openai/types/beta/threads/text_content_block.py b/src/openai/types/beta/threads/text_content_block.py index 3706d6b9d8..b9b1368a17 100644 --- a/src/openai/types/beta/threads/text_content_block.py +++ b/src/openai/types/beta/threads/text_content_block.py @@ -9,6 +9,8 @@ class TextContentBlock(BaseModel): + """The text content that is part of a message.""" + text: Text type: Literal["text"] diff --git a/src/openai/types/beta/threads/text_content_block_param.py b/src/openai/types/beta/threads/text_content_block_param.py index 6313de32cc..22c864438d 100644 --- a/src/openai/types/beta/threads/text_content_block_param.py +++ b/src/openai/types/beta/threads/text_content_block_param.py @@ -8,6 +8,8 @@ class TextContentBlockParam(TypedDict, total=False): + """The text content that is part of a message.""" + text: Required[str] """Text content to be sent to the model""" diff --git a/src/openai/types/beta/threads/text_delta_block.py b/src/openai/types/beta/threads/text_delta_block.py index 586116e0d6..a3d339ccad 100644 --- a/src/openai/types/beta/threads/text_delta_block.py +++ b/src/openai/types/beta/threads/text_delta_block.py @@ -10,6 +10,8 @@ class TextDeltaBlock(BaseModel): + """The text content that is part of a message.""" + index: int """The index of the content part in the message.""" diff --git a/src/openai/types/beta/vector_stores/file_batch_create_params.py b/src/openai/types/beta/vector_stores/file_batch_create_params.py deleted file mode 100644 index e42ea99cd1..0000000000 --- a/src/openai/types/beta/vector_stores/file_batch_create_params.py +++ /dev/null @@ -1,26 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List -from typing_extensions import Required, TypedDict - -from ..file_chunking_strategy_param import FileChunkingStrategyParam - -__all__ = ["FileBatchCreateParams"] - - -class FileBatchCreateParams(TypedDict, total=False): - file_ids: Required[List[str]] - """ - A list of [File](https://platform.openai.com/docs/api-reference/files) IDs that - the vector store should use. Useful for tools like `file_search` that can access - files. - """ - - chunking_strategy: FileChunkingStrategyParam - """The chunking strategy used to chunk the file(s). - - If not set, will use the `auto` strategy. Only applicable if `file_ids` is - non-empty. - """ diff --git a/src/openai/types/chat/__init__.py b/src/openai/types/chat/__init__.py index a5cf3734b8..50bdac7c65 100644 --- a/src/openai/types/chat/__init__.py +++ b/src/openai/types/chat/__init__.py @@ -4,25 +4,43 @@ from .chat_completion import ChatCompletion as ChatCompletion from .chat_completion_role import ChatCompletionRole as ChatCompletionRole +from .chat_completion_audio import ChatCompletionAudio as ChatCompletionAudio from .chat_completion_chunk import ChatCompletionChunk as ChatCompletionChunk +from .completion_list_params import CompletionListParams as CompletionListParams from .parsed_chat_completion import ( ParsedChoice as ParsedChoice, ParsedChatCompletion as ParsedChatCompletion, ParsedChatCompletionMessage as ParsedChatCompletionMessage, ) +from .chat_completion_deleted import ChatCompletionDeleted as ChatCompletionDeleted from .chat_completion_message import ChatCompletionMessage as ChatCompletionMessage +from .chat_completion_modality import ChatCompletionModality as ChatCompletionModality from .completion_create_params import CompletionCreateParams as CompletionCreateParams +from .completion_update_params import CompletionUpdateParams as CompletionUpdateParams from .parsed_function_tool_call import ( ParsedFunction as ParsedFunction, ParsedFunctionToolCall as ParsedFunctionToolCall, ) from .chat_completion_tool_param import ChatCompletionToolParam as ChatCompletionToolParam +from .chat_completion_audio_param import ChatCompletionAudioParam as ChatCompletionAudioParam +from .chat_completion_function_tool import ChatCompletionFunctionTool as ChatCompletionFunctionTool from .chat_completion_message_param import ChatCompletionMessageParam as ChatCompletionMessageParam +from .chat_completion_store_message import ChatCompletionStoreMessage as ChatCompletionStoreMessage from .chat_completion_token_logprob import ChatCompletionTokenLogprob as ChatCompletionTokenLogprob -from .chat_completion_message_tool_call import ChatCompletionMessageToolCall as ChatCompletionMessageToolCall +from .chat_completion_reasoning_effort import ChatCompletionReasoningEffort as ChatCompletionReasoningEffort +from .chat_completion_tool_union_param import ChatCompletionToolUnionParam as ChatCompletionToolUnionParam +from .chat_completion_content_part_text import ChatCompletionContentPartText as ChatCompletionContentPartText +from .chat_completion_custom_tool_param import ChatCompletionCustomToolParam as ChatCompletionCustomToolParam +from .chat_completion_message_tool_call import ( + ChatCompletionMessageToolCall as ChatCompletionMessageToolCall, + ChatCompletionMessageToolCallUnion as ChatCompletionMessageToolCallUnion, +) +from .chat_completion_content_part_image import ChatCompletionContentPartImage as ChatCompletionContentPartImage from .chat_completion_content_part_param import ChatCompletionContentPartParam as ChatCompletionContentPartParam from .chat_completion_tool_message_param import ChatCompletionToolMessageParam as ChatCompletionToolMessageParam from .chat_completion_user_message_param import ChatCompletionUserMessageParam as ChatCompletionUserMessageParam +from .chat_completion_allowed_tools_param import ChatCompletionAllowedToolsParam as ChatCompletionAllowedToolsParam +from .chat_completion_function_tool_param import ChatCompletionFunctionToolParam as ChatCompletionFunctionToolParam from .chat_completion_stream_options_param import ChatCompletionStreamOptionsParam as ChatCompletionStreamOptionsParam from .chat_completion_system_message_param import ChatCompletionSystemMessageParam as ChatCompletionSystemMessageParam from .chat_completion_function_message_param import ( @@ -34,6 +52,9 @@ from .chat_completion_content_part_text_param import ( ChatCompletionContentPartTextParam as ChatCompletionContentPartTextParam, ) +from .chat_completion_developer_message_param import ( + ChatCompletionDeveloperMessageParam as ChatCompletionDeveloperMessageParam, +) from .chat_completion_message_tool_call_param import ( ChatCompletionMessageToolCallParam as ChatCompletionMessageToolCallParam, ) @@ -43,12 +64,39 @@ from .chat_completion_content_part_image_param import ( ChatCompletionContentPartImageParam as ChatCompletionContentPartImageParam, ) +from .chat_completion_message_custom_tool_call import ( + ChatCompletionMessageCustomToolCall as ChatCompletionMessageCustomToolCall, +) +from .chat_completion_prediction_content_param import ( + ChatCompletionPredictionContentParam as ChatCompletionPredictionContentParam, +) from .chat_completion_tool_choice_option_param import ( ChatCompletionToolChoiceOptionParam as ChatCompletionToolChoiceOptionParam, ) +from .chat_completion_allowed_tool_choice_param import ( + ChatCompletionAllowedToolChoiceParam as ChatCompletionAllowedToolChoiceParam, +) from .chat_completion_content_part_refusal_param import ( ChatCompletionContentPartRefusalParam as ChatCompletionContentPartRefusalParam, ) from .chat_completion_function_call_option_param import ( ChatCompletionFunctionCallOptionParam as ChatCompletionFunctionCallOptionParam, ) +from .chat_completion_message_function_tool_call import ( + ChatCompletionMessageFunctionToolCall as ChatCompletionMessageFunctionToolCall, +) +from .chat_completion_message_tool_call_union_param import ( + ChatCompletionMessageToolCallUnionParam as ChatCompletionMessageToolCallUnionParam, +) +from .chat_completion_content_part_input_audio_param import ( + ChatCompletionContentPartInputAudioParam as ChatCompletionContentPartInputAudioParam, +) +from .chat_completion_message_custom_tool_call_param import ( + ChatCompletionMessageCustomToolCallParam as ChatCompletionMessageCustomToolCallParam, +) +from .chat_completion_named_tool_choice_custom_param import ( + ChatCompletionNamedToolChoiceCustomParam as ChatCompletionNamedToolChoiceCustomParam, +) +from .chat_completion_message_function_tool_call_param import ( + ChatCompletionMessageFunctionToolCallParam as ChatCompletionMessageFunctionToolCallParam, +) diff --git a/src/openai/types/chat/chat_completion.py b/src/openai/types/chat/chat_completion.py index 4b53e70890..2c4a78cd35 100644 --- a/src/openai/types/chat/chat_completion.py +++ b/src/openai/types/chat/chat_completion.py @@ -1,17 +1,33 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional -from typing_extensions import Literal +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias +from ..._utils import PropertyInfo from ..._models import BaseModel from ..completion_usage import CompletionUsage from .chat_completion_message import ChatCompletionMessage from .chat_completion_token_logprob import ChatCompletionTokenLogprob -__all__ = ["ChatCompletion", "Choice", "ChoiceLogprobs"] +__all__ = [ + "ChatCompletion", + "Choice", + "ChoiceLogprobs", + "Moderation", + "ModerationInput", + "ModerationInputModerationResults", + "ModerationInputModerationResultsResult", + "ModerationInputError", + "ModerationOutput", + "ModerationOutputModerationResults", + "ModerationOutputModerationResultsResult", + "ModerationOutputError", +] class ChoiceLogprobs(BaseModel): + """Log probability information for the choice.""" + content: Optional[List[ChatCompletionTokenLogprob]] = None """A list of message content tokens with log probability information.""" @@ -27,7 +43,8 @@ class Choice(BaseModel): sequence, `length` if the maximum number of tokens specified in the request was reached, `content_filter` if content was omitted due to a flag from our content filters, `tool_calls` if the model called a tool, or `function_call` - (deprecated) if the model called a function. + (deprecated) if the model called a function. Read the + [Model Spec](https://model-spec.openai.com/2025-12-18.html) for more. """ index: int @@ -40,7 +57,142 @@ class Choice(BaseModel): """A chat completion message generated by the model.""" +class ModerationInputModerationResultsResult(BaseModel): + """A moderation result produced for the response input or output.""" + + categories: Dict[str, bool] + """ + A dictionary of moderation categories to booleans, True if the input is flagged + under this category. + """ + + category_applied_input_types: Dict[str, List[Literal["text", "image"]]] + """Which modalities of input are reflected by the score for each category.""" + + category_scores: Dict[str, float] + """A dictionary of moderation categories to scores.""" + + flagged: bool + """A boolean indicating whether the content was flagged by any category.""" + + model: str + """The moderation model that produced this result.""" + + type: Literal["moderation_result"] + """ + The object type, which was always `moderation_result` for successful moderation + results. + """ + + +class ModerationInputModerationResults(BaseModel): + """Successful moderation results for the request input or generated output.""" + + model: str + """The moderation model used to generate the results.""" + + results: List[ModerationInputModerationResultsResult] + """A list of moderation results.""" + + type: Literal["moderation_results"] + """The object type, which is always `moderation_results`.""" + + +class ModerationInputError(BaseModel): + """An error produced while attempting moderation.""" + + code: str + """The error code.""" + + message: str + """The error message.""" + + type: Literal["error"] + """The object type, which is always `error`.""" + + +ModerationInput: TypeAlias = Annotated[ + Union[ModerationInputModerationResults, ModerationInputError], PropertyInfo(discriminator="type") +] + + +class ModerationOutputModerationResultsResult(BaseModel): + """A moderation result produced for the response input or output.""" + + categories: Dict[str, bool] + """ + A dictionary of moderation categories to booleans, True if the input is flagged + under this category. + """ + + category_applied_input_types: Dict[str, List[Literal["text", "image"]]] + """Which modalities of input are reflected by the score for each category.""" + + category_scores: Dict[str, float] + """A dictionary of moderation categories to scores.""" + + flagged: bool + """A boolean indicating whether the content was flagged by any category.""" + + model: str + """The moderation model that produced this result.""" + + type: Literal["moderation_result"] + """ + The object type, which was always `moderation_result` for successful moderation + results. + """ + + +class ModerationOutputModerationResults(BaseModel): + """Successful moderation results for the request input or generated output.""" + + model: str + """The moderation model used to generate the results.""" + + results: List[ModerationOutputModerationResultsResult] + """A list of moderation results.""" + + type: Literal["moderation_results"] + """The object type, which is always `moderation_results`.""" + + +class ModerationOutputError(BaseModel): + """An error produced while attempting moderation.""" + + code: str + """The error code.""" + + message: str + """The error message.""" + + type: Literal["error"] + """The object type, which is always `error`.""" + + +ModerationOutput: TypeAlias = Annotated[ + Union[ModerationOutputModerationResults, ModerationOutputError], PropertyInfo(discriminator="type") +] + + +class Moderation(BaseModel): + """ + Moderation results for the request input and generated output, if moderated + completions were requested. + """ + + input: ModerationInput + """Moderation for the request input.""" + + output: ModerationOutput + """Moderation for the generated output.""" + + class ChatCompletion(BaseModel): + """ + Represents a chat completion response returned by model, based on the provided input. + """ + id: str """A unique identifier for the chat completion.""" @@ -59,11 +211,29 @@ class ChatCompletion(BaseModel): object: Literal["chat.completion"] """The object type, which is always `chat.completion`.""" - service_tier: Optional[Literal["scale", "default"]] = None - """The service tier used for processing the request. + moderation: Optional[Moderation] = None + """ + Moderation results for the request input and generated output, if moderated + completions were requested. + """ - This field is only included if the `service_tier` parameter is specified in the - request. + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] = None + """Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. """ system_fingerprint: Optional[str] = None diff --git a/src/openai/types/chat/chat_completion_allowed_tool_choice_param.py b/src/openai/types/chat/chat_completion_allowed_tool_choice_param.py new file mode 100644 index 0000000000..c5ba21626d --- /dev/null +++ b/src/openai/types/chat/chat_completion_allowed_tool_choice_param.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .chat_completion_allowed_tools_param import ChatCompletionAllowedToolsParam + +__all__ = ["ChatCompletionAllowedToolChoiceParam"] + + +class ChatCompletionAllowedToolChoiceParam(TypedDict, total=False): + """Constrains the tools available to the model to a pre-defined set.""" + + allowed_tools: Required[ChatCompletionAllowedToolsParam] + """Constrains the tools available to the model to a pre-defined set.""" + + type: Required[Literal["allowed_tools"]] + """Allowed tool configuration type. Always `allowed_tools`.""" diff --git a/src/openai/types/chat/chat_completion_allowed_tools_param.py b/src/openai/types/chat/chat_completion_allowed_tools_param.py new file mode 100644 index 0000000000..ac31fcb543 --- /dev/null +++ b/src/openai/types/chat/chat_completion_allowed_tools_param.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Iterable +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionAllowedToolsParam"] + + +class ChatCompletionAllowedToolsParam(TypedDict, total=False): + """Constrains the tools available to the model to a pre-defined set.""" + + mode: Required[Literal["auto", "required"]] + """Constrains the tools available to the model to a pre-defined set. + + `auto` allows the model to pick from among the allowed tools and generate a + message. + + `required` requires the model to call one or more of the allowed tools. + """ + + tools: Required[Iterable[Dict[str, object]]] + """A list of tool definitions that the model should be allowed to call. + + For the Chat Completions API, the list of tool definitions might look like: + + ```json + [ + { "type": "function", "function": { "name": "get_weather" } }, + { "type": "function", "function": { "name": "get_time" } } + ] + ``` + """ diff --git a/src/openai/types/chat/chat_completion_assistant_message_param.py b/src/openai/types/chat/chat_completion_assistant_message_param.py index 2429d41d33..16a218438a 100644 --- a/src/openai/types/chat/chat_completion_assistant_message_param.py +++ b/src/openai/types/chat/chat_completion_assistant_message_param.py @@ -6,15 +6,31 @@ from typing_extensions import Literal, Required, TypeAlias, TypedDict from .chat_completion_content_part_text_param import ChatCompletionContentPartTextParam -from .chat_completion_message_tool_call_param import ChatCompletionMessageToolCallParam from .chat_completion_content_part_refusal_param import ChatCompletionContentPartRefusalParam +from .chat_completion_message_tool_call_union_param import ChatCompletionMessageToolCallUnionParam + +__all__ = ["ChatCompletionAssistantMessageParam", "Audio", "ContentArrayOfContentPart", "FunctionCall"] + + +class Audio(TypedDict, total=False): + """ + Data about a previous audio response from the model. + [Learn more](https://platform.openai.com/docs/guides/audio). + """ + + id: Required[str] + """Unique identifier for a previous audio response from the model.""" -__all__ = ["ChatCompletionAssistantMessageParam", "ContentArrayOfContentPart", "FunctionCall"] ContentArrayOfContentPart: TypeAlias = Union[ChatCompletionContentPartTextParam, ChatCompletionContentPartRefusalParam] class FunctionCall(TypedDict, total=False): + """Deprecated and replaced by `tool_calls`. + + The name and arguments of a function that should be called, as generated by the model. + """ + arguments: Required[str] """ The arguments to call the function with, as generated by the model in JSON @@ -28,9 +44,17 @@ class FunctionCall(TypedDict, total=False): class ChatCompletionAssistantMessageParam(TypedDict, total=False): + """Messages sent by the model in response to user messages.""" + role: Required[Literal["assistant"]] """The role of the messages author, in this case `assistant`.""" + audio: Optional[Audio] + """ + Data about a previous audio response from the model. + [Learn more](https://platform.openai.com/docs/guides/audio). + """ + content: Union[str, Iterable[ContentArrayOfContentPart], None] """The contents of the assistant message. @@ -54,5 +78,5 @@ class ChatCompletionAssistantMessageParam(TypedDict, total=False): refusal: Optional[str] """The refusal message by the assistant.""" - tool_calls: Iterable[ChatCompletionMessageToolCallParam] + tool_calls: Iterable[ChatCompletionMessageToolCallUnionParam] """The tool calls generated by the model, such as function calls.""" diff --git a/src/openai/types/chat/chat_completion_audio.py b/src/openai/types/chat/chat_completion_audio.py new file mode 100644 index 0000000000..df346d8c9d --- /dev/null +++ b/src/openai/types/chat/chat_completion_audio.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["ChatCompletionAudio"] + + +class ChatCompletionAudio(BaseModel): + """ + If the audio output modality is requested, this object contains data + about the audio response from the model. [Learn more](https://platform.openai.com/docs/guides/audio). + """ + + id: str + """Unique identifier for this audio response.""" + + data: str + """ + Base64 encoded audio bytes generated by the model, in the format specified in + the request. + """ + + expires_at: int + """ + The Unix timestamp (in seconds) for when this audio response will no longer be + accessible on the server for use in multi-turn conversations. + """ + + transcript: str + """Transcript of the audio generated by the model.""" diff --git a/src/openai/types/chat/chat_completion_audio_param.py b/src/openai/types/chat/chat_completion_audio_param.py new file mode 100644 index 0000000000..fe64ba49aa --- /dev/null +++ b/src/openai/types/chat/chat_completion_audio_param.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = ["ChatCompletionAudioParam", "Voice", "VoiceID"] + + +class VoiceID(TypedDict, total=False): + """Custom voice reference.""" + + id: Required[str] + """The custom voice ID, e.g. `voice_1234`.""" + + +Voice: TypeAlias = Union[ + str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"], VoiceID +] + + +class ChatCompletionAudioParam(TypedDict, total=False): + """Parameters for audio output. + + Required when audio output is requested with + `modalities: ["audio"]`. [Learn more](https://platform.openai.com/docs/guides/audio). + """ + + format: Required[Literal["wav", "aac", "mp3", "flac", "opus", "pcm16"]] + """Specifies the output audio format. + + Must be one of `wav`, `mp3`, `flac`, `opus`, or `pcm16`. + """ + + voice: Required[Voice] + """The voice the model uses to respond. + + Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, + `fable`, `nova`, `onyx`, `sage`, `shimmer`, `marin`, and `cedar`. You may also + provide a custom voice object with an `id`, for example + `{ "id": "voice_1234" }`. + """ diff --git a/src/openai/types/chat/chat_completion_chunk.py b/src/openai/types/chat/chat_completion_chunk.py index 9ec6dc4bdb..d983336fab 100644 --- a/src/openai/types/chat/chat_completion_chunk.py +++ b/src/openai/types/chat/chat_completion_chunk.py @@ -1,8 +1,9 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional -from typing_extensions import Literal +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias +from ..._utils import PropertyInfo from ..._models import BaseModel from ..completion_usage import CompletionUsage from .chat_completion_token_logprob import ChatCompletionTokenLogprob @@ -15,10 +16,24 @@ "ChoiceDeltaToolCall", "ChoiceDeltaToolCallFunction", "ChoiceLogprobs", + "Moderation", + "ModerationInput", + "ModerationInputModerationResults", + "ModerationInputModerationResultsResult", + "ModerationInputError", + "ModerationOutput", + "ModerationOutputModerationResults", + "ModerationOutputModerationResultsResult", + "ModerationOutputError", ] class ChoiceDeltaFunctionCall(BaseModel): + """Deprecated and replaced by `tool_calls`. + + The name and arguments of a function that should be called, as generated by the model. + """ + arguments: Optional[str] = None """ The arguments to call the function with, as generated by the model in JSON @@ -57,6 +72,8 @@ class ChoiceDeltaToolCall(BaseModel): class ChoiceDelta(BaseModel): + """A chat completion delta generated by streamed model responses.""" + content: Optional[str] = None """The contents of the chunk message.""" @@ -70,13 +87,15 @@ class ChoiceDelta(BaseModel): refusal: Optional[str] = None """The refusal message generated by the model.""" - role: Optional[Literal["system", "user", "assistant", "tool"]] = None + role: Optional[Literal["developer", "system", "user", "assistant", "tool"]] = None """The role of the author of this message.""" tool_calls: Optional[List[ChoiceDeltaToolCall]] = None class ChoiceLogprobs(BaseModel): + """Log probability information for the choice.""" + content: Optional[List[ChatCompletionTokenLogprob]] = None """A list of message content tokens with log probability information.""" @@ -105,7 +124,145 @@ class Choice(BaseModel): """Log probability information for the choice.""" +class ModerationInputModerationResultsResult(BaseModel): + """A moderation result produced for the response input or output.""" + + categories: Dict[str, bool] + """ + A dictionary of moderation categories to booleans, True if the input is flagged + under this category. + """ + + category_applied_input_types: Dict[str, List[Literal["text", "image"]]] + """Which modalities of input are reflected by the score for each category.""" + + category_scores: Dict[str, float] + """A dictionary of moderation categories to scores.""" + + flagged: bool + """A boolean indicating whether the content was flagged by any category.""" + + model: str + """The moderation model that produced this result.""" + + type: Literal["moderation_result"] + """ + The object type, which was always `moderation_result` for successful moderation + results. + """ + + +class ModerationInputModerationResults(BaseModel): + """Successful moderation results for the request input or generated output.""" + + model: str + """The moderation model used to generate the results.""" + + results: List[ModerationInputModerationResultsResult] + """A list of moderation results.""" + + type: Literal["moderation_results"] + """The object type, which is always `moderation_results`.""" + + +class ModerationInputError(BaseModel): + """An error produced while attempting moderation.""" + + code: str + """The error code.""" + + message: str + """The error message.""" + + type: Literal["error"] + """The object type, which is always `error`.""" + + +ModerationInput: TypeAlias = Annotated[ + Union[ModerationInputModerationResults, ModerationInputError], PropertyInfo(discriminator="type") +] + + +class ModerationOutputModerationResultsResult(BaseModel): + """A moderation result produced for the response input or output.""" + + categories: Dict[str, bool] + """ + A dictionary of moderation categories to booleans, True if the input is flagged + under this category. + """ + + category_applied_input_types: Dict[str, List[Literal["text", "image"]]] + """Which modalities of input are reflected by the score for each category.""" + + category_scores: Dict[str, float] + """A dictionary of moderation categories to scores.""" + + flagged: bool + """A boolean indicating whether the content was flagged by any category.""" + + model: str + """The moderation model that produced this result.""" + + type: Literal["moderation_result"] + """ + The object type, which was always `moderation_result` for successful moderation + results. + """ + + +class ModerationOutputModerationResults(BaseModel): + """Successful moderation results for the request input or generated output.""" + + model: str + """The moderation model used to generate the results.""" + + results: List[ModerationOutputModerationResultsResult] + """A list of moderation results.""" + + type: Literal["moderation_results"] + """The object type, which is always `moderation_results`.""" + + +class ModerationOutputError(BaseModel): + """An error produced while attempting moderation.""" + + code: str + """The error code.""" + + message: str + """The error message.""" + + type: Literal["error"] + """The object type, which is always `error`.""" + + +ModerationOutput: TypeAlias = Annotated[ + Union[ModerationOutputModerationResults, ModerationOutputError], PropertyInfo(discriminator="type") +] + + +class Moderation(BaseModel): + """Moderation results for the request input and generated output. + + Present + on the moderation chunk when moderated completions are requested. + """ + + input: ModerationInput + """Moderation for the request input.""" + + output: ModerationOutput + """Moderation for the generated output.""" + + class ChatCompletionChunk(BaseModel): + """ + Represents a streamed chunk of a chat completion response returned + by the model, based on the provided input. + [Learn more](https://platform.openai.com/docs/guides/streaming-responses). + """ + id: str """A unique identifier for the chat completion. Each chunk has the same ID.""" @@ -128,11 +285,29 @@ class ChatCompletionChunk(BaseModel): object: Literal["chat.completion.chunk"] """The object type, which is always `chat.completion.chunk`.""" - service_tier: Optional[Literal["scale", "default"]] = None - """The service tier used for processing the request. + moderation: Optional[Moderation] = None + """Moderation results for the request input and generated output. + + Present on the moderation chunk when moderated completions are requested. + """ - This field is only included if the `service_tier` parameter is specified in the - request. + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] = None + """Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. """ system_fingerprint: Optional[str] = None @@ -146,6 +321,9 @@ class ChatCompletionChunk(BaseModel): """ An optional field that will only be present when you set `stream_options: {"include_usage": true}` in your request. When present, it - contains a null value except for the last chunk which contains the token usage - statistics for the entire request. + contains a null value **except for the last chunk** which contains the token + usage statistics for the entire request. + + **NOTE:** If the stream is interrupted or cancelled, you may not receive the + final usage chunk which contains the total token usage for the request. """ diff --git a/src/openai/types/chat/chat_completion_content_part_image.py b/src/openai/types/chat/chat_completion_content_part_image.py new file mode 100644 index 0000000000..a636c51fb4 --- /dev/null +++ b/src/openai/types/chat/chat_completion_content_part_image.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ChatCompletionContentPartImage", "ImageURL"] + + +class ImageURL(BaseModel): + url: str + """Either a URL of the image or the base64 encoded image data.""" + + detail: Optional[Literal["auto", "low", "high"]] = None + """Specifies the detail level of the image. + + Learn more in the + [Vision guide](https://platform.openai.com/docs/guides/vision#low-or-high-fidelity-image-understanding). + """ + + +class ChatCompletionContentPartImage(BaseModel): + """Learn about [image inputs](https://platform.openai.com/docs/guides/vision).""" + + image_url: ImageURL + + type: Literal["image_url"] + """The type of the content part.""" diff --git a/src/openai/types/chat/chat_completion_content_part_image_param.py b/src/openai/types/chat/chat_completion_content_part_image_param.py index b1a186aa6d..a230a340a7 100644 --- a/src/openai/types/chat/chat_completion_content_part_image_param.py +++ b/src/openai/types/chat/chat_completion_content_part_image_param.py @@ -15,11 +15,13 @@ class ImageURL(TypedDict, total=False): """Specifies the detail level of the image. Learn more in the - [Vision guide](https://platform.openai.com/docs/guides/vision/low-or-high-fidelity-image-understanding). + [Vision guide](https://platform.openai.com/docs/guides/vision#low-or-high-fidelity-image-understanding). """ class ChatCompletionContentPartImageParam(TypedDict, total=False): + """Learn about [image inputs](https://platform.openai.com/docs/guides/vision).""" + image_url: Required[ImageURL] type: Required[Literal["image_url"]] diff --git a/src/openai/types/chat/chat_completion_content_part_input_audio_param.py b/src/openai/types/chat/chat_completion_content_part_input_audio_param.py new file mode 100644 index 0000000000..98d9e3c5eb --- /dev/null +++ b/src/openai/types/chat/chat_completion_content_part_input_audio_param.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionContentPartInputAudioParam", "InputAudio"] + + +class InputAudio(TypedDict, total=False): + data: Required[str] + """Base64 encoded audio data.""" + + format: Required[Literal["wav", "mp3"]] + """The format of the encoded audio data. Currently supports "wav" and "mp3".""" + + +class ChatCompletionContentPartInputAudioParam(TypedDict, total=False): + """Learn about [audio inputs](https://platform.openai.com/docs/guides/audio).""" + + input_audio: Required[InputAudio] + + type: Required[Literal["input_audio"]] + """The type of the content part. Always `input_audio`.""" diff --git a/src/openai/types/chat/chat_completion_content_part_param.py b/src/openai/types/chat/chat_completion_content_part_param.py index e0c6e480f2..b8c710a980 100644 --- a/src/openai/types/chat/chat_completion_content_part_param.py +++ b/src/openai/types/chat/chat_completion_content_part_param.py @@ -3,13 +3,43 @@ from __future__ import annotations from typing import Union -from typing_extensions import TypeAlias +from typing_extensions import Literal, Required, TypeAlias, TypedDict from .chat_completion_content_part_text_param import ChatCompletionContentPartTextParam from .chat_completion_content_part_image_param import ChatCompletionContentPartImageParam +from .chat_completion_content_part_input_audio_param import ChatCompletionContentPartInputAudioParam + +__all__ = ["ChatCompletionContentPartParam", "File", "FileFile"] + + +class FileFile(TypedDict, total=False): + file_data: str + """ + The base64 encoded file data, used when passing the file to the model as a + string. + """ + + file_id: str + """The ID of an uploaded file to use as input.""" + + filename: str + """The name of the file, used when passing the file to the model as a string.""" + + +class File(TypedDict, total=False): + """ + Learn about [file inputs](https://platform.openai.com/docs/guides/text) for text generation. + """ + + file: Required[FileFile] + + type: Required[Literal["file"]] + """The type of the content part. Always `file`.""" -__all__ = ["ChatCompletionContentPartParam"] ChatCompletionContentPartParam: TypeAlias = Union[ - ChatCompletionContentPartTextParam, ChatCompletionContentPartImageParam + ChatCompletionContentPartTextParam, + ChatCompletionContentPartImageParam, + ChatCompletionContentPartInputAudioParam, + File, ] diff --git a/src/openai/types/chat/chat_completion_content_part_text.py b/src/openai/types/chat/chat_completion_content_part_text.py new file mode 100644 index 0000000000..e6d1bf1ec0 --- /dev/null +++ b/src/openai/types/chat/chat_completion_content_part_text.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ChatCompletionContentPartText"] + + +class ChatCompletionContentPartText(BaseModel): + """ + Learn about [text inputs](https://platform.openai.com/docs/guides/text-generation). + """ + + text: str + """The text content.""" + + type: Literal["text"] + """The type of the content part.""" diff --git a/src/openai/types/chat/chat_completion_content_part_text_param.py b/src/openai/types/chat/chat_completion_content_part_text_param.py index a270744417..be69bf66fa 100644 --- a/src/openai/types/chat/chat_completion_content_part_text_param.py +++ b/src/openai/types/chat/chat_completion_content_part_text_param.py @@ -8,6 +8,10 @@ class ChatCompletionContentPartTextParam(TypedDict, total=False): + """ + Learn about [text inputs](https://platform.openai.com/docs/guides/text-generation). + """ + text: Required[str] """The text content.""" diff --git a/src/openai/types/chat/chat_completion_custom_tool_param.py b/src/openai/types/chat/chat_completion_custom_tool_param.py new file mode 100644 index 0000000000..d4f21ba0ca --- /dev/null +++ b/src/openai/types/chat/chat_completion_custom_tool_param.py @@ -0,0 +1,68 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = [ + "ChatCompletionCustomToolParam", + "Custom", + "CustomFormat", + "CustomFormatText", + "CustomFormatGrammar", + "CustomFormatGrammarGrammar", +] + + +class CustomFormatText(TypedDict, total=False): + """Unconstrained free-form text.""" + + type: Required[Literal["text"]] + """Unconstrained text format. Always `text`.""" + + +class CustomFormatGrammarGrammar(TypedDict, total=False): + """Your chosen grammar.""" + + definition: Required[str] + """The grammar definition.""" + + syntax: Required[Literal["lark", "regex"]] + """The syntax of the grammar definition. One of `lark` or `regex`.""" + + +class CustomFormatGrammar(TypedDict, total=False): + """A grammar defined by the user.""" + + grammar: Required[CustomFormatGrammarGrammar] + """Your chosen grammar.""" + + type: Required[Literal["grammar"]] + """Grammar format. Always `grammar`.""" + + +CustomFormat: TypeAlias = Union[CustomFormatText, CustomFormatGrammar] + + +class Custom(TypedDict, total=False): + """Properties of the custom tool.""" + + name: Required[str] + """The name of the custom tool, used to identify it in tool calls.""" + + description: str + """Optional description of the custom tool, used to provide more context.""" + + format: CustomFormat + """The input format for the custom tool. Default is unconstrained text.""" + + +class ChatCompletionCustomToolParam(TypedDict, total=False): + """A custom tool that processes input using a specified format.""" + + custom: Required[Custom] + """Properties of the custom tool.""" + + type: Required[Literal["custom"]] + """The type of the custom tool. Always `custom`.""" diff --git a/src/openai/types/chat/chat_completion_deleted.py b/src/openai/types/chat/chat_completion_deleted.py new file mode 100644 index 0000000000..0a541cb23d --- /dev/null +++ b/src/openai/types/chat/chat_completion_deleted.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ChatCompletionDeleted"] + + +class ChatCompletionDeleted(BaseModel): + id: str + """The ID of the chat completion that was deleted.""" + + deleted: bool + """Whether the chat completion was deleted.""" + + object: Literal["chat.completion.deleted"] + """The type of object being deleted.""" diff --git a/src/openai/types/chat/chat_completion_developer_message_param.py b/src/openai/types/chat/chat_completion_developer_message_param.py new file mode 100644 index 0000000000..94fb3359f6 --- /dev/null +++ b/src/openai/types/chat/chat_completion_developer_message_param.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypedDict + +from .chat_completion_content_part_text_param import ChatCompletionContentPartTextParam + +__all__ = ["ChatCompletionDeveloperMessageParam"] + + +class ChatCompletionDeveloperMessageParam(TypedDict, total=False): + """ + Developer-provided instructions that the model should follow, regardless of + messages sent by the user. With o1 models and newer, `developer` messages + replace the previous `system` messages. + """ + + content: Required[Union[str, Iterable[ChatCompletionContentPartTextParam]]] + """The contents of the developer message.""" + + role: Required[Literal["developer"]] + """The role of the messages author, in this case `developer`.""" + + name: str + """An optional name for the participant. + + Provides the model information to differentiate between participants of the same + role. + """ diff --git a/src/openai/types/chat/chat_completion_function_call_option_param.py b/src/openai/types/chat/chat_completion_function_call_option_param.py index 2bc014af7a..b1ca37bf58 100644 --- a/src/openai/types/chat/chat_completion_function_call_option_param.py +++ b/src/openai/types/chat/chat_completion_function_call_option_param.py @@ -8,5 +8,9 @@ class ChatCompletionFunctionCallOptionParam(TypedDict, total=False): + """ + Specifying a particular function via `{"name": "my_function"}` forces the model to call that function. + """ + name: Required[str] """The name of the function to call.""" diff --git a/src/openai/types/chat/chat_completion_function_tool.py b/src/openai/types/chat/chat_completion_function_tool.py new file mode 100644 index 0000000000..5d43a1e836 --- /dev/null +++ b/src/openai/types/chat/chat_completion_function_tool.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from ..shared.function_definition import FunctionDefinition + +__all__ = ["ChatCompletionFunctionTool"] + + +class ChatCompletionFunctionTool(BaseModel): + """A function tool that can be used to generate a response.""" + + function: FunctionDefinition + + type: Literal["function"] + """The type of the tool. Currently, only `function` is supported.""" diff --git a/src/openai/types/chat/chat_completion_function_tool_param.py b/src/openai/types/chat/chat_completion_function_tool_param.py new file mode 100644 index 0000000000..d336e8c08c --- /dev/null +++ b/src/openai/types/chat/chat_completion_function_tool_param.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from ..shared_params.function_definition import FunctionDefinition + +__all__ = ["ChatCompletionFunctionToolParam"] + + +class ChatCompletionFunctionToolParam(TypedDict, total=False): + """A function tool that can be used to generate a response.""" + + function: Required[FunctionDefinition] + + type: Required[Literal["function"]] + """The type of the tool. Currently, only `function` is supported.""" diff --git a/src/openai/types/chat/chat_completion_message.py b/src/openai/types/chat/chat_completion_message.py index 492bb68c85..3f88f776b9 100644 --- a/src/openai/types/chat/chat_completion_message.py +++ b/src/openai/types/chat/chat_completion_message.py @@ -4,12 +4,44 @@ from typing_extensions import Literal from ..._models import BaseModel -from .chat_completion_message_tool_call import ChatCompletionMessageToolCall +from .chat_completion_audio import ChatCompletionAudio +from .chat_completion_message_tool_call import ChatCompletionMessageToolCallUnion -__all__ = ["ChatCompletionMessage", "FunctionCall"] +__all__ = ["ChatCompletionMessage", "Annotation", "AnnotationURLCitation", "FunctionCall"] + + +class AnnotationURLCitation(BaseModel): + """A URL citation when using web search.""" + + end_index: int + """The index of the last character of the URL citation in the message.""" + + start_index: int + """The index of the first character of the URL citation in the message.""" + + title: str + """The title of the web resource.""" + + url: str + """The URL of the web resource.""" + + +class Annotation(BaseModel): + """A URL citation when using web search.""" + + type: Literal["url_citation"] + """The type of the URL citation. Always `url_citation`.""" + + url_citation: AnnotationURLCitation + """A URL citation when using web search.""" class FunctionCall(BaseModel): + """Deprecated and replaced by `tool_calls`. + + The name and arguments of a function that should be called, as generated by the model. + """ + arguments: str """ The arguments to call the function with, as generated by the model in JSON @@ -23,6 +55,8 @@ class FunctionCall(BaseModel): class ChatCompletionMessage(BaseModel): + """A chat completion message generated by the model.""" + content: Optional[str] = None """The contents of the message.""" @@ -32,6 +66,19 @@ class ChatCompletionMessage(BaseModel): role: Literal["assistant"] """The role of the author of this message.""" + annotations: Optional[List[Annotation]] = None + """ + Annotations for the message, when applicable, as when using the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). + """ + + audio: Optional[ChatCompletionAudio] = None + """ + If the audio output modality is requested, this object contains data about the + audio response from the model. + [Learn more](https://platform.openai.com/docs/guides/audio). + """ + function_call: Optional[FunctionCall] = None """Deprecated and replaced by `tool_calls`. @@ -39,5 +86,5 @@ class ChatCompletionMessage(BaseModel): model. """ - tool_calls: Optional[List[ChatCompletionMessageToolCall]] = None + tool_calls: Optional[List[ChatCompletionMessageToolCallUnion]] = None """The tool calls generated by the model, such as function calls.""" diff --git a/src/openai/types/chat/chat_completion_message_custom_tool_call.py b/src/openai/types/chat/chat_completion_message_custom_tool_call.py new file mode 100644 index 0000000000..9542d8b924 --- /dev/null +++ b/src/openai/types/chat/chat_completion_message_custom_tool_call.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ChatCompletionMessageCustomToolCall", "Custom"] + + +class Custom(BaseModel): + """The custom tool that the model called.""" + + input: str + """The input for the custom tool call generated by the model.""" + + name: str + """The name of the custom tool to call.""" + + +class ChatCompletionMessageCustomToolCall(BaseModel): + """A call to a custom tool created by the model.""" + + id: str + """The ID of the tool call.""" + + custom: Custom + """The custom tool that the model called.""" + + type: Literal["custom"] + """The type of the tool. Always `custom`.""" diff --git a/src/openai/types/chat/chat_completion_message_custom_tool_call_param.py b/src/openai/types/chat/chat_completion_message_custom_tool_call_param.py new file mode 100644 index 0000000000..3d03f0a93c --- /dev/null +++ b/src/openai/types/chat/chat_completion_message_custom_tool_call_param.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionMessageCustomToolCallParam", "Custom"] + + +class Custom(TypedDict, total=False): + """The custom tool that the model called.""" + + input: Required[str] + """The input for the custom tool call generated by the model.""" + + name: Required[str] + """The name of the custom tool to call.""" + + +class ChatCompletionMessageCustomToolCallParam(TypedDict, total=False): + """A call to a custom tool created by the model.""" + + id: Required[str] + """The ID of the tool call.""" + + custom: Required[Custom] + """The custom tool that the model called.""" + + type: Required[Literal["custom"]] + """The type of the tool. Always `custom`.""" diff --git a/src/openai/types/chat/chat_completion_message_function_tool_call.py b/src/openai/types/chat/chat_completion_message_function_tool_call.py new file mode 100644 index 0000000000..e7278b923c --- /dev/null +++ b/src/openai/types/chat/chat_completion_message_function_tool_call.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ChatCompletionMessageFunctionToolCall", "Function"] + + +class Function(BaseModel): + """The function that the model called.""" + + arguments: str + """ + The arguments to call the function with, as generated by the model in JSON + format. Note that the model does not always generate valid JSON, and may + hallucinate parameters not defined by your function schema. Validate the + arguments in your code before calling your function. + """ + + name: str + """The name of the function to call.""" + + +class ChatCompletionMessageFunctionToolCall(BaseModel): + """A call to a function tool created by the model.""" + + id: str + """The ID of the tool call.""" + + function: Function + """The function that the model called.""" + + type: Literal["function"] + """The type of the tool. Currently, only `function` is supported.""" diff --git a/src/openai/types/chat/chat_completion_message_function_tool_call_param.py b/src/openai/types/chat/chat_completion_message_function_tool_call_param.py new file mode 100644 index 0000000000..a8094ea63a --- /dev/null +++ b/src/openai/types/chat/chat_completion_message_function_tool_call_param.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionMessageFunctionToolCallParam", "Function"] + + +class Function(TypedDict, total=False): + """The function that the model called.""" + + arguments: Required[str] + """ + The arguments to call the function with, as generated by the model in JSON + format. Note that the model does not always generate valid JSON, and may + hallucinate parameters not defined by your function schema. Validate the + arguments in your code before calling your function. + """ + + name: Required[str] + """The name of the function to call.""" + + +class ChatCompletionMessageFunctionToolCallParam(TypedDict, total=False): + """A call to a function tool created by the model.""" + + id: Required[str] + """The ID of the tool call.""" + + function: Required[Function] + """The function that the model called.""" + + type: Required[Literal["function"]] + """The type of the tool. Currently, only `function` is supported.""" diff --git a/src/openai/types/chat/chat_completion_message_param.py b/src/openai/types/chat/chat_completion_message_param.py index ec65d94cae..942da24304 100644 --- a/src/openai/types/chat/chat_completion_message_param.py +++ b/src/openai/types/chat/chat_completion_message_param.py @@ -10,10 +10,12 @@ from .chat_completion_system_message_param import ChatCompletionSystemMessageParam from .chat_completion_function_message_param import ChatCompletionFunctionMessageParam from .chat_completion_assistant_message_param import ChatCompletionAssistantMessageParam +from .chat_completion_developer_message_param import ChatCompletionDeveloperMessageParam __all__ = ["ChatCompletionMessageParam"] ChatCompletionMessageParam: TypeAlias = Union[ + ChatCompletionDeveloperMessageParam, ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam, ChatCompletionAssistantMessageParam, diff --git a/src/openai/types/chat/chat_completion_message_tool_call.py b/src/openai/types/chat/chat_completion_message_tool_call.py index 4fec667096..71ac63f58e 100644 --- a/src/openai/types/chat/chat_completion_message_tool_call.py +++ b/src/openai/types/chat/chat_completion_message_tool_call.py @@ -1,31 +1,17 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing_extensions import Literal +from typing import Union +from typing_extensions import Annotated, TypeAlias -from ..._models import BaseModel +from ..._utils import PropertyInfo +from .chat_completion_message_custom_tool_call import ChatCompletionMessageCustomToolCall +from .chat_completion_message_function_tool_call import Function as Function, ChatCompletionMessageFunctionToolCall -__all__ = ["ChatCompletionMessageToolCall", "Function"] +__all__ = ["Function", "ChatCompletionMessageToolCallUnion"] +ChatCompletionMessageToolCallUnion: TypeAlias = Annotated[ + Union[ChatCompletionMessageFunctionToolCall, ChatCompletionMessageCustomToolCall], + PropertyInfo(discriminator="type"), +] -class Function(BaseModel): - arguments: str - """ - The arguments to call the function with, as generated by the model in JSON - format. Note that the model does not always generate valid JSON, and may - hallucinate parameters not defined by your function schema. Validate the - arguments in your code before calling your function. - """ - - name: str - """The name of the function to call.""" - - -class ChatCompletionMessageToolCall(BaseModel): - id: str - """The ID of the tool call.""" - - function: Function - """The function that the model called.""" - - type: Literal["function"] - """The type of the tool. Currently, only `function` is supported.""" +ChatCompletionMessageToolCall: TypeAlias = ChatCompletionMessageFunctionToolCall diff --git a/src/openai/types/chat/chat_completion_message_tool_call_param.py b/src/openai/types/chat/chat_completion_message_tool_call_param.py index f616c363d0..6baa1b57ab 100644 --- a/src/openai/types/chat/chat_completion_message_tool_call_param.py +++ b/src/openai/types/chat/chat_completion_message_tool_call_param.py @@ -2,30 +2,13 @@ from __future__ import annotations -from typing_extensions import Literal, Required, TypedDict +from typing_extensions import TypeAlias -__all__ = ["ChatCompletionMessageToolCallParam", "Function"] - - -class Function(TypedDict, total=False): - arguments: Required[str] - """ - The arguments to call the function with, as generated by the model in JSON - format. Note that the model does not always generate valid JSON, and may - hallucinate parameters not defined by your function schema. Validate the - arguments in your code before calling your function. - """ - - name: Required[str] - """The name of the function to call.""" +from .chat_completion_message_function_tool_call_param import ( + Function as Function, + ChatCompletionMessageFunctionToolCallParam, +) +__all__ = ["ChatCompletionMessageToolCallParam", "Function"] -class ChatCompletionMessageToolCallParam(TypedDict, total=False): - id: Required[str] - """The ID of the tool call.""" - - function: Required[Function] - """The function that the model called.""" - - type: Required[Literal["function"]] - """The type of the tool. Currently, only `function` is supported.""" +ChatCompletionMessageToolCallParam: TypeAlias = ChatCompletionMessageFunctionToolCallParam diff --git a/src/openai/types/chat/chat_completion_message_tool_call_union_param.py b/src/openai/types/chat/chat_completion_message_tool_call_union_param.py new file mode 100644 index 0000000000..fcca9bb116 --- /dev/null +++ b/src/openai/types/chat/chat_completion_message_tool_call_union_param.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypeAlias + +from .chat_completion_message_custom_tool_call_param import ChatCompletionMessageCustomToolCallParam +from .chat_completion_message_function_tool_call_param import ChatCompletionMessageFunctionToolCallParam + +__all__ = ["ChatCompletionMessageToolCallUnionParam"] + +ChatCompletionMessageToolCallUnionParam: TypeAlias = Union[ + ChatCompletionMessageFunctionToolCallParam, ChatCompletionMessageCustomToolCallParam +] diff --git a/src/openai/types/chat/chat_completion_modality.py b/src/openai/types/chat/chat_completion_modality.py new file mode 100644 index 0000000000..8e3c145979 --- /dev/null +++ b/src/openai/types/chat/chat_completion_modality.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ChatCompletionModality"] + +ChatCompletionModality: TypeAlias = Literal["text", "audio"] diff --git a/src/openai/types/chat/chat_completion_named_tool_choice_custom_param.py b/src/openai/types/chat/chat_completion_named_tool_choice_custom_param.py new file mode 100644 index 0000000000..147fb87965 --- /dev/null +++ b/src/openai/types/chat/chat_completion_named_tool_choice_custom_param.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionNamedToolChoiceCustomParam", "Custom"] + + +class Custom(TypedDict, total=False): + name: Required[str] + """The name of the custom tool to call.""" + + +class ChatCompletionNamedToolChoiceCustomParam(TypedDict, total=False): + """Specifies a tool the model should use. + + Use to force the model to call a specific custom tool. + """ + + custom: Required[Custom] + + type: Required[Literal["custom"]] + """For custom tool calling, the type is always `custom`.""" diff --git a/src/openai/types/chat/chat_completion_named_tool_choice_param.py b/src/openai/types/chat/chat_completion_named_tool_choice_param.py index 369f8b42dd..f684fcea5e 100644 --- a/src/openai/types/chat/chat_completion_named_tool_choice_param.py +++ b/src/openai/types/chat/chat_completion_named_tool_choice_param.py @@ -13,7 +13,12 @@ class Function(TypedDict, total=False): class ChatCompletionNamedToolChoiceParam(TypedDict, total=False): + """Specifies a tool the model should use. + + Use to force the model to call a specific function. + """ + function: Required[Function] type: Required[Literal["function"]] - """The type of the tool. Currently, only `function` is supported.""" + """For function calling, the type is always `function`.""" diff --git a/src/openai/types/chat/chat_completion_prediction_content_param.py b/src/openai/types/chat/chat_completion_prediction_content_param.py new file mode 100644 index 0000000000..6184a314b5 --- /dev/null +++ b/src/openai/types/chat/chat_completion_prediction_content_param.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypedDict + +from .chat_completion_content_part_text_param import ChatCompletionContentPartTextParam + +__all__ = ["ChatCompletionPredictionContentParam"] + + +class ChatCompletionPredictionContentParam(TypedDict, total=False): + """ + Static predicted output content, such as the content of a text file that is + being regenerated. + """ + + content: Required[Union[str, Iterable[ChatCompletionContentPartTextParam]]] + """ + The content that should be matched when generating a model response. If + generated tokens would match this content, the entire model response can be + returned much more quickly. + """ + + type: Required[Literal["content"]] + """The type of the predicted content you want to provide. + + This type is currently always `content`. + """ diff --git a/src/openai/types/chat/chat_completion_reasoning_effort.py b/src/openai/types/chat/chat_completion_reasoning_effort.py new file mode 100644 index 0000000000..42a980c5b8 --- /dev/null +++ b/src/openai/types/chat/chat_completion_reasoning_effort.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..shared.reasoning_effort import ReasoningEffort + +__all__ = ["ChatCompletionReasoningEffort"] + +ChatCompletionReasoningEffort = ReasoningEffort diff --git a/src/openai/types/chat/chat_completion_role.py b/src/openai/types/chat/chat_completion_role.py index c2ebef74c8..3ec5e9ad87 100644 --- a/src/openai/types/chat/chat_completion_role.py +++ b/src/openai/types/chat/chat_completion_role.py @@ -4,4 +4,4 @@ __all__ = ["ChatCompletionRole"] -ChatCompletionRole: TypeAlias = Literal["system", "user", "assistant", "tool", "function"] +ChatCompletionRole: TypeAlias = Literal["developer", "system", "user", "assistant", "tool", "function"] diff --git a/src/openai/types/chat/chat_completion_store_message.py b/src/openai/types/chat/chat_completion_store_message.py new file mode 100644 index 0000000000..6a805cce76 --- /dev/null +++ b/src/openai/types/chat/chat_completion_store_message.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import TypeAlias + +from .chat_completion_message import ChatCompletionMessage +from .chat_completion_content_part_text import ChatCompletionContentPartText +from .chat_completion_content_part_image import ChatCompletionContentPartImage + +__all__ = ["ChatCompletionStoreMessage", "ChatCompletionStoreMessageContentPart"] + +ChatCompletionStoreMessageContentPart: TypeAlias = Union[ChatCompletionContentPartText, ChatCompletionContentPartImage] + + +class ChatCompletionStoreMessage(ChatCompletionMessage): + """A chat completion message generated by the model.""" + + id: str + """The identifier of the chat message.""" + + content_parts: Optional[List[ChatCompletionStoreMessageContentPart]] = None + """ + If a content parts array was provided, this is an array of `text` and + `image_url` parts. Otherwise, null. + """ diff --git a/src/openai/types/chat/chat_completion_stream_options_param.py b/src/openai/types/chat/chat_completion_stream_options_param.py index fbf7291821..9b881fff02 100644 --- a/src/openai/types/chat/chat_completion_stream_options_param.py +++ b/src/openai/types/chat/chat_completion_stream_options_param.py @@ -8,10 +8,26 @@ class ChatCompletionStreamOptionsParam(TypedDict, total=False): + """Options for streaming response. Only set this when you set `stream: true`.""" + + include_obfuscation: bool + """When true, stream obfuscation will be enabled. + + Stream obfuscation adds random characters to an `obfuscation` field on streaming + delta events to normalize payload sizes as a mitigation to certain side-channel + attacks. These obfuscation fields are included by default, but add a small + amount of overhead to the data stream. You can set `include_obfuscation` to + false to optimize for bandwidth if you trust the network links between your + application and the OpenAI API. + """ + include_usage: bool """If set, an additional chunk will be streamed before the `data: [DONE]` message. The `usage` field on this chunk shows the token usage statistics for the entire - request, and the `choices` field will always be an empty array. All other chunks - will also include a `usage` field, but with a null value. + request, and the `choices` field will always be an empty array. + + All other chunks will also include a `usage` field, but with a null value. + **NOTE:** If the stream is interrupted, you may not receive the final usage + chunk which contains the total token usage for the request. """ diff --git a/src/openai/types/chat/chat_completion_system_message_param.py b/src/openai/types/chat/chat_completion_system_message_param.py index 172ccea09e..9dcc5e07f9 100644 --- a/src/openai/types/chat/chat_completion_system_message_param.py +++ b/src/openai/types/chat/chat_completion_system_message_param.py @@ -11,6 +11,12 @@ class ChatCompletionSystemMessageParam(TypedDict, total=False): + """ + Developer-provided instructions that the model should follow, regardless of + messages sent by the user. With o1 models and newer, use `developer` messages + for this purpose instead. + """ + content: Required[Union[str, Iterable[ChatCompletionContentPartTextParam]]] """The contents of the system message.""" diff --git a/src/openai/types/chat/chat_completion_token_logprob.py b/src/openai/types/chat/chat_completion_token_logprob.py index c69e258910..4ce582366a 100644 --- a/src/openai/types/chat/chat_completion_token_logprob.py +++ b/src/openai/types/chat/chat_completion_token_logprob.py @@ -52,6 +52,5 @@ class ChatCompletionTokenLogprob(BaseModel): """List of the most likely tokens and their log probability, at this token position. - In rare cases, there may be fewer than the number of requested `top_logprobs` - returned. + The number of entries may be fewer than the requested `top_logprobs`. """ diff --git a/src/openai/types/chat/chat_completion_tool_choice_option_param.py b/src/openai/types/chat/chat_completion_tool_choice_option_param.py index 7dedf041b7..f3bb0a46df 100644 --- a/src/openai/types/chat/chat_completion_tool_choice_option_param.py +++ b/src/openai/types/chat/chat_completion_tool_choice_option_param.py @@ -6,9 +6,14 @@ from typing_extensions import Literal, TypeAlias from .chat_completion_named_tool_choice_param import ChatCompletionNamedToolChoiceParam +from .chat_completion_allowed_tool_choice_param import ChatCompletionAllowedToolChoiceParam +from .chat_completion_named_tool_choice_custom_param import ChatCompletionNamedToolChoiceCustomParam __all__ = ["ChatCompletionToolChoiceOptionParam"] ChatCompletionToolChoiceOptionParam: TypeAlias = Union[ - Literal["none", "auto", "required"], ChatCompletionNamedToolChoiceParam + Literal["none", "auto", "required"], + ChatCompletionAllowedToolChoiceParam, + ChatCompletionNamedToolChoiceParam, + ChatCompletionNamedToolChoiceCustomParam, ] diff --git a/src/openai/types/chat/chat_completion_tool_param.py b/src/openai/types/chat/chat_completion_tool_param.py index 6c2b1a36f0..a18b13b471 100644 --- a/src/openai/types/chat/chat_completion_tool_param.py +++ b/src/openai/types/chat/chat_completion_tool_param.py @@ -2,15 +2,13 @@ from __future__ import annotations -from typing_extensions import Literal, Required, TypedDict +from typing_extensions import TypeAlias -from ..shared_params.function_definition import FunctionDefinition +from .chat_completion_function_tool_param import ( + FunctionDefinition as FunctionDefinition, + ChatCompletionFunctionToolParam, +) -__all__ = ["ChatCompletionToolParam"] +__all__ = ["ChatCompletionToolParam", "FunctionDefinition"] - -class ChatCompletionToolParam(TypedDict, total=False): - function: Required[FunctionDefinition] - - type: Required[Literal["function"]] - """The type of the tool. Currently, only `function` is supported.""" +ChatCompletionToolParam: TypeAlias = ChatCompletionFunctionToolParam diff --git a/src/openai/types/chat/chat_completion_tool_union_param.py b/src/openai/types/chat/chat_completion_tool_union_param.py new file mode 100644 index 0000000000..0f8bf7b0e7 --- /dev/null +++ b/src/openai/types/chat/chat_completion_tool_union_param.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypeAlias + +from .chat_completion_custom_tool_param import ChatCompletionCustomToolParam +from .chat_completion_function_tool_param import ChatCompletionFunctionToolParam + +__all__ = ["ChatCompletionToolUnionParam"] + +ChatCompletionToolUnionParam: TypeAlias = Union[ChatCompletionFunctionToolParam, ChatCompletionCustomToolParam] diff --git a/src/openai/types/chat/chat_completion_user_message_param.py b/src/openai/types/chat/chat_completion_user_message_param.py index 5c15322a22..c97ba535eb 100644 --- a/src/openai/types/chat/chat_completion_user_message_param.py +++ b/src/openai/types/chat/chat_completion_user_message_param.py @@ -11,6 +11,11 @@ class ChatCompletionUserMessageParam(TypedDict, total=False): + """ + Messages sent by an end user, containing prompts or additional context + information. + """ + content: Required[Union[str, Iterable[ChatCompletionContentPartParam]]] """The contents of the user message.""" diff --git a/src/openai/types/chat/completion_create_params.py b/src/openai/types/chat/completion_create_params.py index 4ed89b00f5..b7de79be72 100644 --- a/src/openai/types/chat/completion_create_params.py +++ b/src/openai/types/chat/completion_create_params.py @@ -5,12 +5,17 @@ from typing import Dict, List, Union, Iterable, Optional from typing_extensions import Literal, Required, TypeAlias, TypedDict -from ..chat_model import ChatModel -from .chat_completion_tool_param import ChatCompletionToolParam +from ..._types import SequenceNotStr +from ..shared.chat_model import ChatModel +from ..shared_params.metadata import Metadata +from ..shared.reasoning_effort import ReasoningEffort +from .chat_completion_audio_param import ChatCompletionAudioParam from .chat_completion_message_param import ChatCompletionMessageParam +from .chat_completion_tool_union_param import ChatCompletionToolUnionParam from ..shared_params.function_parameters import FunctionParameters from ..shared_params.response_format_text import ResponseFormatText from .chat_completion_stream_options_param import ChatCompletionStreamOptionsParam +from .chat_completion_prediction_content_param import ChatCompletionPredictionContentParam from .chat_completion_tool_choice_option_param import ChatCompletionToolChoiceOptionParam from ..shared_params.response_format_json_object import ResponseFormatJSONObject from ..shared_params.response_format_json_schema import ResponseFormatJSONSchema @@ -20,7 +25,11 @@ "CompletionCreateParamsBase", "FunctionCall", "Function", + "Moderation", "ResponseFormat", + "WebSearchOptions", + "WebSearchOptionsUserLocation", + "WebSearchOptionsUserLocationApproximate", "CompletionCreateParamsNonStreaming", "CompletionCreateParamsStreaming", ] @@ -30,15 +39,27 @@ class CompletionCreateParamsBase(TypedDict, total=False): messages: Required[Iterable[ChatCompletionMessageParam]] """A list of messages comprising the conversation so far. - [Example Python code](https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models). + Depending on the [model](https://platform.openai.com/docs/models) you use, + different message types (modalities) are supported, like + [text](https://platform.openai.com/docs/guides/text-generation), + [images](https://platform.openai.com/docs/guides/vision), and + [audio](https://platform.openai.com/docs/guides/audio). """ model: Required[Union[str, ChatModel]] - """ID of the model to use. + """Model ID used to generate the response, like `gpt-4o` or `o3`. - See the - [model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility) - table for details on which models work with the Chat API. + OpenAI offers a wide range of models with different capabilities, performance + characteristics, and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + """ + + audio: Optional[ChatCompletionAudioParam] + """Parameters for audio output. + + Required when audio output is requested with `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). """ frequency_penalty: Optional[float] @@ -46,19 +67,21 @@ class CompletionCreateParamsBase(TypedDict, total=False): Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) """ function_call: FunctionCall """Deprecated in favor of `tool_choice`. - Controls which (if any) function is called by the model. `none` means the model - will not call a function and instead generates a message. `auto` means the model - can pick between generating a message or calling a function. Specifying a - particular function via `{"name": "my_function"}` forces the model to call that + Controls which (if any) function is called by the model. + + `none` means the model will not call a function and instead generates a message. + + `auto` means the model can pick between generating a message or calling a function. + Specifying a particular function via `{"name": "my_function"}` forces the model + to call that function. + `none` is the default when no functions are present. `auto` is the default if functions are present. """ @@ -102,9 +125,36 @@ class CompletionCreateParamsBase(TypedDict, total=False): This value is now deprecated in favor of `max_completion_tokens`, and is not compatible with - [o1 series models](https://platform.openai.com/docs/guides/reasoning). + [o-series models](https://platform.openai.com/docs/guides/reasoning). """ + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + modalities: Optional[List[Literal["text", "audio"]]] + """ + Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: + + `["text"]` + + The `gpt-4o-audio-preview` model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + + `["text", "audio"]` + """ + + moderation: Optional[Moderation] + """Configuration for running moderation on the request input and generated output.""" + n: Optional[int] """How many chat completion choices to generate for each input message. @@ -115,42 +165,84 @@ class CompletionCreateParamsBase(TypedDict, total=False): parallel_tool_calls: bool """ Whether to enable - [parallel function calling](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling) + [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling) during tool use. """ + prediction: Optional[ChatCompletionPredictionContentParam] + """ + Static predicted output content, such as the content of a text file that is + being regenerated. + """ + presence_penalty: Optional[float] """Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. + """ - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + prompt_cache_key: str + """ + Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + """ + + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] + """The retention policy for the prompt cache. + + Set to `24h` to enable extended prompt caching, which keeps cached prefixes + active for longer, up to a maximum of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + """ + + reasoning_effort: Optional[ReasoningEffort] + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. """ response_format: ResponseFormat """An object specifying the format that the model must output. - Compatible with [GPT-4o](https://platform.openai.com/docs/models/gpt-4o), - [GPT-4o mini](https://platform.openai.com/docs/models/gpt-4o-mini), - [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) and - all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. - Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). - Setting to `{ "type": "json_object" }` enables JSON mode, which ensures the - message the model generates is valid JSON. + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ - **Important:** when using JSON mode, you **must** also instruct the model to - produce JSON yourself via a system or user message. Without this, the model may - generate an unending stream of whitespace until the generation reaches the token - limit, resulting in a long-running and seemingly "stuck" request. Also note that - the message content may be partially cut off if `finish_reason="length"`, which - indicates the generation exceeded `max_tokens` or the conversation exceeded the - max context length. + safety_identifier: str + """ + A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). """ seed: Optional[int] @@ -162,26 +254,40 @@ class CompletionCreateParamsBase(TypedDict, total=False): in the backend. """ - service_tier: Optional[Literal["auto", "default"]] - """Specifies the latency tier to use for processing the request. - - This parameter is relevant for customers subscribed to the scale tier service: + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] + """Specifies the processing type used for serving the request. - - If set to 'auto', and the Project is Scale tier enabled, the system will - utilize scale tier credits until they are exhausted. - - If set to 'auto', and the Project is not Scale tier enabled, the request will - be processed using the default service tier with a lower uptime SLA and no - latency guarentee. - - If set to 'default', the request will be processed using the default service - tier with a lower uptime SLA and no latency guarentee. + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. - When not set, the default behavior is 'auto'. - When this parameter is set, the response body will include the `service_tier` - utilized. + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. """ - stop: Union[Optional[str], List[str]] - """Up to 4 sequences where the API will stop generating further tokens.""" + stop: Union[Optional[str], SequenceNotStr[str], None] + """Not supported with latest reasoning models `o3` and `o4-mini`. + + Up to 4 sequences where the API will stop generating further tokens. The + returned text will not contain the stop sequence. + """ + + store: Optional[bool] + """ + Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. + + Supports text and image inputs. Note: image inputs over 8MB will be dropped. + """ stream_options: Optional[ChatCompletionStreamOptionsParam] """Options for streaming response. Only set this when you set `stream: true`.""" @@ -190,9 +296,8 @@ class CompletionCreateParamsBase(TypedDict, total=False): """What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like - 0.2 will make it more focused and deterministic. - - We generally recommend altering this or `top_p` but not both. + 0.2 will make it more focused and deterministic. We generally recommend altering + this or `top_p` but not both. """ tool_choice: ChatCompletionToolChoiceOptionParam @@ -208,18 +313,19 @@ class CompletionCreateParamsBase(TypedDict, total=False): are present. """ - tools: Iterable[ChatCompletionToolParam] + tools: Iterable[ChatCompletionToolUnionParam] """A list of tools the model may call. - Currently, only functions are supported as a tool. Use this to provide a list of - functions the model may generate JSON inputs for. A max of 128 functions are - supported. + You can provide either + [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + or [function tools](https://platform.openai.com/docs/guides/function-calling). """ top_logprobs: Optional[int] """ - An integer between 0 and 20 specifying the number of most likely tokens to - return at each token position, each with an associated log probability. + An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. `logprobs` must be set to `true` if this parameter is used. """ @@ -233,10 +339,27 @@ class CompletionCreateParamsBase(TypedDict, total=False): """ user: str + """This field is being replaced by `safety_identifier` and `prompt_cache_key`. + + Use `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). """ - A unique identifier representing your end-user, which can help OpenAI to monitor - and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + + verbosity: Optional[Literal["low", "medium", "high"]] + """Constrains the verbosity of the model's response. + + Lower values will result in more concise responses, while higher values will + result in more verbose responses. Currently supported values are `low`, + `medium`, and `high`. + """ + + web_search_options: WebSearchOptions + """ + This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). """ @@ -269,30 +392,92 @@ class Function(TypedDict, total=False): """ -ResponseFormat: TypeAlias = Union[ResponseFormatText, ResponseFormatJSONObject, ResponseFormatJSONSchema] +class Moderation(TypedDict, total=False): + """Configuration for running moderation on the request input and generated output.""" + + model: Required[str] + """The moderation model to use for moderated completions, e.g. + + 'omni-moderation-latest'. + """ + + +ResponseFormat: TypeAlias = Union[ResponseFormatText, ResponseFormatJSONSchema, ResponseFormatJSONObject] + + +class WebSearchOptionsUserLocationApproximate(TypedDict, total=False): + """Approximate location parameters for the search.""" + + city: str + """Free text input for the city of the user, e.g. `San Francisco`.""" + + country: str + """ + The two-letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1) of + the user, e.g. `US`. + """ + + region: str + """Free text input for the region of the user, e.g. `California`.""" + + timezone: str + """ + The [IANA timezone](https://timeapi.io/documentation/iana-timezones) of the + user, e.g. `America/Los_Angeles`. + """ + + +class WebSearchOptionsUserLocation(TypedDict, total=False): + """Approximate location parameters for the search.""" + + approximate: Required[WebSearchOptionsUserLocationApproximate] + """Approximate location parameters for the search.""" + + type: Required[Literal["approximate"]] + """The type of location approximation. Always `approximate`.""" + + +class WebSearchOptions(TypedDict, total=False): + """ + This tool searches the web for relevant results to use in a response. + Learn more about the [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). + """ + + search_context_size: Literal["low", "medium", "high"] + """ + High level guidance for the amount of context window space to use for the + search. One of `low`, `medium`, or `high`. `medium` is the default. + """ + + user_location: Optional[WebSearchOptionsUserLocation] + """Approximate location parameters for the search.""" class CompletionCreateParamsNonStreaming(CompletionCreateParamsBase, total=False): stream: Optional[Literal[False]] - """If set, partial message deltas will be sent, like in ChatGPT. - - Tokens will be sent as data-only - [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available, with the stream terminated by a `data: [DONE]` - message. - [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). + """ + If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/chat/streaming) + for more information, along with the + [streaming responses](https://platform.openai.com/docs/guides/streaming-responses) + guide for more information on how to handle the streaming events. """ class CompletionCreateParamsStreaming(CompletionCreateParamsBase): stream: Required[Literal[True]] - """If set, partial message deltas will be sent, like in ChatGPT. - - Tokens will be sent as data-only - [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available, with the stream terminated by a `data: [DONE]` - message. - [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). + """ + If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/chat/streaming) + for more information, along with the + [streaming responses](https://platform.openai.com/docs/guides/streaming-responses) + guide for more information on how to handle the streaming events. """ diff --git a/src/openai/types/chat/completion_list_params.py b/src/openai/types/chat/completion_list_params.py new file mode 100644 index 0000000000..d93da834a3 --- /dev/null +++ b/src/openai/types/chat/completion_list_params.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +from ..shared_params.metadata import Metadata + +__all__ = ["CompletionListParams"] + + +class CompletionListParams(TypedDict, total=False): + after: str + """Identifier for the last chat completion from the previous pagination request.""" + + limit: int + """Number of Chat Completions to retrieve.""" + + metadata: Optional[Metadata] + """A list of metadata keys to filter the Chat Completions by. Example: + + `metadata[key1]=value1&metadata[key2]=value2` + """ + + model: str + """The model used to generate the Chat Completions.""" + + order: Literal["asc", "desc"] + """Sort order for Chat Completions by timestamp. + + Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`. + """ diff --git a/src/openai/types/chat/completion_update_params.py b/src/openai/types/chat/completion_update_params.py new file mode 100644 index 0000000000..fc71733f07 --- /dev/null +++ b/src/openai/types/chat/completion_update_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +from ..shared_params.metadata import Metadata + +__all__ = ["CompletionUpdateParams"] + + +class CompletionUpdateParams(TypedDict, total=False): + metadata: Required[Optional[Metadata]] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ diff --git a/src/openai/types/chat/completions/__init__.py b/src/openai/types/chat/completions/__init__.py new file mode 100644 index 0000000000..b8e62d6a64 --- /dev/null +++ b/src/openai/types/chat/completions/__init__.py @@ -0,0 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .message_list_params import MessageListParams as MessageListParams diff --git a/src/openai/types/chat/completions/message_list_params.py b/src/openai/types/chat/completions/message_list_params.py new file mode 100644 index 0000000000..4e694e83ea --- /dev/null +++ b/src/openai/types/chat/completions/message_list_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["MessageListParams"] + + +class MessageListParams(TypedDict, total=False): + after: str + """Identifier for the last message from the previous pagination request.""" + + limit: int + """Number of messages to retrieve.""" + + order: Literal["asc", "desc"] + """Sort order for messages by timestamp. + + Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`. + """ diff --git a/src/openai/types/chat/parsed_function_tool_call.py b/src/openai/types/chat/parsed_function_tool_call.py index 3e90789f85..e06b3546cb 100644 --- a/src/openai/types/chat/parsed_function_tool_call.py +++ b/src/openai/types/chat/parsed_function_tool_call.py @@ -2,7 +2,7 @@ from typing import Optional -from .chat_completion_message_tool_call import Function, ChatCompletionMessageToolCall +from .chat_completion_message_function_tool_call import Function, ChatCompletionMessageFunctionToolCall __all__ = ["ParsedFunctionToolCall", "ParsedFunction"] @@ -24,6 +24,6 @@ class ParsedFunction(Function): """ -class ParsedFunctionToolCall(ChatCompletionMessageToolCall): +class ParsedFunctionToolCall(ChatCompletionMessageFunctionToolCall): function: ParsedFunction """The function that the model called.""" diff --git a/src/openai/types/chat_model.py b/src/openai/types/chat_model.py index f8438c75c8..f3b0e310cc 100644 --- a/src/openai/types/chat_model.py +++ b/src/openai/types/chat_model.py @@ -1,37 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing_extensions import Literal, TypeAlias +from .shared import chat_model __all__ = ["ChatModel"] -ChatModel: TypeAlias = Literal[ - "o1-preview", - "o1-preview-2024-09-12", - "o1-mini", - "o1-mini-2024-09-12", - "gpt-4o", - "gpt-4o-2024-08-06", - "gpt-4o-2024-05-13", - "chatgpt-4o-latest", - "gpt-4o-mini", - "gpt-4o-mini-2024-07-18", - "gpt-4-turbo", - "gpt-4-turbo-2024-04-09", - "gpt-4-0125-preview", - "gpt-4-turbo-preview", - "gpt-4-1106-preview", - "gpt-4-vision-preview", - "gpt-4", - "gpt-4-0314", - "gpt-4-0613", - "gpt-4-32k", - "gpt-4-32k-0314", - "gpt-4-32k-0613", - "gpt-3.5-turbo", - "gpt-3.5-turbo-16k", - "gpt-3.5-turbo-0301", - "gpt-3.5-turbo-0613", - "gpt-3.5-turbo-1106", - "gpt-3.5-turbo-0125", - "gpt-3.5-turbo-16k-0613", -] +ChatModel = chat_model.ChatModel diff --git a/src/openai/types/completion.py b/src/openai/types/completion.py index d3b3102a4a..ee59b2e209 100644 --- a/src/openai/types/completion.py +++ b/src/openai/types/completion.py @@ -11,6 +11,11 @@ class Completion(BaseModel): + """Represents a completion response from the API. + + Note: both the streamed and non-streamed response objects share the same shape (unlike the chat endpoint). + """ + id: str """A unique identifier for the completion.""" diff --git a/src/openai/types/completion_create_params.py b/src/openai/types/completion_create_params.py index 6c112b3902..f9beb9afc7 100644 --- a/src/openai/types/completion_create_params.py +++ b/src/openai/types/completion_create_params.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable, Optional +from typing import Dict, Union, Iterable, Optional from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr from .chat.chat_completion_stream_options_param import ChatCompletionStreamOptionsParam __all__ = ["CompletionCreateParamsBase", "CompletionCreateParamsNonStreaming", "CompletionCreateParamsStreaming"] @@ -17,11 +18,11 @@ class CompletionCreateParamsBase(TypedDict, total=False): You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. """ - prompt: Required[Union[str, List[str], Iterable[int], Iterable[Iterable[int]], None]] + prompt: Required[Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]], None]] """ The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays. @@ -53,7 +54,7 @@ class CompletionCreateParamsBase(TypedDict, total=False): Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) """ logit_bias: Optional[Dict[str, int]] @@ -106,7 +107,7 @@ class CompletionCreateParamsBase(TypedDict, total=False): Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation/parameter-details) + [See more information about frequency and presence penalties.](https://platform.openai.com/docs/guides/text-generation) """ seed: Optional[int] @@ -119,10 +120,11 @@ class CompletionCreateParamsBase(TypedDict, total=False): response parameter to monitor changes in the backend. """ - stop: Union[Optional[str], List[str], None] - """Up to 4 sequences where the API will stop generating further tokens. + stop: Union[Optional[str], SequenceNotStr[str], None] + """Not supported with latest reasoning models `o3` and `o4-mini`. - The returned text will not contain the stop sequence. + Up to 4 sequences where the API will stop generating further tokens. The + returned text will not contain the stop sequence. """ stream_options: Optional[ChatCompletionStreamOptionsParam] @@ -156,7 +158,7 @@ class CompletionCreateParamsBase(TypedDict, total=False): """ A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). """ diff --git a/src/openai/types/completion_usage.py b/src/openai/types/completion_usage.py index a4b9116e35..9b5202da14 100644 --- a/src/openai/types/completion_usage.py +++ b/src/openai/types/completion_usage.py @@ -4,15 +4,46 @@ from .._models import BaseModel -__all__ = ["CompletionUsage", "CompletionTokensDetails"] +__all__ = ["CompletionUsage", "CompletionTokensDetails", "PromptTokensDetails"] class CompletionTokensDetails(BaseModel): + """Breakdown of tokens used in a completion.""" + + accepted_prediction_tokens: Optional[int] = None + """ + When using Predicted Outputs, the number of tokens in the prediction that + appeared in the completion. + """ + + audio_tokens: Optional[int] = None + """Audio input tokens generated by the model.""" + reasoning_tokens: Optional[int] = None """Tokens generated by the model for reasoning.""" + rejected_prediction_tokens: Optional[int] = None + """ + When using Predicted Outputs, the number of tokens in the prediction that did + not appear in the completion. However, like reasoning tokens, these tokens are + still counted in the total completion tokens for purposes of billing, output, + and context window limits. + """ + + +class PromptTokensDetails(BaseModel): + """Breakdown of tokens used in the prompt.""" + + audio_tokens: Optional[int] = None + """Audio input tokens present in the prompt.""" + + cached_tokens: Optional[int] = None + """Cached tokens present in the prompt.""" + class CompletionUsage(BaseModel): + """Usage statistics for the completion request.""" + completion_tokens: int """Number of tokens in the generated completion.""" @@ -24,3 +55,6 @@ class CompletionUsage(BaseModel): completion_tokens_details: Optional[CompletionTokensDetails] = None """Breakdown of tokens used in a completion.""" + + prompt_tokens_details: Optional[PromptTokensDetails] = None + """Breakdown of tokens used in the prompt.""" diff --git a/src/openai/types/container_create_params.py b/src/openai/types/container_create_params.py new file mode 100644 index 0000000000..63d28f3955 --- /dev/null +++ b/src/openai/types/container_create_params.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .._types import SequenceNotStr +from .responses.inline_skill_param import InlineSkillParam +from .responses.skill_reference_param import SkillReferenceParam +from .responses.container_network_policy_disabled_param import ContainerNetworkPolicyDisabledParam +from .responses.container_network_policy_allowlist_param import ContainerNetworkPolicyAllowlistParam + +__all__ = ["ContainerCreateParams", "ExpiresAfter", "NetworkPolicy", "Skill"] + + +class ContainerCreateParams(TypedDict, total=False): + name: Required[str] + """Name of the container to create.""" + + expires_after: ExpiresAfter + """Container expiration time in seconds relative to the 'anchor' time.""" + + file_ids: SequenceNotStr[str] + """IDs of files to copy to the container.""" + + memory_limit: Literal["1g", "4g", "16g", "64g"] + """Optional memory limit for the container. Defaults to "1g".""" + + network_policy: NetworkPolicy + """Network access policy for the container.""" + + skills: Iterable[Skill] + """An optional list of skills referenced by id or inline data.""" + + +class ExpiresAfter(TypedDict, total=False): + """Container expiration time in seconds relative to the 'anchor' time.""" + + anchor: Required[Literal["last_active_at"]] + """Time anchor for the expiration time. + + Currently only 'last_active_at' is supported. + """ + + minutes: Required[int] + + +NetworkPolicy: TypeAlias = Union[ContainerNetworkPolicyDisabledParam, ContainerNetworkPolicyAllowlistParam] + +Skill: TypeAlias = Union[SkillReferenceParam, InlineSkillParam] diff --git a/src/openai/types/container_create_response.py b/src/openai/types/container_create_response.py new file mode 100644 index 0000000000..34bc56ad13 --- /dev/null +++ b/src/openai/types/container_create_response.py @@ -0,0 +1,65 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ContainerCreateResponse", "ExpiresAfter", "NetworkPolicy"] + + +class ExpiresAfter(BaseModel): + """ + The container will expire after this time period. + The anchor is the reference point for the expiration. + The minutes is the number of minutes after the anchor before the container expires. + """ + + anchor: Optional[Literal["last_active_at"]] = None + """The reference point for the expiration.""" + + minutes: Optional[int] = None + """The number of minutes after the anchor before the container expires.""" + + +class NetworkPolicy(BaseModel): + """Network access policy for the container.""" + + type: Literal["allowlist", "disabled"] + """The network policy mode.""" + + allowed_domains: Optional[List[str]] = None + """Allowed outbound domains when `type` is `allowlist`.""" + + +class ContainerCreateResponse(BaseModel): + id: str + """Unique identifier for the container.""" + + created_at: int + """Unix timestamp (in seconds) when the container was created.""" + + name: str + """Name of the container.""" + + object: str + """The type of this object.""" + + status: str + """Status of the container (e.g., active, deleted).""" + + expires_after: Optional[ExpiresAfter] = None + """ + The container will expire after this time period. The anchor is the reference + point for the expiration. The minutes is the number of minutes after the anchor + before the container expires. + """ + + last_active_at: Optional[int] = None + """Unix timestamp (in seconds) when the container was last active.""" + + memory_limit: Optional[Literal["1g", "4g", "16g", "64g"]] = None + """The memory limit configured for the container.""" + + network_policy: Optional[NetworkPolicy] = None + """Network access policy for the container.""" diff --git a/src/openai/types/container_list_params.py b/src/openai/types/container_list_params.py new file mode 100644 index 0000000000..01ec43af32 --- /dev/null +++ b/src/openai/types/container_list_params.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["ContainerListParams"] + + +class ContainerListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ + + name: str + """Filter results by container name.""" + + order: Literal["asc", "desc"] + """Sort order by the `created_at` timestamp of the objects. + + `asc` for ascending order and `desc` for descending order. + """ diff --git a/src/openai/types/container_list_response.py b/src/openai/types/container_list_response.py new file mode 100644 index 0000000000..bf572acd6b --- /dev/null +++ b/src/openai/types/container_list_response.py @@ -0,0 +1,65 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ContainerListResponse", "ExpiresAfter", "NetworkPolicy"] + + +class ExpiresAfter(BaseModel): + """ + The container will expire after this time period. + The anchor is the reference point for the expiration. + The minutes is the number of minutes after the anchor before the container expires. + """ + + anchor: Optional[Literal["last_active_at"]] = None + """The reference point for the expiration.""" + + minutes: Optional[int] = None + """The number of minutes after the anchor before the container expires.""" + + +class NetworkPolicy(BaseModel): + """Network access policy for the container.""" + + type: Literal["allowlist", "disabled"] + """The network policy mode.""" + + allowed_domains: Optional[List[str]] = None + """Allowed outbound domains when `type` is `allowlist`.""" + + +class ContainerListResponse(BaseModel): + id: str + """Unique identifier for the container.""" + + created_at: int + """Unix timestamp (in seconds) when the container was created.""" + + name: str + """Name of the container.""" + + object: str + """The type of this object.""" + + status: str + """Status of the container (e.g., active, deleted).""" + + expires_after: Optional[ExpiresAfter] = None + """ + The container will expire after this time period. The anchor is the reference + point for the expiration. The minutes is the number of minutes after the anchor + before the container expires. + """ + + last_active_at: Optional[int] = None + """Unix timestamp (in seconds) when the container was last active.""" + + memory_limit: Optional[Literal["1g", "4g", "16g", "64g"]] = None + """The memory limit configured for the container.""" + + network_policy: Optional[NetworkPolicy] = None + """Network access policy for the container.""" diff --git a/src/openai/types/container_retrieve_response.py b/src/openai/types/container_retrieve_response.py new file mode 100644 index 0000000000..b5a6d350ff --- /dev/null +++ b/src/openai/types/container_retrieve_response.py @@ -0,0 +1,65 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ContainerRetrieveResponse", "ExpiresAfter", "NetworkPolicy"] + + +class ExpiresAfter(BaseModel): + """ + The container will expire after this time period. + The anchor is the reference point for the expiration. + The minutes is the number of minutes after the anchor before the container expires. + """ + + anchor: Optional[Literal["last_active_at"]] = None + """The reference point for the expiration.""" + + minutes: Optional[int] = None + """The number of minutes after the anchor before the container expires.""" + + +class NetworkPolicy(BaseModel): + """Network access policy for the container.""" + + type: Literal["allowlist", "disabled"] + """The network policy mode.""" + + allowed_domains: Optional[List[str]] = None + """Allowed outbound domains when `type` is `allowlist`.""" + + +class ContainerRetrieveResponse(BaseModel): + id: str + """Unique identifier for the container.""" + + created_at: int + """Unix timestamp (in seconds) when the container was created.""" + + name: str + """Name of the container.""" + + object: str + """The type of this object.""" + + status: str + """Status of the container (e.g., active, deleted).""" + + expires_after: Optional[ExpiresAfter] = None + """ + The container will expire after this time period. The anchor is the reference + point for the expiration. The minutes is the number of minutes after the anchor + before the container expires. + """ + + last_active_at: Optional[int] = None + """Unix timestamp (in seconds) when the container was last active.""" + + memory_limit: Optional[Literal["1g", "4g", "16g", "64g"]] = None + """The memory limit configured for the container.""" + + network_policy: Optional[NetworkPolicy] = None + """Network access policy for the container.""" diff --git a/src/openai/types/containers/__init__.py b/src/openai/types/containers/__init__.py new file mode 100644 index 0000000000..7d555ad3a4 --- /dev/null +++ b/src/openai/types/containers/__init__.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .file_list_params import FileListParams as FileListParams +from .file_create_params import FileCreateParams as FileCreateParams +from .file_list_response import FileListResponse as FileListResponse +from .file_create_response import FileCreateResponse as FileCreateResponse +from .file_retrieve_response import FileRetrieveResponse as FileRetrieveResponse diff --git a/src/openai/types/containers/file_create_params.py b/src/openai/types/containers/file_create_params.py new file mode 100644 index 0000000000..1e41330017 --- /dev/null +++ b/src/openai/types/containers/file_create_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +from ..._types import FileTypes + +__all__ = ["FileCreateParams"] + + +class FileCreateParams(TypedDict, total=False): + file: FileTypes + """The File object (not file name) to be uploaded.""" + + file_id: str + """Name of the file to create.""" diff --git a/src/openai/types/containers/file_create_response.py b/src/openai/types/containers/file_create_response.py new file mode 100644 index 0000000000..4a652483fc --- /dev/null +++ b/src/openai/types/containers/file_create_response.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FileCreateResponse"] + + +class FileCreateResponse(BaseModel): + id: str + """Unique identifier for the file.""" + + bytes: int + """Size of the file in bytes.""" + + container_id: str + """The container this file belongs to.""" + + created_at: int + """Unix timestamp (in seconds) when the file was created.""" + + object: Literal["container.file"] + """The type of this object (`container.file`).""" + + path: str + """Path of the file in the container.""" + + source: str + """Source of the file (e.g., `user`, `assistant`).""" diff --git a/src/openai/types/containers/file_list_params.py b/src/openai/types/containers/file_list_params.py new file mode 100644 index 0000000000..3565acaf36 --- /dev/null +++ b/src/openai/types/containers/file_list_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["FileListParams"] + + +class FileListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ + + order: Literal["asc", "desc"] + """Sort order by the `created_at` timestamp of the objects. + + `asc` for ascending order and `desc` for descending order. + """ diff --git a/src/openai/types/containers/file_list_response.py b/src/openai/types/containers/file_list_response.py new file mode 100644 index 0000000000..e5eee38d99 --- /dev/null +++ b/src/openai/types/containers/file_list_response.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FileListResponse"] + + +class FileListResponse(BaseModel): + id: str + """Unique identifier for the file.""" + + bytes: int + """Size of the file in bytes.""" + + container_id: str + """The container this file belongs to.""" + + created_at: int + """Unix timestamp (in seconds) when the file was created.""" + + object: Literal["container.file"] + """The type of this object (`container.file`).""" + + path: str + """Path of the file in the container.""" + + source: str + """Source of the file (e.g., `user`, `assistant`).""" diff --git a/src/openai/types/containers/file_retrieve_response.py b/src/openai/types/containers/file_retrieve_response.py new file mode 100644 index 0000000000..37fb0e43dd --- /dev/null +++ b/src/openai/types/containers/file_retrieve_response.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FileRetrieveResponse"] + + +class FileRetrieveResponse(BaseModel): + id: str + """Unique identifier for the file.""" + + bytes: int + """Size of the file in bytes.""" + + container_id: str + """The container this file belongs to.""" + + created_at: int + """Unix timestamp (in seconds) when the file was created.""" + + object: Literal["container.file"] + """The type of this object (`container.file`).""" + + path: str + """Path of the file in the container.""" + + source: str + """Source of the file (e.g., `user`, `assistant`).""" diff --git a/src/openai/types/containers/files/__init__.py b/src/openai/types/containers/files/__init__.py new file mode 100644 index 0000000000..f8ee8b14b1 --- /dev/null +++ b/src/openai/types/containers/files/__init__.py @@ -0,0 +1,3 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations diff --git a/src/openai/types/conversations/__init__.py b/src/openai/types/conversations/__init__.py new file mode 100644 index 0000000000..9dec848737 --- /dev/null +++ b/src/openai/types/conversations/__init__.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .message import Message as Message +from .conversation import Conversation as Conversation +from .text_content import TextContent as TextContent +from .refusal_content import RefusalContent as RefusalContent +from .item_list_params import ItemListParams as ItemListParams +from .conversation_item import ConversationItem as ConversationItem +from .input_file_content import InputFileContent as InputFileContent +from .input_text_content import InputTextContent as InputTextContent +from .item_create_params import ItemCreateParams as ItemCreateParams +from .input_image_content import InputImageContent as InputImageContent +from .output_text_content import OutputTextContent as OutputTextContent +from .item_retrieve_params import ItemRetrieveParams as ItemRetrieveParams +from .summary_text_content import SummaryTextContent as SummaryTextContent +from .refusal_content_param import RefusalContentParam as RefusalContentParam +from .conversation_item_list import ConversationItemList as ConversationItemList +from .input_file_content_param import InputFileContentParam as InputFileContentParam +from .input_text_content_param import InputTextContentParam as InputTextContentParam +from .input_image_content_param import InputImageContentParam as InputImageContentParam +from .output_text_content_param import OutputTextContentParam as OutputTextContentParam +from .conversation_create_params import ConversationCreateParams as ConversationCreateParams +from .conversation_update_params import ConversationUpdateParams as ConversationUpdateParams +from .computer_screenshot_content import ComputerScreenshotContent as ComputerScreenshotContent +from .conversation_deleted_resource import ConversationDeletedResource as ConversationDeletedResource diff --git a/src/openai/types/conversations/computer_screenshot_content.py b/src/openai/types/conversations/computer_screenshot_content.py new file mode 100644 index 0000000000..ff43a7e589 --- /dev/null +++ b/src/openai/types/conversations/computer_screenshot_content.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ComputerScreenshotContent"] + + +class ComputerScreenshotContent(BaseModel): + """A screenshot of a computer.""" + + detail: Literal["low", "high", "auto", "original"] + """The detail level of the screenshot image to be sent to the model. + + One of `high`, `low`, `auto`, or `original`. Defaults to `auto`. + """ + + file_id: Optional[str] = None + """The identifier of an uploaded file that contains the screenshot.""" + + image_url: Optional[str] = None + """The URL of the screenshot image.""" + + type: Literal["computer_screenshot"] + """Specifies the event type. + + For a computer screenshot, this property is always set to `computer_screenshot`. + """ diff --git a/src/openai/types/conversations/conversation.py b/src/openai/types/conversations/conversation.py new file mode 100644 index 0000000000..ed63d40355 --- /dev/null +++ b/src/openai/types/conversations/conversation.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["Conversation"] + + +class Conversation(BaseModel): + id: str + """The unique ID of the conversation.""" + + created_at: int + """ + The time at which the conversation was created, measured in seconds since the + Unix epoch. + """ + + metadata: object + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. Keys are + strings with a maximum length of 64 characters. Values are strings with a + maximum length of 512 characters. + """ + + object: Literal["conversation"] + """The object type, which is always `conversation`.""" diff --git a/src/openai/types/conversations/conversation_create_params.py b/src/openai/types/conversations/conversation_create_params.py new file mode 100644 index 0000000000..5f38d2aca7 --- /dev/null +++ b/src/openai/types/conversations/conversation_create_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import TypedDict + +from ..shared_params.metadata import Metadata +from ..responses.response_input_item_param import ResponseInputItemParam + +__all__ = ["ConversationCreateParams"] + + +class ConversationCreateParams(TypedDict, total=False): + items: Optional[Iterable[ResponseInputItemParam]] + """Initial items to include in the conversation context. + + You may add up to 20 items at a time. + """ + + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ diff --git a/src/openai/types/conversations/conversation_deleted_resource.py b/src/openai/types/conversations/conversation_deleted_resource.py new file mode 100644 index 0000000000..7abcb2448e --- /dev/null +++ b/src/openai/types/conversations/conversation_deleted_resource.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ConversationDeletedResource"] + + +class ConversationDeletedResource(BaseModel): + id: str + + deleted: bool + + object: Literal["conversation.deleted"] diff --git a/src/openai/types/conversations/conversation_item.py b/src/openai/types/conversations/conversation_item.py new file mode 100644 index 0000000000..9bc0495a34 --- /dev/null +++ b/src/openai/types/conversations/conversation_item.py @@ -0,0 +1,271 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from .message import Message +from ..._utils import PropertyInfo +from ..._models import BaseModel +from ..responses.tool import Tool +from ..responses.response_reasoning_item import ResponseReasoningItem +from ..responses.response_compaction_item import ResponseCompactionItem +from ..responses.response_custom_tool_call import ResponseCustomToolCall +from ..responses.response_tool_search_call import ResponseToolSearchCall +from ..responses.response_computer_tool_call import ResponseComputerToolCall +from ..responses.response_function_web_search import ResponseFunctionWebSearch +from ..responses.response_apply_patch_tool_call import ResponseApplyPatchToolCall +from ..responses.response_file_search_tool_call import ResponseFileSearchToolCall +from ..responses.response_custom_tool_call_output import ResponseCustomToolCallOutput +from ..responses.response_function_tool_call_item import ResponseFunctionToolCallItem +from ..responses.response_tool_search_output_item import ResponseToolSearchOutputItem +from ..responses.response_function_shell_tool_call import ResponseFunctionShellToolCall +from ..responses.response_code_interpreter_tool_call import ResponseCodeInterpreterToolCall +from ..responses.response_apply_patch_tool_call_output import ResponseApplyPatchToolCallOutput +from ..responses.response_computer_tool_call_output_item import ResponseComputerToolCallOutputItem +from ..responses.response_function_tool_call_output_item import ResponseFunctionToolCallOutputItem +from ..responses.response_function_shell_tool_call_output import ResponseFunctionShellToolCallOutput + +__all__ = [ + "ConversationItem", + "ImageGenerationCall", + "AdditionalTools", + "LocalShellCall", + "LocalShellCallAction", + "LocalShellCallOutput", + "McpListTools", + "McpListToolsTool", + "McpApprovalRequest", + "McpApprovalResponse", + "McpCall", +] + + +class ImageGenerationCall(BaseModel): + """An image generation request made by the model.""" + + id: str + """The unique ID of the image generation call.""" + + result: Optional[str] = None + """The generated image encoded in base64.""" + + status: Literal["in_progress", "completed", "generating", "failed"] + """The status of the image generation call.""" + + type: Literal["image_generation_call"] + """The type of the image generation call. Always `image_generation_call`.""" + + +class AdditionalTools(BaseModel): + id: str + """The unique ID of the additional tools item.""" + + role: Literal["unknown", "user", "assistant", "system", "critic", "discriminator", "developer", "tool"] + """The role that provided the additional tools.""" + + tools: List[Tool] + """The additional tool definitions made available at this item.""" + + type: Literal["additional_tools"] + """The type of the item. Always `additional_tools`.""" + + +class LocalShellCallAction(BaseModel): + """Execute a shell command on the server.""" + + command: List[str] + """The command to run.""" + + env: Dict[str, str] + """Environment variables to set for the command.""" + + type: Literal["exec"] + """The type of the local shell action. Always `exec`.""" + + timeout_ms: Optional[int] = None + """Optional timeout in milliseconds for the command.""" + + user: Optional[str] = None + """Optional user to run the command as.""" + + working_directory: Optional[str] = None + """Optional working directory to run the command in.""" + + +class LocalShellCall(BaseModel): + """A tool call to run a command on the local shell.""" + + id: str + """The unique ID of the local shell call.""" + + action: LocalShellCallAction + """Execute a shell command on the server.""" + + call_id: str + """The unique ID of the local shell tool call generated by the model.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the local shell call.""" + + type: Literal["local_shell_call"] + """The type of the local shell call. Always `local_shell_call`.""" + + +class LocalShellCallOutput(BaseModel): + """The output of a local shell tool call.""" + + id: str + """The unique ID of the local shell tool call generated by the model.""" + + output: str + """A JSON string of the output of the local shell tool call.""" + + type: Literal["local_shell_call_output"] + """The type of the local shell tool call output. Always `local_shell_call_output`.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the item. One of `in_progress`, `completed`, or `incomplete`.""" + + +class McpListToolsTool(BaseModel): + """A tool available on an MCP server.""" + + input_schema: object + """The JSON schema describing the tool's input.""" + + name: str + """The name of the tool.""" + + annotations: Optional[object] = None + """Additional annotations about the tool.""" + + description: Optional[str] = None + """The description of the tool.""" + + +class McpListTools(BaseModel): + """A list of tools available on an MCP server.""" + + id: str + """The unique ID of the list.""" + + server_label: str + """The label of the MCP server.""" + + tools: List[McpListToolsTool] + """The tools available on the server.""" + + type: Literal["mcp_list_tools"] + """The type of the item. Always `mcp_list_tools`.""" + + error: Optional[str] = None + """Error message if the server could not list tools.""" + + +class McpApprovalRequest(BaseModel): + """A request for human approval of a tool invocation.""" + + id: str + """The unique ID of the approval request.""" + + arguments: str + """A JSON string of arguments for the tool.""" + + name: str + """The name of the tool to run.""" + + server_label: str + """The label of the MCP server making the request.""" + + type: Literal["mcp_approval_request"] + """The type of the item. Always `mcp_approval_request`.""" + + +class McpApprovalResponse(BaseModel): + """A response to an MCP approval request.""" + + id: str + """The unique ID of the approval response""" + + approval_request_id: str + """The ID of the approval request being answered.""" + + approve: bool + """Whether the request was approved.""" + + type: Literal["mcp_approval_response"] + """The type of the item. Always `mcp_approval_response`.""" + + reason: Optional[str] = None + """Optional reason for the decision.""" + + +class McpCall(BaseModel): + """An invocation of a tool on an MCP server.""" + + id: str + """The unique ID of the tool call.""" + + arguments: str + """A JSON string of the arguments passed to the tool.""" + + name: str + """The name of the tool that was run.""" + + server_label: str + """The label of the MCP server running the tool.""" + + type: Literal["mcp_call"] + """The type of the item. Always `mcp_call`.""" + + approval_request_id: Optional[str] = None + """ + Unique identifier for the MCP tool call approval request. Include this value in + a subsequent `mcp_approval_response` input to approve or reject the + corresponding tool call. + """ + + error: Optional[str] = None + """The error from the tool call, if any.""" + + output: Optional[str] = None + """The output from the tool call.""" + + status: Optional[Literal["in_progress", "completed", "incomplete", "calling", "failed"]] = None + """The status of the tool call. + + One of `in_progress`, `completed`, `incomplete`, `calling`, or `failed`. + """ + + +ConversationItem: TypeAlias = Annotated[ + Union[ + Message, + ResponseFunctionToolCallItem, + ResponseFunctionToolCallOutputItem, + ResponseFileSearchToolCall, + ResponseFunctionWebSearch, + ImageGenerationCall, + ResponseComputerToolCall, + ResponseComputerToolCallOutputItem, + ResponseToolSearchCall, + ResponseToolSearchOutputItem, + AdditionalTools, + ResponseReasoningItem, + ResponseCompactionItem, + ResponseCodeInterpreterToolCall, + LocalShellCall, + LocalShellCallOutput, + ResponseFunctionShellToolCall, + ResponseFunctionShellToolCallOutput, + ResponseApplyPatchToolCall, + ResponseApplyPatchToolCallOutput, + McpListTools, + McpApprovalRequest, + McpApprovalResponse, + McpCall, + ResponseCustomToolCall, + ResponseCustomToolCallOutput, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/conversations/conversation_item_list.py b/src/openai/types/conversations/conversation_item_list.py new file mode 100644 index 0000000000..74d945d864 --- /dev/null +++ b/src/openai/types/conversations/conversation_item_list.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import Literal + +from ..._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ConversationItemList"] + + +class ConversationItemList(BaseModel): + """A list of Conversation items.""" + + data: List[ConversationItem] + """A list of conversation items.""" + + first_id: str + """The ID of the first item in the list.""" + + has_more: bool + """Whether there are more items available.""" + + last_id: str + """The ID of the last item in the list.""" + + object: Literal["list"] + """The type of object returned, must be `list`.""" diff --git a/src/openai/types/conversations/conversation_update_params.py b/src/openai/types/conversations/conversation_update_params.py new file mode 100644 index 0000000000..1f0dd09e50 --- /dev/null +++ b/src/openai/types/conversations/conversation_update_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +from ..shared_params.metadata import Metadata + +__all__ = ["ConversationUpdateParams"] + + +class ConversationUpdateParams(TypedDict, total=False): + metadata: Required[Optional[Metadata]] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ diff --git a/src/openai/types/conversations/input_file_content.py b/src/openai/types/conversations/input_file_content.py new file mode 100644 index 0000000000..ca555d85fc --- /dev/null +++ b/src/openai/types/conversations/input_file_content.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..responses.response_input_file import ResponseInputFile + +__all__ = ["InputFileContent"] + +InputFileContent = ResponseInputFile diff --git a/src/openai/types/conversations/input_file_content_param.py b/src/openai/types/conversations/input_file_content_param.py new file mode 100644 index 0000000000..1ed8b8b9d1 --- /dev/null +++ b/src/openai/types/conversations/input_file_content_param.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..responses.response_input_file_param import ResponseInputFileParam + +InputFileContentParam = ResponseInputFileParam diff --git a/src/openai/types/conversations/input_image_content.py b/src/openai/types/conversations/input_image_content.py new file mode 100644 index 0000000000..4304323c3a --- /dev/null +++ b/src/openai/types/conversations/input_image_content.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..responses.response_input_image import ResponseInputImage + +__all__ = ["InputImageContent"] + +InputImageContent = ResponseInputImage diff --git a/src/openai/types/conversations/input_image_content_param.py b/src/openai/types/conversations/input_image_content_param.py new file mode 100644 index 0000000000..a0ef9f545c --- /dev/null +++ b/src/openai/types/conversations/input_image_content_param.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..responses.response_input_image_param import ResponseInputImageParam + +InputImageContentParam = ResponseInputImageParam diff --git a/src/openai/types/conversations/input_text_content.py b/src/openai/types/conversations/input_text_content.py new file mode 100644 index 0000000000..cab8b26cb1 --- /dev/null +++ b/src/openai/types/conversations/input_text_content.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..responses.response_input_text import ResponseInputText + +__all__ = ["InputTextContent"] + +InputTextContent = ResponseInputText diff --git a/src/openai/types/conversations/input_text_content_param.py b/src/openai/types/conversations/input_text_content_param.py new file mode 100644 index 0000000000..b1fd9a5f1c --- /dev/null +++ b/src/openai/types/conversations/input_text_content_param.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..responses.response_input_text_param import ResponseInputTextParam + +InputTextContentParam = ResponseInputTextParam diff --git a/src/openai/types/conversations/item_create_params.py b/src/openai/types/conversations/item_create_params.py new file mode 100644 index 0000000000..9158b7167f --- /dev/null +++ b/src/openai/types/conversations/item_create_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Iterable +from typing_extensions import Required, TypedDict + +from ..responses.response_includable import ResponseIncludable +from ..responses.response_input_item_param import ResponseInputItemParam + +__all__ = ["ItemCreateParams"] + + +class ItemCreateParams(TypedDict, total=False): + items: Required[Iterable[ResponseInputItemParam]] + """The items to add to the conversation. You may add up to 20 items at a time.""" + + include: List[ResponseIncludable] + """Additional fields to include in the response. + + See the `include` parameter for + [listing Conversation items above](https://platform.openai.com/docs/api-reference/conversations/list-items#conversations_list_items-include) + for more information. + """ diff --git a/src/openai/types/conversations/item_list_params.py b/src/openai/types/conversations/item_list_params.py new file mode 100644 index 0000000000..a4dd61f399 --- /dev/null +++ b/src/openai/types/conversations/item_list_params.py @@ -0,0 +1,50 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, TypedDict + +from ..responses.response_includable import ResponseIncludable + +__all__ = ["ItemListParams"] + + +class ItemListParams(TypedDict, total=False): + after: str + """An item ID to list items after, used in pagination.""" + + include: List[ResponseIncludable] + """Specify additional output data to include in the model response. + + Currently supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ + + order: Literal["asc", "desc"] + """The order to return the input items in. Default is `desc`. + + - `asc`: Return the input items in ascending order. + - `desc`: Return the input items in descending order. + """ diff --git a/src/openai/types/conversations/item_retrieve_params.py b/src/openai/types/conversations/item_retrieve_params.py new file mode 100644 index 0000000000..8c5db1e533 --- /dev/null +++ b/src/openai/types/conversations/item_retrieve_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Required, TypedDict + +from ..responses.response_includable import ResponseIncludable + +__all__ = ["ItemRetrieveParams"] + + +class ItemRetrieveParams(TypedDict, total=False): + conversation_id: Required[str] + + include: List[ResponseIncludable] + """Additional fields to include in the response. + + See the `include` parameter for + [listing Conversation items above](https://platform.openai.com/docs/api-reference/conversations/list-items#conversations_list_items-include) + for more information. + """ diff --git a/src/openai/types/conversations/message.py b/src/openai/types/conversations/message.py new file mode 100644 index 0000000000..075f5bd290 --- /dev/null +++ b/src/openai/types/conversations/message.py @@ -0,0 +1,78 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .text_content import TextContent +from .summary_text_content import SummaryTextContent +from .computer_screenshot_content import ComputerScreenshotContent +from ..responses.response_input_file import ResponseInputFile +from ..responses.response_input_text import ResponseInputText +from ..responses.response_input_image import ResponseInputImage +from ..responses.response_output_text import ResponseOutputText +from ..responses.response_output_refusal import ResponseOutputRefusal + +__all__ = ["Message", "Content", "ContentReasoningText"] + + +class ContentReasoningText(BaseModel): + """Reasoning text from the model.""" + + text: str + """The reasoning text from the model.""" + + type: Literal["reasoning_text"] + """The type of the reasoning text. Always `reasoning_text`.""" + + +Content: TypeAlias = Annotated[ + Union[ + ResponseInputText, + ResponseOutputText, + TextContent, + SummaryTextContent, + ContentReasoningText, + ResponseOutputRefusal, + ResponseInputImage, + ComputerScreenshotContent, + ResponseInputFile, + ], + PropertyInfo(discriminator="type"), +] + + +class Message(BaseModel): + """A message to or from the model.""" + + id: str + """The unique ID of the message.""" + + content: List[Content] + """The content of the message""" + + role: Literal["unknown", "user", "assistant", "system", "critic", "discriminator", "developer", "tool"] + """The role of the message. + + One of `unknown`, `user`, `assistant`, `system`, `critic`, `discriminator`, + `developer`, or `tool`. + """ + + status: Literal["in_progress", "completed", "incomplete"] + """The status of item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + type: Literal["message"] + """The type of the message. Always set to `message`.""" + + phase: Optional[Literal["commentary", "final_answer"]] = None + """ + Labels an `assistant` message as intermediate commentary (`commentary`) or the + final answer (`final_answer`). For models like `gpt-5.3-codex` and beyond, when + sending follow-up requests, preserve and resend phase on all assistant messages + — dropping it can degrade performance. Not used for user messages. + """ diff --git a/src/openai/types/conversations/output_text_content.py b/src/openai/types/conversations/output_text_content.py new file mode 100644 index 0000000000..cfe9307d74 --- /dev/null +++ b/src/openai/types/conversations/output_text_content.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..responses.response_output_text import ResponseOutputText + +__all__ = ["OutputTextContent"] + +OutputTextContent = ResponseOutputText diff --git a/src/openai/types/conversations/output_text_content_param.py b/src/openai/types/conversations/output_text_content_param.py new file mode 100644 index 0000000000..dc3e2026f6 --- /dev/null +++ b/src/openai/types/conversations/output_text_content_param.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..responses.response_output_text_param import ResponseOutputTextParam + +OutputTextContentParam = ResponseOutputTextParam diff --git a/src/openai/types/conversations/refusal_content.py b/src/openai/types/conversations/refusal_content.py new file mode 100644 index 0000000000..6206c267dc --- /dev/null +++ b/src/openai/types/conversations/refusal_content.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..responses.response_output_refusal import ResponseOutputRefusal + +__all__ = ["RefusalContent"] + +RefusalContent = ResponseOutputRefusal diff --git a/src/openai/types/conversations/refusal_content_param.py b/src/openai/types/conversations/refusal_content_param.py new file mode 100644 index 0000000000..9b83da5f2d --- /dev/null +++ b/src/openai/types/conversations/refusal_content_param.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..responses.response_output_refusal_param import ResponseOutputRefusalParam + +RefusalContentParam = ResponseOutputRefusalParam diff --git a/src/openai/types/conversations/summary_text_content.py b/src/openai/types/conversations/summary_text_content.py new file mode 100644 index 0000000000..6464a36599 --- /dev/null +++ b/src/openai/types/conversations/summary_text_content.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["SummaryTextContent"] + + +class SummaryTextContent(BaseModel): + """A summary text from the model.""" + + text: str + """A summary of the reasoning output from the model so far.""" + + type: Literal["summary_text"] + """The type of the object. Always `summary_text`.""" diff --git a/src/openai/types/conversations/text_content.py b/src/openai/types/conversations/text_content.py new file mode 100644 index 0000000000..e602466c47 --- /dev/null +++ b/src/openai/types/conversations/text_content.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["TextContent"] + + +class TextContent(BaseModel): + """A text content.""" + + text: str + + type: Literal["text"] diff --git a/src/openai/types/create_embedding_response.py b/src/openai/types/create_embedding_response.py index eff247a112..314a7f9afc 100644 --- a/src/openai/types/create_embedding_response.py +++ b/src/openai/types/create_embedding_response.py @@ -10,6 +10,8 @@ class Usage(BaseModel): + """The usage information for the request.""" + prompt_tokens: int """The number of tokens used by the prompt.""" diff --git a/src/openai/types/deleted_skill.py b/src/openai/types/deleted_skill.py new file mode 100644 index 0000000000..6f02f58ca4 --- /dev/null +++ b/src/openai/types/deleted_skill.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["DeletedSkill"] + + +class DeletedSkill(BaseModel): + id: str + + deleted: bool + + object: Literal["skill.deleted"] diff --git a/src/openai/types/embedding.py b/src/openai/types/embedding.py index 769b1d165f..fbffec01e0 100644 --- a/src/openai/types/embedding.py +++ b/src/openai/types/embedding.py @@ -9,6 +9,8 @@ class Embedding(BaseModel): + """Represents an embedding vector returned by embedding endpoint.""" + embedding: List[float] """The embedding vector, which is a list of floats. diff --git a/src/openai/types/embedding_create_params.py b/src/openai/types/embedding_create_params.py index 930b3b7914..ab3e877964 100644 --- a/src/openai/types/embedding_create_params.py +++ b/src/openai/types/embedding_create_params.py @@ -2,32 +2,37 @@ from __future__ import annotations -from typing import List, Union, Iterable +from typing import Union, Iterable from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr +from .embedding_model import EmbeddingModel + __all__ = ["EmbeddingCreateParams"] class EmbeddingCreateParams(TypedDict, total=False): - input: Required[Union[str, List[str], Iterable[int], Iterable[Iterable[int]]]] + input: Required[Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]]]] """Input text to embed, encoded as a string or array of tokens. To embed multiple inputs in a single request, pass an array of strings or array of token arrays. The input must not exceed the max input tokens for the model - (8192 tokens for `text-embedding-ada-002`), cannot be an empty string, and any - array must be 2048 dimensions or less. + (8192 tokens for all embedding models), cannot be an empty string, and any array + must be 2048 dimensions or less. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) - for counting tokens. + for counting tokens. In addition to the per-input token limit, all embedding + models enforce a maximum of 300,000 tokens summed across all inputs in a single + request. """ - model: Required[Union[str, Literal["text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large"]]] + model: Required[Union[str, EmbeddingModel]] """ID of the model to use. You can use the [List models](https://platform.openai.com/docs/api-reference/models/list) API to see all of your available models, or see our - [Model overview](https://platform.openai.com/docs/models/overview) for - descriptions of them. + [Model overview](https://platform.openai.com/docs/models) for descriptions of + them. """ dimensions: int @@ -46,5 +51,5 @@ class EmbeddingCreateParams(TypedDict, total=False): """ A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). """ diff --git a/src/openai/types/embedding_model.py b/src/openai/types/embedding_model.py new file mode 100644 index 0000000000..075ff97644 --- /dev/null +++ b/src/openai/types/embedding_model.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["EmbeddingModel"] + +EmbeddingModel: TypeAlias = Literal["text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large"] diff --git a/src/openai/types/eval_create_params.py b/src/openai/types/eval_create_params.py new file mode 100644 index 0000000000..a1d5ea5371 --- /dev/null +++ b/src/openai/types/eval_create_params.py @@ -0,0 +1,244 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .._types import SequenceNotStr +from .shared_params.metadata import Metadata +from .graders.grader_inputs_param import GraderInputsParam +from .graders.python_grader_param import PythonGraderParam +from .graders.score_model_grader_param import ScoreModelGraderParam +from .graders.string_check_grader_param import StringCheckGraderParam +from .responses.response_input_text_param import ResponseInputTextParam +from .graders.text_similarity_grader_param import TextSimilarityGraderParam +from .responses.response_input_audio_param import ResponseInputAudioParam + +__all__ = [ + "EvalCreateParams", + "DataSourceConfig", + "DataSourceConfigCustom", + "DataSourceConfigLogs", + "DataSourceConfigStoredCompletions", + "TestingCriterion", + "TestingCriterionLabelModel", + "TestingCriterionLabelModelInput", + "TestingCriterionLabelModelInputSimpleInputMessage", + "TestingCriterionLabelModelInputEvalItem", + "TestingCriterionLabelModelInputEvalItemContent", + "TestingCriterionLabelModelInputEvalItemContentOutputText", + "TestingCriterionLabelModelInputEvalItemContentInputImage", + "TestingCriterionTextSimilarity", + "TestingCriterionPython", + "TestingCriterionScoreModel", +] + + +class EvalCreateParams(TypedDict, total=False): + data_source_config: Required[DataSourceConfig] + """The configuration for the data source used for the evaluation runs. + + Dictates the schema of the data used in the evaluation. + """ + + testing_criteria: Required[Iterable[TestingCriterion]] + """A list of graders for all eval runs in this group. + + Graders can reference variables in the data source using double curly braces + notation, like `{{item.variable_name}}`. To reference the model's output, use + the `sample` namespace (ie, `{{sample.output_text}}`). + """ + + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + name: str + """The name of the evaluation.""" + + +class DataSourceConfigCustom(TypedDict, total=False): + """ + A CustomDataSourceConfig object that defines the schema for the data source used for the evaluation runs. + This schema is used to define the shape of the data that will be: + - Used to define your testing criteria and + - What data is required when creating a run + """ + + item_schema: Required[Dict[str, object]] + """The json schema for each row in the data source.""" + + type: Required[Literal["custom"]] + """The type of data source. Always `custom`.""" + + include_sample_schema: bool + """ + Whether the eval should expect you to populate the sample namespace (ie, by + generating responses off of your data source) + """ + + +class DataSourceConfigLogs(TypedDict, total=False): + """ + A data source config which specifies the metadata property of your logs query. + This is usually metadata like `usecase=chatbot` or `prompt-version=v2`, etc. + """ + + type: Required[Literal["logs"]] + """The type of data source. Always `logs`.""" + + metadata: Dict[str, object] + """Metadata filters for the logs data source.""" + + +class DataSourceConfigStoredCompletions(TypedDict, total=False): + """Deprecated in favor of LogsDataSourceConfig.""" + + type: Required[Literal["stored_completions"]] + """The type of data source. Always `stored_completions`.""" + + metadata: Dict[str, object] + """Metadata filters for the stored completions data source.""" + + +DataSourceConfig: TypeAlias = Union[DataSourceConfigCustom, DataSourceConfigLogs, DataSourceConfigStoredCompletions] + + +class TestingCriterionLabelModelInputSimpleInputMessage(TypedDict, total=False): + content: Required[str] + """The content of the message.""" + + role: Required[str] + """The role of the message (e.g. "system", "assistant", "user").""" + + +class TestingCriterionLabelModelInputEvalItemContentOutputText(TypedDict, total=False): + """A text output from the model.""" + + text: Required[str] + """The text output from the model.""" + + type: Required[Literal["output_text"]] + """The type of the output text. Always `output_text`.""" + + +class TestingCriterionLabelModelInputEvalItemContentInputImage(TypedDict, total=False): + """An image input block used within EvalItem content arrays.""" + + image_url: Required[str] + """The URL of the image input.""" + + type: Required[Literal["input_image"]] + """The type of the image input. Always `input_image`.""" + + detail: str + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +TestingCriterionLabelModelInputEvalItemContent: TypeAlias = Union[ + str, + ResponseInputTextParam, + TestingCriterionLabelModelInputEvalItemContentOutputText, + TestingCriterionLabelModelInputEvalItemContentInputImage, + ResponseInputAudioParam, + GraderInputsParam, +] + + +class TestingCriterionLabelModelInputEvalItem(TypedDict, total=False): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: Required[TestingCriterionLabelModelInputEvalItemContent] + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Required[Literal["user", "assistant", "system", "developer"]] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Literal["message"] + """The type of the message input. Always `message`.""" + + +TestingCriterionLabelModelInput: TypeAlias = Union[ + TestingCriterionLabelModelInputSimpleInputMessage, TestingCriterionLabelModelInputEvalItem +] + + +class TestingCriterionLabelModel(TypedDict, total=False): + """ + A LabelModelGrader object which uses a model to assign labels to each item + in the evaluation. + """ + + input: Required[Iterable[TestingCriterionLabelModelInput]] + """A list of chat messages forming the prompt or context. + + May include variable references to the `item` namespace, ie {{item.name}}. + """ + + labels: Required[SequenceNotStr[str]] + """The labels to classify to each item in the evaluation.""" + + model: Required[str] + """The model to use for the evaluation. Must support structured outputs.""" + + name: Required[str] + """The name of the grader.""" + + passing_labels: Required[SequenceNotStr[str]] + """The labels that indicate a passing result. Must be a subset of labels.""" + + type: Required[Literal["label_model"]] + """The object type, which is always `label_model`.""" + + +class TestingCriterionTextSimilarity(TextSimilarityGraderParam, total=False): + """A TextSimilarityGrader object which grades text based on similarity metrics.""" + + pass_threshold: Required[float] + """The threshold for the score.""" + + +class TestingCriterionPython(PythonGraderParam, total=False): + """A PythonGrader object that runs a python script on the input.""" + + pass_threshold: float + """The threshold for the score.""" + + +class TestingCriterionScoreModel(ScoreModelGraderParam, total=False): + """A ScoreModelGrader object that uses a model to assign a score to the input.""" + + pass_threshold: float + """The threshold for the score.""" + + +TestingCriterion: TypeAlias = Union[ + TestingCriterionLabelModel, + StringCheckGraderParam, + TestingCriterionTextSimilarity, + TestingCriterionPython, + TestingCriterionScoreModel, +] diff --git a/src/openai/types/eval_create_response.py b/src/openai/types/eval_create_response.py new file mode 100644 index 0000000000..f3166422ba --- /dev/null +++ b/src/openai/types/eval_create_response.py @@ -0,0 +1,130 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from .._utils import PropertyInfo +from .._models import BaseModel +from .shared.metadata import Metadata +from .graders.python_grader import PythonGrader +from .graders.label_model_grader import LabelModelGrader +from .graders.score_model_grader import ScoreModelGrader +from .graders.string_check_grader import StringCheckGrader +from .eval_custom_data_source_config import EvalCustomDataSourceConfig +from .graders.text_similarity_grader import TextSimilarityGrader +from .eval_stored_completions_data_source_config import EvalStoredCompletionsDataSourceConfig + +__all__ = [ + "EvalCreateResponse", + "DataSourceConfig", + "DataSourceConfigLogs", + "TestingCriterion", + "TestingCriterionEvalGraderTextSimilarity", + "TestingCriterionEvalGraderPython", + "TestingCriterionEvalGraderScoreModel", +] + + +class DataSourceConfigLogs(BaseModel): + """ + A LogsDataSourceConfig which specifies the metadata property of your logs query. + This is usually metadata like `usecase=chatbot` or `prompt-version=v2`, etc. + The schema returned by this data source config is used to defined what variables are available in your evals. + `item` and `sample` are both defined when using this data source config. + """ + + schema_: Dict[str, object] = FieldInfo(alias="schema") + """ + The json schema for the run data source items. Learn how to build JSON schemas + [here](https://json-schema.org/). + """ + + type: Literal["logs"] + """The type of data source. Always `logs`.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + +DataSourceConfig: TypeAlias = Annotated[ + Union[EvalCustomDataSourceConfig, DataSourceConfigLogs, EvalStoredCompletionsDataSourceConfig], + PropertyInfo(discriminator="type"), +] + + +class TestingCriterionEvalGraderTextSimilarity(TextSimilarityGrader): + __test__ = False + """A TextSimilarityGrader object which grades text based on similarity metrics.""" + pass_threshold: float + """The threshold for the score.""" + + +class TestingCriterionEvalGraderPython(PythonGrader): + __test__ = False + """A PythonGrader object that runs a python script on the input.""" + pass_threshold: Optional[float] = None + """The threshold for the score.""" + + +class TestingCriterionEvalGraderScoreModel(ScoreModelGrader): + __test__ = False + """A ScoreModelGrader object that uses a model to assign a score to the input.""" + pass_threshold: Optional[float] = None + """The threshold for the score.""" + + +TestingCriterion: TypeAlias = Union[ + LabelModelGrader, + StringCheckGrader, + TestingCriterionEvalGraderTextSimilarity, + TestingCriterionEvalGraderPython, + TestingCriterionEvalGraderScoreModel, +] + + +class EvalCreateResponse(BaseModel): + """ + An Eval object with a data source config and testing criteria. + An Eval represents a task to be done for your LLM integration. + Like: + - Improve the quality of my chatbot + - See how well my chatbot handles customer support + - Check if o4-mini is better at my usecase than gpt-4o + """ + + id: str + """Unique identifier for the evaluation.""" + + created_at: int + """The Unix timestamp (in seconds) for when the eval was created.""" + + data_source_config: DataSourceConfig + """Configuration of data sources used in runs of the evaluation.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + name: str + """The name of the evaluation.""" + + object: Literal["eval"] + """The object type.""" + + testing_criteria: List[TestingCriterion] + """A list of testing criteria.""" diff --git a/src/openai/types/eval_custom_data_source_config.py b/src/openai/types/eval_custom_data_source_config.py new file mode 100644 index 0000000000..6234c4f47a --- /dev/null +++ b/src/openai/types/eval_custom_data_source_config.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["EvalCustomDataSourceConfig"] + + +class EvalCustomDataSourceConfig(BaseModel): + """ + A CustomDataSourceConfig which specifies the schema of your `item` and optionally `sample` namespaces. + The response schema defines the shape of the data that will be: + - Used to define your testing criteria and + - What data is required when creating a run + """ + + schema_: Dict[str, object] = FieldInfo(alias="schema") + """ + The json schema for the run data source items. Learn how to build JSON schemas + [here](https://json-schema.org/). + """ + + type: Literal["custom"] + """The type of data source. Always `custom`.""" diff --git a/src/openai/types/eval_delete_response.py b/src/openai/types/eval_delete_response.py new file mode 100644 index 0000000000..a27261e242 --- /dev/null +++ b/src/openai/types/eval_delete_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["EvalDeleteResponse"] + + +class EvalDeleteResponse(BaseModel): + deleted: bool + + eval_id: str + + object: str diff --git a/src/openai/types/eval_list_params.py b/src/openai/types/eval_list_params.py new file mode 100644 index 0000000000..d9a12d0ddf --- /dev/null +++ b/src/openai/types/eval_list_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["EvalListParams"] + + +class EvalListParams(TypedDict, total=False): + after: str + """Identifier for the last eval from the previous pagination request.""" + + limit: int + """Number of evals to retrieve.""" + + order: Literal["asc", "desc"] + """Sort order for evals by timestamp. + + Use `asc` for ascending order or `desc` for descending order. + """ + + order_by: Literal["created_at", "updated_at"] + """Evals can be ordered by creation time or last updated time. + + Use `created_at` for creation time or `updated_at` for last updated time. + """ diff --git a/src/openai/types/eval_list_response.py b/src/openai/types/eval_list_response.py new file mode 100644 index 0000000000..7cd92c5a09 --- /dev/null +++ b/src/openai/types/eval_list_response.py @@ -0,0 +1,130 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from .._utils import PropertyInfo +from .._models import BaseModel +from .shared.metadata import Metadata +from .graders.python_grader import PythonGrader +from .graders.label_model_grader import LabelModelGrader +from .graders.score_model_grader import ScoreModelGrader +from .graders.string_check_grader import StringCheckGrader +from .eval_custom_data_source_config import EvalCustomDataSourceConfig +from .graders.text_similarity_grader import TextSimilarityGrader +from .eval_stored_completions_data_source_config import EvalStoredCompletionsDataSourceConfig + +__all__ = [ + "EvalListResponse", + "DataSourceConfig", + "DataSourceConfigLogs", + "TestingCriterion", + "TestingCriterionEvalGraderTextSimilarity", + "TestingCriterionEvalGraderPython", + "TestingCriterionEvalGraderScoreModel", +] + + +class DataSourceConfigLogs(BaseModel): + """ + A LogsDataSourceConfig which specifies the metadata property of your logs query. + This is usually metadata like `usecase=chatbot` or `prompt-version=v2`, etc. + The schema returned by this data source config is used to defined what variables are available in your evals. + `item` and `sample` are both defined when using this data source config. + """ + + schema_: Dict[str, object] = FieldInfo(alias="schema") + """ + The json schema for the run data source items. Learn how to build JSON schemas + [here](https://json-schema.org/). + """ + + type: Literal["logs"] + """The type of data source. Always `logs`.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + +DataSourceConfig: TypeAlias = Annotated[ + Union[EvalCustomDataSourceConfig, DataSourceConfigLogs, EvalStoredCompletionsDataSourceConfig], + PropertyInfo(discriminator="type"), +] + + +class TestingCriterionEvalGraderTextSimilarity(TextSimilarityGrader): + __test__ = False + """A TextSimilarityGrader object which grades text based on similarity metrics.""" + pass_threshold: float + """The threshold for the score.""" + + +class TestingCriterionEvalGraderPython(PythonGrader): + __test__ = False + """A PythonGrader object that runs a python script on the input.""" + pass_threshold: Optional[float] = None + """The threshold for the score.""" + + +class TestingCriterionEvalGraderScoreModel(ScoreModelGrader): + __test__ = False + """A ScoreModelGrader object that uses a model to assign a score to the input.""" + pass_threshold: Optional[float] = None + """The threshold for the score.""" + + +TestingCriterion: TypeAlias = Union[ + LabelModelGrader, + StringCheckGrader, + TestingCriterionEvalGraderTextSimilarity, + TestingCriterionEvalGraderPython, + TestingCriterionEvalGraderScoreModel, +] + + +class EvalListResponse(BaseModel): + """ + An Eval object with a data source config and testing criteria. + An Eval represents a task to be done for your LLM integration. + Like: + - Improve the quality of my chatbot + - See how well my chatbot handles customer support + - Check if o4-mini is better at my usecase than gpt-4o + """ + + id: str + """Unique identifier for the evaluation.""" + + created_at: int + """The Unix timestamp (in seconds) for when the eval was created.""" + + data_source_config: DataSourceConfig + """Configuration of data sources used in runs of the evaluation.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + name: str + """The name of the evaluation.""" + + object: Literal["eval"] + """The object type.""" + + testing_criteria: List[TestingCriterion] + """A list of testing criteria.""" diff --git a/src/openai/types/eval_retrieve_response.py b/src/openai/types/eval_retrieve_response.py new file mode 100644 index 0000000000..56db7d6bc1 --- /dev/null +++ b/src/openai/types/eval_retrieve_response.py @@ -0,0 +1,130 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from .._utils import PropertyInfo +from .._models import BaseModel +from .shared.metadata import Metadata +from .graders.python_grader import PythonGrader +from .graders.label_model_grader import LabelModelGrader +from .graders.score_model_grader import ScoreModelGrader +from .graders.string_check_grader import StringCheckGrader +from .eval_custom_data_source_config import EvalCustomDataSourceConfig +from .graders.text_similarity_grader import TextSimilarityGrader +from .eval_stored_completions_data_source_config import EvalStoredCompletionsDataSourceConfig + +__all__ = [ + "EvalRetrieveResponse", + "DataSourceConfig", + "DataSourceConfigLogs", + "TestingCriterion", + "TestingCriterionEvalGraderTextSimilarity", + "TestingCriterionEvalGraderPython", + "TestingCriterionEvalGraderScoreModel", +] + + +class DataSourceConfigLogs(BaseModel): + """ + A LogsDataSourceConfig which specifies the metadata property of your logs query. + This is usually metadata like `usecase=chatbot` or `prompt-version=v2`, etc. + The schema returned by this data source config is used to defined what variables are available in your evals. + `item` and `sample` are both defined when using this data source config. + """ + + schema_: Dict[str, object] = FieldInfo(alias="schema") + """ + The json schema for the run data source items. Learn how to build JSON schemas + [here](https://json-schema.org/). + """ + + type: Literal["logs"] + """The type of data source. Always `logs`.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + +DataSourceConfig: TypeAlias = Annotated[ + Union[EvalCustomDataSourceConfig, DataSourceConfigLogs, EvalStoredCompletionsDataSourceConfig], + PropertyInfo(discriminator="type"), +] + + +class TestingCriterionEvalGraderTextSimilarity(TextSimilarityGrader): + __test__ = False + """A TextSimilarityGrader object which grades text based on similarity metrics.""" + pass_threshold: float + """The threshold for the score.""" + + +class TestingCriterionEvalGraderPython(PythonGrader): + __test__ = False + """A PythonGrader object that runs a python script on the input.""" + pass_threshold: Optional[float] = None + """The threshold for the score.""" + + +class TestingCriterionEvalGraderScoreModel(ScoreModelGrader): + __test__ = False + """A ScoreModelGrader object that uses a model to assign a score to the input.""" + pass_threshold: Optional[float] = None + """The threshold for the score.""" + + +TestingCriterion: TypeAlias = Union[ + LabelModelGrader, + StringCheckGrader, + TestingCriterionEvalGraderTextSimilarity, + TestingCriterionEvalGraderPython, + TestingCriterionEvalGraderScoreModel, +] + + +class EvalRetrieveResponse(BaseModel): + """ + An Eval object with a data source config and testing criteria. + An Eval represents a task to be done for your LLM integration. + Like: + - Improve the quality of my chatbot + - See how well my chatbot handles customer support + - Check if o4-mini is better at my usecase than gpt-4o + """ + + id: str + """Unique identifier for the evaluation.""" + + created_at: int + """The Unix timestamp (in seconds) for when the eval was created.""" + + data_source_config: DataSourceConfig + """Configuration of data sources used in runs of the evaluation.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + name: str + """The name of the evaluation.""" + + object: Literal["eval"] + """The object type.""" + + testing_criteria: List[TestingCriterion] + """A list of testing criteria.""" diff --git a/src/openai/types/eval_stored_completions_data_source_config.py b/src/openai/types/eval_stored_completions_data_source_config.py new file mode 100644 index 0000000000..d11f6ae14c --- /dev/null +++ b/src/openai/types/eval_stored_completions_data_source_config.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.metadata import Metadata + +__all__ = ["EvalStoredCompletionsDataSourceConfig"] + + +class EvalStoredCompletionsDataSourceConfig(BaseModel): + """Deprecated in favor of LogsDataSourceConfig.""" + + schema_: Dict[str, object] = FieldInfo(alias="schema") + """ + The json schema for the run data source items. Learn how to build JSON schemas + [here](https://json-schema.org/). + """ + + type: Literal["stored_completions"] + """The type of data source. Always `stored_completions`.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ diff --git a/src/openai/types/eval_update_params.py b/src/openai/types/eval_update_params.py new file mode 100644 index 0000000000..042db29af5 --- /dev/null +++ b/src/openai/types/eval_update_params.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +from .shared_params.metadata import Metadata + +__all__ = ["EvalUpdateParams"] + + +class EvalUpdateParams(TypedDict, total=False): + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + name: str + """Rename the evaluation.""" diff --git a/src/openai/types/eval_update_response.py b/src/openai/types/eval_update_response.py new file mode 100644 index 0000000000..30d4dbc3a1 --- /dev/null +++ b/src/openai/types/eval_update_response.py @@ -0,0 +1,130 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from .._utils import PropertyInfo +from .._models import BaseModel +from .shared.metadata import Metadata +from .graders.python_grader import PythonGrader +from .graders.label_model_grader import LabelModelGrader +from .graders.score_model_grader import ScoreModelGrader +from .graders.string_check_grader import StringCheckGrader +from .eval_custom_data_source_config import EvalCustomDataSourceConfig +from .graders.text_similarity_grader import TextSimilarityGrader +from .eval_stored_completions_data_source_config import EvalStoredCompletionsDataSourceConfig + +__all__ = [ + "EvalUpdateResponse", + "DataSourceConfig", + "DataSourceConfigLogs", + "TestingCriterion", + "TestingCriterionEvalGraderTextSimilarity", + "TestingCriterionEvalGraderPython", + "TestingCriterionEvalGraderScoreModel", +] + + +class DataSourceConfigLogs(BaseModel): + """ + A LogsDataSourceConfig which specifies the metadata property of your logs query. + This is usually metadata like `usecase=chatbot` or `prompt-version=v2`, etc. + The schema returned by this data source config is used to defined what variables are available in your evals. + `item` and `sample` are both defined when using this data source config. + """ + + schema_: Dict[str, object] = FieldInfo(alias="schema") + """ + The json schema for the run data source items. Learn how to build JSON schemas + [here](https://json-schema.org/). + """ + + type: Literal["logs"] + """The type of data source. Always `logs`.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + +DataSourceConfig: TypeAlias = Annotated[ + Union[EvalCustomDataSourceConfig, DataSourceConfigLogs, EvalStoredCompletionsDataSourceConfig], + PropertyInfo(discriminator="type"), +] + + +class TestingCriterionEvalGraderTextSimilarity(TextSimilarityGrader): + __test__ = False + """A TextSimilarityGrader object which grades text based on similarity metrics.""" + pass_threshold: float + """The threshold for the score.""" + + +class TestingCriterionEvalGraderPython(PythonGrader): + __test__ = False + """A PythonGrader object that runs a python script on the input.""" + pass_threshold: Optional[float] = None + """The threshold for the score.""" + + +class TestingCriterionEvalGraderScoreModel(ScoreModelGrader): + __test__ = False + """A ScoreModelGrader object that uses a model to assign a score to the input.""" + pass_threshold: Optional[float] = None + """The threshold for the score.""" + + +TestingCriterion: TypeAlias = Union[ + LabelModelGrader, + StringCheckGrader, + TestingCriterionEvalGraderTextSimilarity, + TestingCriterionEvalGraderPython, + TestingCriterionEvalGraderScoreModel, +] + + +class EvalUpdateResponse(BaseModel): + """ + An Eval object with a data source config and testing criteria. + An Eval represents a task to be done for your LLM integration. + Like: + - Improve the quality of my chatbot + - See how well my chatbot handles customer support + - Check if o4-mini is better at my usecase than gpt-4o + """ + + id: str + """Unique identifier for the evaluation.""" + + created_at: int + """The Unix timestamp (in seconds) for when the eval was created.""" + + data_source_config: DataSourceConfig + """Configuration of data sources used in runs of the evaluation.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + name: str + """The name of the evaluation.""" + + object: Literal["eval"] + """The object type.""" + + testing_criteria: List[TestingCriterion] + """A list of testing criteria.""" diff --git a/src/openai/types/evals/__init__.py b/src/openai/types/evals/__init__.py new file mode 100644 index 0000000000..ebf84c6b8d --- /dev/null +++ b/src/openai/types/evals/__init__.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .eval_api_error import EvalAPIError as EvalAPIError +from .run_list_params import RunListParams as RunListParams +from .run_create_params import RunCreateParams as RunCreateParams +from .run_list_response import RunListResponse as RunListResponse +from .run_cancel_response import RunCancelResponse as RunCancelResponse +from .run_create_response import RunCreateResponse as RunCreateResponse +from .run_delete_response import RunDeleteResponse as RunDeleteResponse +from .run_retrieve_response import RunRetrieveResponse as RunRetrieveResponse +from .create_eval_jsonl_run_data_source import CreateEvalJSONLRunDataSource as CreateEvalJSONLRunDataSource +from .create_eval_completions_run_data_source import ( + CreateEvalCompletionsRunDataSource as CreateEvalCompletionsRunDataSource, +) +from .create_eval_jsonl_run_data_source_param import ( + CreateEvalJSONLRunDataSourceParam as CreateEvalJSONLRunDataSourceParam, +) +from .create_eval_completions_run_data_source_param import ( + CreateEvalCompletionsRunDataSourceParam as CreateEvalCompletionsRunDataSourceParam, +) diff --git a/src/openai/types/evals/create_eval_completions_run_data_source.py b/src/openai/types/evals/create_eval_completions_run_data_source.py new file mode 100644 index 0000000000..726ae6abf0 --- /dev/null +++ b/src/openai/types/evals/create_eval_completions_run_data_source.py @@ -0,0 +1,258 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from ..shared.metadata import Metadata +from ..graders.grader_inputs import GraderInputs +from ..shared.reasoning_effort import ReasoningEffort +from ..shared.response_format_text import ResponseFormatText +from ..responses.easy_input_message import EasyInputMessage +from ..responses.response_input_text import ResponseInputText +from ..responses.response_input_audio import ResponseInputAudio +from ..chat.chat_completion_function_tool import ChatCompletionFunctionTool +from ..shared.response_format_json_object import ResponseFormatJSONObject +from ..shared.response_format_json_schema import ResponseFormatJSONSchema + +__all__ = [ + "CreateEvalCompletionsRunDataSource", + "Source", + "SourceFileContent", + "SourceFileContentContent", + "SourceFileID", + "SourceStoredCompletions", + "InputMessages", + "InputMessagesTemplate", + "InputMessagesTemplateTemplate", + "InputMessagesTemplateTemplateEvalItem", + "InputMessagesTemplateTemplateEvalItemContent", + "InputMessagesTemplateTemplateEvalItemContentOutputText", + "InputMessagesTemplateTemplateEvalItemContentInputImage", + "InputMessagesItemReference", + "SamplingParams", + "SamplingParamsResponseFormat", +] + + +class SourceFileContentContent(BaseModel): + item: Dict[str, object] + + sample: Optional[Dict[str, object]] = None + + +class SourceFileContent(BaseModel): + content: List[SourceFileContentContent] + """The content of the jsonl file.""" + + type: Literal["file_content"] + """The type of jsonl source. Always `file_content`.""" + + +class SourceFileID(BaseModel): + id: str + """The identifier of the file.""" + + type: Literal["file_id"] + """The type of jsonl source. Always `file_id`.""" + + +class SourceStoredCompletions(BaseModel): + """A StoredCompletionsRunDataSource configuration describing a set of filters""" + + type: Literal["stored_completions"] + """The type of source. Always `stored_completions`.""" + + created_after: Optional[int] = None + """An optional Unix timestamp to filter items created after this time.""" + + created_before: Optional[int] = None + """An optional Unix timestamp to filter items created before this time.""" + + limit: Optional[int] = None + """An optional maximum number of items to return.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: Optional[str] = None + """An optional model to filter by (e.g., 'gpt-4o').""" + + +Source: TypeAlias = Annotated[ + Union[SourceFileContent, SourceFileID, SourceStoredCompletions], PropertyInfo(discriminator="type") +] + + +class InputMessagesTemplateTemplateEvalItemContentOutputText(BaseModel): + """A text output from the model.""" + + text: str + """The text output from the model.""" + + type: Literal["output_text"] + """The type of the output text. Always `output_text`.""" + + +class InputMessagesTemplateTemplateEvalItemContentInputImage(BaseModel): + """An image input block used within EvalItem content arrays.""" + + image_url: str + """The URL of the image input.""" + + type: Literal["input_image"] + """The type of the image input. Always `input_image`.""" + + detail: Optional[str] = None + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +InputMessagesTemplateTemplateEvalItemContent: TypeAlias = Union[ + str, + ResponseInputText, + InputMessagesTemplateTemplateEvalItemContentOutputText, + InputMessagesTemplateTemplateEvalItemContentInputImage, + ResponseInputAudio, + GraderInputs, +] + + +class InputMessagesTemplateTemplateEvalItem(BaseModel): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: InputMessagesTemplateTemplateEvalItemContent + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Literal["user", "assistant", "system", "developer"] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Optional[Literal["message"]] = None + """The type of the message input. Always `message`.""" + + +InputMessagesTemplateTemplate: TypeAlias = Union[EasyInputMessage, InputMessagesTemplateTemplateEvalItem] + + +class InputMessagesTemplate(BaseModel): + template: List[InputMessagesTemplateTemplate] + """A list of chat messages forming the prompt or context. + + May include variable references to the `item` namespace, ie {{item.name}}. + """ + + type: Literal["template"] + """The type of input messages. Always `template`.""" + + +class InputMessagesItemReference(BaseModel): + item_reference: str + """A reference to a variable in the `item` namespace. Ie, "item.input_trajectory" """ + + type: Literal["item_reference"] + """The type of input messages. Always `item_reference`.""" + + +InputMessages: TypeAlias = Annotated[ + Union[InputMessagesTemplate, InputMessagesItemReference], PropertyInfo(discriminator="type") +] + +SamplingParamsResponseFormat: TypeAlias = Union[ResponseFormatText, ResponseFormatJSONSchema, ResponseFormatJSONObject] + + +class SamplingParams(BaseModel): + max_completion_tokens: Optional[int] = None + """The maximum number of tokens in the generated output.""" + + reasoning_effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + response_format: Optional[SamplingParamsResponseFormat] = None + """An object specifying the format that the model must output. + + Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured + Outputs which ensures the model will match your supplied JSON schema. Learn more + in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ + + seed: Optional[int] = None + """A seed value to initialize the randomness, during sampling.""" + + temperature: Optional[float] = None + """A higher temperature increases randomness in the outputs.""" + + tools: Optional[List[ChatCompletionFunctionTool]] = None + """A list of tools the model may call. + + Currently, only functions are supported as a tool. Use this to provide a list of + functions the model may generate JSON inputs for. A max of 128 functions are + supported. + """ + + top_p: Optional[float] = None + """An alternative to temperature for nucleus sampling; 1.0 includes all tokens.""" + + +class CreateEvalCompletionsRunDataSource(BaseModel): + """A CompletionsRunDataSource object describing a model sampling configuration.""" + + source: Source + """Determines what populates the `item` namespace in this run's data source.""" + + type: Literal["completions"] + """The type of run data source. Always `completions`.""" + + input_messages: Optional[InputMessages] = None + """Used when sampling from a model. + + Dictates the structure of the messages passed into the model. Can either be a + reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template + with variable references to the `item` namespace. + """ + + model: Optional[str] = None + """The name of the model to use for generating completions (e.g. "o3-mini").""" + + sampling_params: Optional[SamplingParams] = None diff --git a/src/openai/types/evals/create_eval_completions_run_data_source_param.py b/src/openai/types/evals/create_eval_completions_run_data_source_param.py new file mode 100644 index 0000000000..6842f84af9 --- /dev/null +++ b/src/openai/types/evals/create_eval_completions_run_data_source_param.py @@ -0,0 +1,254 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..shared_params.metadata import Metadata +from ..shared.reasoning_effort import ReasoningEffort +from ..graders.grader_inputs_param import GraderInputsParam +from ..responses.easy_input_message_param import EasyInputMessageParam +from ..shared_params.response_format_text import ResponseFormatText +from ..responses.response_input_text_param import ResponseInputTextParam +from ..responses.response_input_audio_param import ResponseInputAudioParam +from ..chat.chat_completion_function_tool_param import ChatCompletionFunctionToolParam +from ..shared_params.response_format_json_object import ResponseFormatJSONObject +from ..shared_params.response_format_json_schema import ResponseFormatJSONSchema + +__all__ = [ + "CreateEvalCompletionsRunDataSourceParam", + "Source", + "SourceFileContent", + "SourceFileContentContent", + "SourceFileID", + "SourceStoredCompletions", + "InputMessages", + "InputMessagesTemplate", + "InputMessagesTemplateTemplate", + "InputMessagesTemplateTemplateEvalItem", + "InputMessagesTemplateTemplateEvalItemContent", + "InputMessagesTemplateTemplateEvalItemContentOutputText", + "InputMessagesTemplateTemplateEvalItemContentInputImage", + "InputMessagesItemReference", + "SamplingParams", + "SamplingParamsResponseFormat", +] + + +class SourceFileContentContent(TypedDict, total=False): + item: Required[Dict[str, object]] + + sample: Dict[str, object] + + +class SourceFileContent(TypedDict, total=False): + content: Required[Iterable[SourceFileContentContent]] + """The content of the jsonl file.""" + + type: Required[Literal["file_content"]] + """The type of jsonl source. Always `file_content`.""" + + +class SourceFileID(TypedDict, total=False): + id: Required[str] + """The identifier of the file.""" + + type: Required[Literal["file_id"]] + """The type of jsonl source. Always `file_id`.""" + + +class SourceStoredCompletions(TypedDict, total=False): + """A StoredCompletionsRunDataSource configuration describing a set of filters""" + + type: Required[Literal["stored_completions"]] + """The type of source. Always `stored_completions`.""" + + created_after: Optional[int] + """An optional Unix timestamp to filter items created after this time.""" + + created_before: Optional[int] + """An optional Unix timestamp to filter items created before this time.""" + + limit: Optional[int] + """An optional maximum number of items to return.""" + + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: Optional[str] + """An optional model to filter by (e.g., 'gpt-4o').""" + + +Source: TypeAlias = Union[SourceFileContent, SourceFileID, SourceStoredCompletions] + + +class InputMessagesTemplateTemplateEvalItemContentOutputText(TypedDict, total=False): + """A text output from the model.""" + + text: Required[str] + """The text output from the model.""" + + type: Required[Literal["output_text"]] + """The type of the output text. Always `output_text`.""" + + +class InputMessagesTemplateTemplateEvalItemContentInputImage(TypedDict, total=False): + """An image input block used within EvalItem content arrays.""" + + image_url: Required[str] + """The URL of the image input.""" + + type: Required[Literal["input_image"]] + """The type of the image input. Always `input_image`.""" + + detail: str + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +InputMessagesTemplateTemplateEvalItemContent: TypeAlias = Union[ + str, + ResponseInputTextParam, + InputMessagesTemplateTemplateEvalItemContentOutputText, + InputMessagesTemplateTemplateEvalItemContentInputImage, + ResponseInputAudioParam, + GraderInputsParam, +] + + +class InputMessagesTemplateTemplateEvalItem(TypedDict, total=False): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: Required[InputMessagesTemplateTemplateEvalItemContent] + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Required[Literal["user", "assistant", "system", "developer"]] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Literal["message"] + """The type of the message input. Always `message`.""" + + +InputMessagesTemplateTemplate: TypeAlias = Union[EasyInputMessageParam, InputMessagesTemplateTemplateEvalItem] + + +class InputMessagesTemplate(TypedDict, total=False): + template: Required[Iterable[InputMessagesTemplateTemplate]] + """A list of chat messages forming the prompt or context. + + May include variable references to the `item` namespace, ie {{item.name}}. + """ + + type: Required[Literal["template"]] + """The type of input messages. Always `template`.""" + + +class InputMessagesItemReference(TypedDict, total=False): + item_reference: Required[str] + """A reference to a variable in the `item` namespace. Ie, "item.input_trajectory" """ + + type: Required[Literal["item_reference"]] + """The type of input messages. Always `item_reference`.""" + + +InputMessages: TypeAlias = Union[InputMessagesTemplate, InputMessagesItemReference] + +SamplingParamsResponseFormat: TypeAlias = Union[ResponseFormatText, ResponseFormatJSONSchema, ResponseFormatJSONObject] + + +class SamplingParams(TypedDict, total=False): + max_completion_tokens: int + """The maximum number of tokens in the generated output.""" + + reasoning_effort: Optional[ReasoningEffort] + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + response_format: SamplingParamsResponseFormat + """An object specifying the format that the model must output. + + Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured + Outputs which ensures the model will match your supplied JSON schema. Learn more + in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ + + seed: int + """A seed value to initialize the randomness, during sampling.""" + + temperature: float + """A higher temperature increases randomness in the outputs.""" + + tools: Iterable[ChatCompletionFunctionToolParam] + """A list of tools the model may call. + + Currently, only functions are supported as a tool. Use this to provide a list of + functions the model may generate JSON inputs for. A max of 128 functions are + supported. + """ + + top_p: float + """An alternative to temperature for nucleus sampling; 1.0 includes all tokens.""" + + +class CreateEvalCompletionsRunDataSourceParam(TypedDict, total=False): + """A CompletionsRunDataSource object describing a model sampling configuration.""" + + source: Required[Source] + """Determines what populates the `item` namespace in this run's data source.""" + + type: Required[Literal["completions"]] + """The type of run data source. Always `completions`.""" + + input_messages: InputMessages + """Used when sampling from a model. + + Dictates the structure of the messages passed into the model. Can either be a + reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template + with variable references to the `item` namespace. + """ + + model: str + """The name of the model to use for generating completions (e.g. "o3-mini").""" + + sampling_params: SamplingParams diff --git a/src/openai/types/evals/create_eval_jsonl_run_data_source.py b/src/openai/types/evals/create_eval_jsonl_run_data_source.py new file mode 100644 index 0000000000..36ede2d9eb --- /dev/null +++ b/src/openai/types/evals/create_eval_jsonl_run_data_source.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = ["CreateEvalJSONLRunDataSource", "Source", "SourceFileContent", "SourceFileContentContent", "SourceFileID"] + + +class SourceFileContentContent(BaseModel): + item: Dict[str, object] + + sample: Optional[Dict[str, object]] = None + + +class SourceFileContent(BaseModel): + content: List[SourceFileContentContent] + """The content of the jsonl file.""" + + type: Literal["file_content"] + """The type of jsonl source. Always `file_content`.""" + + +class SourceFileID(BaseModel): + id: str + """The identifier of the file.""" + + type: Literal["file_id"] + """The type of jsonl source. Always `file_id`.""" + + +Source: TypeAlias = Annotated[Union[SourceFileContent, SourceFileID], PropertyInfo(discriminator="type")] + + +class CreateEvalJSONLRunDataSource(BaseModel): + """ + A JsonlRunDataSource object with that specifies a JSONL file that matches the eval + """ + + source: Source + """Determines what populates the `item` namespace in the data source.""" + + type: Literal["jsonl"] + """The type of data source. Always `jsonl`.""" diff --git a/src/openai/types/evals/create_eval_jsonl_run_data_source_param.py b/src/openai/types/evals/create_eval_jsonl_run_data_source_param.py new file mode 100644 index 0000000000..b87ba9c5df --- /dev/null +++ b/src/openai/types/evals/create_eval_jsonl_run_data_source_param.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = [ + "CreateEvalJSONLRunDataSourceParam", + "Source", + "SourceFileContent", + "SourceFileContentContent", + "SourceFileID", +] + + +class SourceFileContentContent(TypedDict, total=False): + item: Required[Dict[str, object]] + + sample: Dict[str, object] + + +class SourceFileContent(TypedDict, total=False): + content: Required[Iterable[SourceFileContentContent]] + """The content of the jsonl file.""" + + type: Required[Literal["file_content"]] + """The type of jsonl source. Always `file_content`.""" + + +class SourceFileID(TypedDict, total=False): + id: Required[str] + """The identifier of the file.""" + + type: Required[Literal["file_id"]] + """The type of jsonl source. Always `file_id`.""" + + +Source: TypeAlias = Union[SourceFileContent, SourceFileID] + + +class CreateEvalJSONLRunDataSourceParam(TypedDict, total=False): + """ + A JsonlRunDataSource object with that specifies a JSONL file that matches the eval + """ + + source: Required[Source] + """Determines what populates the `item` namespace in the data source.""" + + type: Required[Literal["jsonl"]] + """The type of data source. Always `jsonl`.""" diff --git a/src/openai/types/evals/eval_api_error.py b/src/openai/types/evals/eval_api_error.py new file mode 100644 index 0000000000..9b2c1871fb --- /dev/null +++ b/src/openai/types/evals/eval_api_error.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["EvalAPIError"] + + +class EvalAPIError(BaseModel): + """An object representing an error response from the Eval API.""" + + code: str + """The error code.""" + + message: str + """The error message.""" diff --git a/src/openai/types/evals/run_cancel_response.py b/src/openai/types/evals/run_cancel_response.py new file mode 100644 index 0000000000..ea4797eecb --- /dev/null +++ b/src/openai/types/evals/run_cancel_response.py @@ -0,0 +1,452 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .eval_api_error import EvalAPIError +from ..responses.tool import Tool +from ..shared.metadata import Metadata +from ..graders.grader_inputs import GraderInputs +from ..shared.reasoning_effort import ReasoningEffort +from ..responses.response_input_text import ResponseInputText +from ..responses.response_input_audio import ResponseInputAudio +from .create_eval_jsonl_run_data_source import CreateEvalJSONLRunDataSource +from ..responses.response_format_text_config import ResponseFormatTextConfig +from .create_eval_completions_run_data_source import CreateEvalCompletionsRunDataSource + +__all__ = [ + "RunCancelResponse", + "DataSource", + "DataSourceResponses", + "DataSourceResponsesSource", + "DataSourceResponsesSourceFileContent", + "DataSourceResponsesSourceFileContentContent", + "DataSourceResponsesSourceFileID", + "DataSourceResponsesSourceResponses", + "DataSourceResponsesInputMessages", + "DataSourceResponsesInputMessagesTemplate", + "DataSourceResponsesInputMessagesTemplateTemplate", + "DataSourceResponsesInputMessagesTemplateTemplateChatMessage", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItem", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage", + "DataSourceResponsesInputMessagesItemReference", + "DataSourceResponsesSamplingParams", + "DataSourceResponsesSamplingParamsText", + "PerModelUsage", + "PerTestingCriteriaResult", + "ResultCounts", +] + + +class DataSourceResponsesSourceFileContentContent(BaseModel): + item: Dict[str, object] + + sample: Optional[Dict[str, object]] = None + + +class DataSourceResponsesSourceFileContent(BaseModel): + content: List[DataSourceResponsesSourceFileContentContent] + """The content of the jsonl file.""" + + type: Literal["file_content"] + """The type of jsonl source. Always `file_content`.""" + + +class DataSourceResponsesSourceFileID(BaseModel): + id: str + """The identifier of the file.""" + + type: Literal["file_id"] + """The type of jsonl source. Always `file_id`.""" + + +class DataSourceResponsesSourceResponses(BaseModel): + """A EvalResponsesSource object describing a run data source configuration.""" + + type: Literal["responses"] + """The type of run data source. Always `responses`.""" + + created_after: Optional[int] = None + """Only include items created after this timestamp (inclusive). + + This is a query parameter used to select responses. + """ + + created_before: Optional[int] = None + """Only include items created before this timestamp (inclusive). + + This is a query parameter used to select responses. + """ + + instructions_search: Optional[str] = None + """Optional string to search the 'instructions' field. + + This is a query parameter used to select responses. + """ + + metadata: Optional[object] = None + """Metadata filter for the responses. + + This is a query parameter used to select responses. + """ + + model: Optional[str] = None + """The name of the model to find responses for. + + This is a query parameter used to select responses. + """ + + reasoning_effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + temperature: Optional[float] = None + """Sampling temperature. This is a query parameter used to select responses.""" + + tools: Optional[List[str]] = None + """List of tool names. This is a query parameter used to select responses.""" + + top_p: Optional[float] = None + """Nucleus sampling parameter. This is a query parameter used to select responses.""" + + users: Optional[List[str]] = None + """List of user identifiers. This is a query parameter used to select responses.""" + + +DataSourceResponsesSource: TypeAlias = Annotated[ + Union[DataSourceResponsesSourceFileContent, DataSourceResponsesSourceFileID, DataSourceResponsesSourceResponses], + PropertyInfo(discriminator="type"), +] + + +class DataSourceResponsesInputMessagesTemplateTemplateChatMessage(BaseModel): + content: str + """The content of the message.""" + + role: str + """The role of the message (e.g. "system", "assistant", "user").""" + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText(BaseModel): + """A text output from the model.""" + + text: str + """The text output from the model.""" + + type: Literal["output_text"] + """The type of the output text. Always `output_text`.""" + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage(BaseModel): + """An image input block used within EvalItem content arrays.""" + + image_url: str + """The URL of the image input.""" + + type: Literal["input_image"] + """The type of the image input. Always `input_image`.""" + + detail: Optional[str] = None + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent: TypeAlias = Union[ + str, + ResponseInputText, + DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText, + DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage, + ResponseInputAudio, + GraderInputs, +] + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItem(BaseModel): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Literal["user", "assistant", "system", "developer"] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Optional[Literal["message"]] = None + """The type of the message input. Always `message`.""" + + +DataSourceResponsesInputMessagesTemplateTemplate: TypeAlias = Union[ + DataSourceResponsesInputMessagesTemplateTemplateChatMessage, + DataSourceResponsesInputMessagesTemplateTemplateEvalItem, +] + + +class DataSourceResponsesInputMessagesTemplate(BaseModel): + template: List[DataSourceResponsesInputMessagesTemplateTemplate] + """A list of chat messages forming the prompt or context. + + May include variable references to the `item` namespace, ie {{item.name}}. + """ + + type: Literal["template"] + """The type of input messages. Always `template`.""" + + +class DataSourceResponsesInputMessagesItemReference(BaseModel): + item_reference: str + """A reference to a variable in the `item` namespace. Ie, "item.name" """ + + type: Literal["item_reference"] + """The type of input messages. Always `item_reference`.""" + + +DataSourceResponsesInputMessages: TypeAlias = Annotated[ + Union[DataSourceResponsesInputMessagesTemplate, DataSourceResponsesInputMessagesItemReference], + PropertyInfo(discriminator="type"), +] + + +class DataSourceResponsesSamplingParamsText(BaseModel): + """Configuration options for a text response from the model. + + Can be plain + text or structured JSON data. Learn more: + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + format: Optional[ResponseFormatTextConfig] = None + """An object specifying the format that the model must output. + + Configuring `{ "type": "json_schema" }` enables Structured Outputs, which + ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + The default format is `{ "type": "text" }` with no additional options. + + **Not recommended for gpt-4o and newer models:** + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ + + +class DataSourceResponsesSamplingParams(BaseModel): + max_completion_tokens: Optional[int] = None + """The maximum number of tokens in the generated output.""" + + reasoning_effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + seed: Optional[int] = None + """A seed value to initialize the randomness, during sampling.""" + + temperature: Optional[float] = None + """A higher temperature increases randomness in the outputs.""" + + text: Optional[DataSourceResponsesSamplingParamsText] = None + """Configuration options for a text response from the model. + + Can be plain text or structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + tools: Optional[List[Tool]] = None + """An array of tools the model may call while generating a response. + + You can specify which tool to use by setting the `tool_choice` parameter. + + The two categories of tools you can provide the model are: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code. Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + """ + + top_p: Optional[float] = None + """An alternative to temperature for nucleus sampling; 1.0 includes all tokens.""" + + +class DataSourceResponses(BaseModel): + """A ResponsesRunDataSource object describing a model sampling configuration.""" + + source: DataSourceResponsesSource + """Determines what populates the `item` namespace in this run's data source.""" + + type: Literal["responses"] + """The type of run data source. Always `responses`.""" + + input_messages: Optional[DataSourceResponsesInputMessages] = None + """Used when sampling from a model. + + Dictates the structure of the messages passed into the model. Can either be a + reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template + with variable references to the `item` namespace. + """ + + model: Optional[str] = None + """The name of the model to use for generating completions (e.g. "o3-mini").""" + + sampling_params: Optional[DataSourceResponsesSamplingParams] = None + + +DataSource: TypeAlias = Annotated[ + Union[CreateEvalJSONLRunDataSource, CreateEvalCompletionsRunDataSource, DataSourceResponses], + PropertyInfo(discriminator="type"), +] + + +class PerModelUsage(BaseModel): + cached_tokens: int + """The number of tokens retrieved from cache.""" + + completion_tokens: int + """The number of completion tokens generated.""" + + invocation_count: int + """The number of invocations.""" + + run_model_name: str = FieldInfo(alias="model_name") + """The name of the model.""" + + prompt_tokens: int + """The number of prompt tokens used.""" + + total_tokens: int + """The total number of tokens used.""" + + +class PerTestingCriteriaResult(BaseModel): + failed: int + """Number of tests failed for this criteria.""" + + passed: int + """Number of tests passed for this criteria.""" + + testing_criteria: str + """A description of the testing criteria.""" + + +class ResultCounts(BaseModel): + """Counters summarizing the outcomes of the evaluation run.""" + + errored: int + """Number of output items that resulted in an error.""" + + failed: int + """Number of output items that failed to pass the evaluation.""" + + passed: int + """Number of output items that passed the evaluation.""" + + total: int + """Total number of executed output items.""" + + +class RunCancelResponse(BaseModel): + """A schema representing an evaluation run.""" + + id: str + """Unique identifier for the evaluation run.""" + + created_at: int + """Unix timestamp (in seconds) when the evaluation run was created.""" + + data_source: DataSource + """Information about the run's data source.""" + + error: EvalAPIError + """An object representing an error response from the Eval API.""" + + eval_id: str + """The identifier of the associated evaluation.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: str + """The model that is evaluated, if applicable.""" + + name: str + """The name of the evaluation run.""" + + object: Literal["eval.run"] + """The type of the object. Always "eval.run".""" + + per_model_usage: List[PerModelUsage] + """Usage statistics for each model during the evaluation run.""" + + per_testing_criteria_results: List[PerTestingCriteriaResult] + """Results per testing criteria applied during the evaluation run.""" + + report_url: str + """The URL to the rendered evaluation run report on the UI dashboard.""" + + result_counts: ResultCounts + """Counters summarizing the outcomes of the evaluation run.""" + + status: str + """The status of the evaluation run.""" diff --git a/src/openai/types/evals/run_create_params.py b/src/openai/types/evals/run_create_params.py new file mode 100644 index 0000000000..02804c30da --- /dev/null +++ b/src/openai/types/evals/run_create_params.py @@ -0,0 +1,371 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from ..responses.tool_param import ToolParam +from ..shared_params.metadata import Metadata +from ..shared.reasoning_effort import ReasoningEffort +from ..graders.grader_inputs_param import GraderInputsParam +from ..responses.response_input_text_param import ResponseInputTextParam +from ..responses.response_input_audio_param import ResponseInputAudioParam +from .create_eval_jsonl_run_data_source_param import CreateEvalJSONLRunDataSourceParam +from ..responses.response_format_text_config_param import ResponseFormatTextConfigParam +from .create_eval_completions_run_data_source_param import CreateEvalCompletionsRunDataSourceParam + +__all__ = [ + "RunCreateParams", + "DataSource", + "DataSourceCreateEvalResponsesRunDataSource", + "DataSourceCreateEvalResponsesRunDataSourceSource", + "DataSourceCreateEvalResponsesRunDataSourceSourceFileContent", + "DataSourceCreateEvalResponsesRunDataSourceSourceFileContentContent", + "DataSourceCreateEvalResponsesRunDataSourceSourceFileID", + "DataSourceCreateEvalResponsesRunDataSourceSourceResponses", + "DataSourceCreateEvalResponsesRunDataSourceInputMessages", + "DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplate", + "DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplate", + "DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateChatMessage", + "DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItem", + "DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItemContent", + "DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItemContentOutputText", + "DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItemContentInputImage", + "DataSourceCreateEvalResponsesRunDataSourceInputMessagesItemReference", + "DataSourceCreateEvalResponsesRunDataSourceSamplingParams", + "DataSourceCreateEvalResponsesRunDataSourceSamplingParamsText", +] + + +class RunCreateParams(TypedDict, total=False): + data_source: Required[DataSource] + """Details about the run's data source.""" + + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + name: str + """The name of the run.""" + + +class DataSourceCreateEvalResponsesRunDataSourceSourceFileContentContent(TypedDict, total=False): + item: Required[Dict[str, object]] + + sample: Dict[str, object] + + +class DataSourceCreateEvalResponsesRunDataSourceSourceFileContent(TypedDict, total=False): + content: Required[Iterable[DataSourceCreateEvalResponsesRunDataSourceSourceFileContentContent]] + """The content of the jsonl file.""" + + type: Required[Literal["file_content"]] + """The type of jsonl source. Always `file_content`.""" + + +class DataSourceCreateEvalResponsesRunDataSourceSourceFileID(TypedDict, total=False): + id: Required[str] + """The identifier of the file.""" + + type: Required[Literal["file_id"]] + """The type of jsonl source. Always `file_id`.""" + + +class DataSourceCreateEvalResponsesRunDataSourceSourceResponses(TypedDict, total=False): + """A EvalResponsesSource object describing a run data source configuration.""" + + type: Required[Literal["responses"]] + """The type of run data source. Always `responses`.""" + + created_after: Optional[int] + """Only include items created after this timestamp (inclusive). + + This is a query parameter used to select responses. + """ + + created_before: Optional[int] + """Only include items created before this timestamp (inclusive). + + This is a query parameter used to select responses. + """ + + instructions_search: Optional[str] + """Optional string to search the 'instructions' field. + + This is a query parameter used to select responses. + """ + + metadata: Optional[object] + """Metadata filter for the responses. + + This is a query parameter used to select responses. + """ + + model: Optional[str] + """The name of the model to find responses for. + + This is a query parameter used to select responses. + """ + + reasoning_effort: Optional[ReasoningEffort] + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + temperature: Optional[float] + """Sampling temperature. This is a query parameter used to select responses.""" + + tools: Optional[SequenceNotStr[str]] + """List of tool names. This is a query parameter used to select responses.""" + + top_p: Optional[float] + """Nucleus sampling parameter. This is a query parameter used to select responses.""" + + users: Optional[SequenceNotStr[str]] + """List of user identifiers. This is a query parameter used to select responses.""" + + +DataSourceCreateEvalResponsesRunDataSourceSource: TypeAlias = Union[ + DataSourceCreateEvalResponsesRunDataSourceSourceFileContent, + DataSourceCreateEvalResponsesRunDataSourceSourceFileID, + DataSourceCreateEvalResponsesRunDataSourceSourceResponses, +] + + +class DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateChatMessage(TypedDict, total=False): + content: Required[str] + """The content of the message.""" + + role: Required[str] + """The role of the message (e.g. "system", "assistant", "user").""" + + +class DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItemContentOutputText( + TypedDict, total=False +): + """A text output from the model.""" + + text: Required[str] + """The text output from the model.""" + + type: Required[Literal["output_text"]] + """The type of the output text. Always `output_text`.""" + + +class DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItemContentInputImage( + TypedDict, total=False +): + """An image input block used within EvalItem content arrays.""" + + image_url: Required[str] + """The URL of the image input.""" + + type: Required[Literal["input_image"]] + """The type of the image input. Always `input_image`.""" + + detail: str + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItemContent: TypeAlias = Union[ + str, + ResponseInputTextParam, + DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItemContentOutputText, + DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItemContentInputImage, + ResponseInputAudioParam, + GraderInputsParam, +] + + +class DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItem(TypedDict, total=False): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: Required[DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItemContent] + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Required[Literal["user", "assistant", "system", "developer"]] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Literal["message"] + """The type of the message input. Always `message`.""" + + +DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplate: TypeAlias = Union[ + DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateChatMessage, + DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplateEvalItem, +] + + +class DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplate(TypedDict, total=False): + template: Required[Iterable[DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplateTemplate]] + """A list of chat messages forming the prompt or context. + + May include variable references to the `item` namespace, ie {{item.name}}. + """ + + type: Required[Literal["template"]] + """The type of input messages. Always `template`.""" + + +class DataSourceCreateEvalResponsesRunDataSourceInputMessagesItemReference(TypedDict, total=False): + item_reference: Required[str] + """A reference to a variable in the `item` namespace. Ie, "item.name" """ + + type: Required[Literal["item_reference"]] + """The type of input messages. Always `item_reference`.""" + + +DataSourceCreateEvalResponsesRunDataSourceInputMessages: TypeAlias = Union[ + DataSourceCreateEvalResponsesRunDataSourceInputMessagesTemplate, + DataSourceCreateEvalResponsesRunDataSourceInputMessagesItemReference, +] + + +class DataSourceCreateEvalResponsesRunDataSourceSamplingParamsText(TypedDict, total=False): + """Configuration options for a text response from the model. + + Can be plain + text or structured JSON data. Learn more: + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + format: ResponseFormatTextConfigParam + """An object specifying the format that the model must output. + + Configuring `{ "type": "json_schema" }` enables Structured Outputs, which + ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + The default format is `{ "type": "text" }` with no additional options. + + **Not recommended for gpt-4o and newer models:** + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ + + +class DataSourceCreateEvalResponsesRunDataSourceSamplingParams(TypedDict, total=False): + max_completion_tokens: int + """The maximum number of tokens in the generated output.""" + + reasoning_effort: Optional[ReasoningEffort] + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + seed: int + """A seed value to initialize the randomness, during sampling.""" + + temperature: float + """A higher temperature increases randomness in the outputs.""" + + text: DataSourceCreateEvalResponsesRunDataSourceSamplingParamsText + """Configuration options for a text response from the model. + + Can be plain text or structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + tools: Iterable[ToolParam] + """An array of tools the model may call while generating a response. + + You can specify which tool to use by setting the `tool_choice` parameter. + + The two categories of tools you can provide the model are: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code. Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + """ + + top_p: float + """An alternative to temperature for nucleus sampling; 1.0 includes all tokens.""" + + +class DataSourceCreateEvalResponsesRunDataSource(TypedDict, total=False): + """A ResponsesRunDataSource object describing a model sampling configuration.""" + + source: Required[DataSourceCreateEvalResponsesRunDataSourceSource] + """Determines what populates the `item` namespace in this run's data source.""" + + type: Required[Literal["responses"]] + """The type of run data source. Always `responses`.""" + + input_messages: DataSourceCreateEvalResponsesRunDataSourceInputMessages + """Used when sampling from a model. + + Dictates the structure of the messages passed into the model. Can either be a + reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template + with variable references to the `item` namespace. + """ + + model: str + """The name of the model to use for generating completions (e.g. "o3-mini").""" + + sampling_params: DataSourceCreateEvalResponsesRunDataSourceSamplingParams + + +DataSource: TypeAlias = Union[ + CreateEvalJSONLRunDataSourceParam, + CreateEvalCompletionsRunDataSourceParam, + DataSourceCreateEvalResponsesRunDataSource, +] diff --git a/src/openai/types/evals/run_create_response.py b/src/openai/types/evals/run_create_response.py new file mode 100644 index 0000000000..2cb856de6f --- /dev/null +++ b/src/openai/types/evals/run_create_response.py @@ -0,0 +1,452 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .eval_api_error import EvalAPIError +from ..responses.tool import Tool +from ..shared.metadata import Metadata +from ..graders.grader_inputs import GraderInputs +from ..shared.reasoning_effort import ReasoningEffort +from ..responses.response_input_text import ResponseInputText +from ..responses.response_input_audio import ResponseInputAudio +from .create_eval_jsonl_run_data_source import CreateEvalJSONLRunDataSource +from ..responses.response_format_text_config import ResponseFormatTextConfig +from .create_eval_completions_run_data_source import CreateEvalCompletionsRunDataSource + +__all__ = [ + "RunCreateResponse", + "DataSource", + "DataSourceResponses", + "DataSourceResponsesSource", + "DataSourceResponsesSourceFileContent", + "DataSourceResponsesSourceFileContentContent", + "DataSourceResponsesSourceFileID", + "DataSourceResponsesSourceResponses", + "DataSourceResponsesInputMessages", + "DataSourceResponsesInputMessagesTemplate", + "DataSourceResponsesInputMessagesTemplateTemplate", + "DataSourceResponsesInputMessagesTemplateTemplateChatMessage", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItem", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage", + "DataSourceResponsesInputMessagesItemReference", + "DataSourceResponsesSamplingParams", + "DataSourceResponsesSamplingParamsText", + "PerModelUsage", + "PerTestingCriteriaResult", + "ResultCounts", +] + + +class DataSourceResponsesSourceFileContentContent(BaseModel): + item: Dict[str, object] + + sample: Optional[Dict[str, object]] = None + + +class DataSourceResponsesSourceFileContent(BaseModel): + content: List[DataSourceResponsesSourceFileContentContent] + """The content of the jsonl file.""" + + type: Literal["file_content"] + """The type of jsonl source. Always `file_content`.""" + + +class DataSourceResponsesSourceFileID(BaseModel): + id: str + """The identifier of the file.""" + + type: Literal["file_id"] + """The type of jsonl source. Always `file_id`.""" + + +class DataSourceResponsesSourceResponses(BaseModel): + """A EvalResponsesSource object describing a run data source configuration.""" + + type: Literal["responses"] + """The type of run data source. Always `responses`.""" + + created_after: Optional[int] = None + """Only include items created after this timestamp (inclusive). + + This is a query parameter used to select responses. + """ + + created_before: Optional[int] = None + """Only include items created before this timestamp (inclusive). + + This is a query parameter used to select responses. + """ + + instructions_search: Optional[str] = None + """Optional string to search the 'instructions' field. + + This is a query parameter used to select responses. + """ + + metadata: Optional[object] = None + """Metadata filter for the responses. + + This is a query parameter used to select responses. + """ + + model: Optional[str] = None + """The name of the model to find responses for. + + This is a query parameter used to select responses. + """ + + reasoning_effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + temperature: Optional[float] = None + """Sampling temperature. This is a query parameter used to select responses.""" + + tools: Optional[List[str]] = None + """List of tool names. This is a query parameter used to select responses.""" + + top_p: Optional[float] = None + """Nucleus sampling parameter. This is a query parameter used to select responses.""" + + users: Optional[List[str]] = None + """List of user identifiers. This is a query parameter used to select responses.""" + + +DataSourceResponsesSource: TypeAlias = Annotated[ + Union[DataSourceResponsesSourceFileContent, DataSourceResponsesSourceFileID, DataSourceResponsesSourceResponses], + PropertyInfo(discriminator="type"), +] + + +class DataSourceResponsesInputMessagesTemplateTemplateChatMessage(BaseModel): + content: str + """The content of the message.""" + + role: str + """The role of the message (e.g. "system", "assistant", "user").""" + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText(BaseModel): + """A text output from the model.""" + + text: str + """The text output from the model.""" + + type: Literal["output_text"] + """The type of the output text. Always `output_text`.""" + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage(BaseModel): + """An image input block used within EvalItem content arrays.""" + + image_url: str + """The URL of the image input.""" + + type: Literal["input_image"] + """The type of the image input. Always `input_image`.""" + + detail: Optional[str] = None + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent: TypeAlias = Union[ + str, + ResponseInputText, + DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText, + DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage, + ResponseInputAudio, + GraderInputs, +] + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItem(BaseModel): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Literal["user", "assistant", "system", "developer"] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Optional[Literal["message"]] = None + """The type of the message input. Always `message`.""" + + +DataSourceResponsesInputMessagesTemplateTemplate: TypeAlias = Union[ + DataSourceResponsesInputMessagesTemplateTemplateChatMessage, + DataSourceResponsesInputMessagesTemplateTemplateEvalItem, +] + + +class DataSourceResponsesInputMessagesTemplate(BaseModel): + template: List[DataSourceResponsesInputMessagesTemplateTemplate] + """A list of chat messages forming the prompt or context. + + May include variable references to the `item` namespace, ie {{item.name}}. + """ + + type: Literal["template"] + """The type of input messages. Always `template`.""" + + +class DataSourceResponsesInputMessagesItemReference(BaseModel): + item_reference: str + """A reference to a variable in the `item` namespace. Ie, "item.name" """ + + type: Literal["item_reference"] + """The type of input messages. Always `item_reference`.""" + + +DataSourceResponsesInputMessages: TypeAlias = Annotated[ + Union[DataSourceResponsesInputMessagesTemplate, DataSourceResponsesInputMessagesItemReference], + PropertyInfo(discriminator="type"), +] + + +class DataSourceResponsesSamplingParamsText(BaseModel): + """Configuration options for a text response from the model. + + Can be plain + text or structured JSON data. Learn more: + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + format: Optional[ResponseFormatTextConfig] = None + """An object specifying the format that the model must output. + + Configuring `{ "type": "json_schema" }` enables Structured Outputs, which + ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + The default format is `{ "type": "text" }` with no additional options. + + **Not recommended for gpt-4o and newer models:** + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ + + +class DataSourceResponsesSamplingParams(BaseModel): + max_completion_tokens: Optional[int] = None + """The maximum number of tokens in the generated output.""" + + reasoning_effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + seed: Optional[int] = None + """A seed value to initialize the randomness, during sampling.""" + + temperature: Optional[float] = None + """A higher temperature increases randomness in the outputs.""" + + text: Optional[DataSourceResponsesSamplingParamsText] = None + """Configuration options for a text response from the model. + + Can be plain text or structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + tools: Optional[List[Tool]] = None + """An array of tools the model may call while generating a response. + + You can specify which tool to use by setting the `tool_choice` parameter. + + The two categories of tools you can provide the model are: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code. Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + """ + + top_p: Optional[float] = None + """An alternative to temperature for nucleus sampling; 1.0 includes all tokens.""" + + +class DataSourceResponses(BaseModel): + """A ResponsesRunDataSource object describing a model sampling configuration.""" + + source: DataSourceResponsesSource + """Determines what populates the `item` namespace in this run's data source.""" + + type: Literal["responses"] + """The type of run data source. Always `responses`.""" + + input_messages: Optional[DataSourceResponsesInputMessages] = None + """Used when sampling from a model. + + Dictates the structure of the messages passed into the model. Can either be a + reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template + with variable references to the `item` namespace. + """ + + model: Optional[str] = None + """The name of the model to use for generating completions (e.g. "o3-mini").""" + + sampling_params: Optional[DataSourceResponsesSamplingParams] = None + + +DataSource: TypeAlias = Annotated[ + Union[CreateEvalJSONLRunDataSource, CreateEvalCompletionsRunDataSource, DataSourceResponses], + PropertyInfo(discriminator="type"), +] + + +class PerModelUsage(BaseModel): + cached_tokens: int + """The number of tokens retrieved from cache.""" + + completion_tokens: int + """The number of completion tokens generated.""" + + invocation_count: int + """The number of invocations.""" + + run_model_name: str = FieldInfo(alias="model_name") + """The name of the model.""" + + prompt_tokens: int + """The number of prompt tokens used.""" + + total_tokens: int + """The total number of tokens used.""" + + +class PerTestingCriteriaResult(BaseModel): + failed: int + """Number of tests failed for this criteria.""" + + passed: int + """Number of tests passed for this criteria.""" + + testing_criteria: str + """A description of the testing criteria.""" + + +class ResultCounts(BaseModel): + """Counters summarizing the outcomes of the evaluation run.""" + + errored: int + """Number of output items that resulted in an error.""" + + failed: int + """Number of output items that failed to pass the evaluation.""" + + passed: int + """Number of output items that passed the evaluation.""" + + total: int + """Total number of executed output items.""" + + +class RunCreateResponse(BaseModel): + """A schema representing an evaluation run.""" + + id: str + """Unique identifier for the evaluation run.""" + + created_at: int + """Unix timestamp (in seconds) when the evaluation run was created.""" + + data_source: DataSource + """Information about the run's data source.""" + + error: EvalAPIError + """An object representing an error response from the Eval API.""" + + eval_id: str + """The identifier of the associated evaluation.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: str + """The model that is evaluated, if applicable.""" + + name: str + """The name of the evaluation run.""" + + object: Literal["eval.run"] + """The type of the object. Always "eval.run".""" + + per_model_usage: List[PerModelUsage] + """Usage statistics for each model during the evaluation run.""" + + per_testing_criteria_results: List[PerTestingCriteriaResult] + """Results per testing criteria applied during the evaluation run.""" + + report_url: str + """The URL to the rendered evaluation run report on the UI dashboard.""" + + result_counts: ResultCounts + """Counters summarizing the outcomes of the evaluation run.""" + + status: str + """The status of the evaluation run.""" diff --git a/src/openai/types/evals/run_delete_response.py b/src/openai/types/evals/run_delete_response.py new file mode 100644 index 0000000000..d48d01f86c --- /dev/null +++ b/src/openai/types/evals/run_delete_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["RunDeleteResponse"] + + +class RunDeleteResponse(BaseModel): + deleted: Optional[bool] = None + + object: Optional[str] = None + + run_id: Optional[str] = None diff --git a/src/openai/types/evals/run_list_params.py b/src/openai/types/evals/run_list_params.py new file mode 100644 index 0000000000..383b89d85c --- /dev/null +++ b/src/openai/types/evals/run_list_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["RunListParams"] + + +class RunListParams(TypedDict, total=False): + after: str + """Identifier for the last run from the previous pagination request.""" + + limit: int + """Number of runs to retrieve.""" + + order: Literal["asc", "desc"] + """Sort order for runs by timestamp. + + Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`. + """ + + status: Literal["queued", "in_progress", "completed", "canceled", "failed"] + """Filter runs by status. + + One of `queued` | `in_progress` | `failed` | `completed` | `canceled`. + """ diff --git a/src/openai/types/evals/run_list_response.py b/src/openai/types/evals/run_list_response.py new file mode 100644 index 0000000000..defd4aa6f9 --- /dev/null +++ b/src/openai/types/evals/run_list_response.py @@ -0,0 +1,452 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .eval_api_error import EvalAPIError +from ..responses.tool import Tool +from ..shared.metadata import Metadata +from ..graders.grader_inputs import GraderInputs +from ..shared.reasoning_effort import ReasoningEffort +from ..responses.response_input_text import ResponseInputText +from ..responses.response_input_audio import ResponseInputAudio +from .create_eval_jsonl_run_data_source import CreateEvalJSONLRunDataSource +from ..responses.response_format_text_config import ResponseFormatTextConfig +from .create_eval_completions_run_data_source import CreateEvalCompletionsRunDataSource + +__all__ = [ + "RunListResponse", + "DataSource", + "DataSourceResponses", + "DataSourceResponsesSource", + "DataSourceResponsesSourceFileContent", + "DataSourceResponsesSourceFileContentContent", + "DataSourceResponsesSourceFileID", + "DataSourceResponsesSourceResponses", + "DataSourceResponsesInputMessages", + "DataSourceResponsesInputMessagesTemplate", + "DataSourceResponsesInputMessagesTemplateTemplate", + "DataSourceResponsesInputMessagesTemplateTemplateChatMessage", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItem", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage", + "DataSourceResponsesInputMessagesItemReference", + "DataSourceResponsesSamplingParams", + "DataSourceResponsesSamplingParamsText", + "PerModelUsage", + "PerTestingCriteriaResult", + "ResultCounts", +] + + +class DataSourceResponsesSourceFileContentContent(BaseModel): + item: Dict[str, object] + + sample: Optional[Dict[str, object]] = None + + +class DataSourceResponsesSourceFileContent(BaseModel): + content: List[DataSourceResponsesSourceFileContentContent] + """The content of the jsonl file.""" + + type: Literal["file_content"] + """The type of jsonl source. Always `file_content`.""" + + +class DataSourceResponsesSourceFileID(BaseModel): + id: str + """The identifier of the file.""" + + type: Literal["file_id"] + """The type of jsonl source. Always `file_id`.""" + + +class DataSourceResponsesSourceResponses(BaseModel): + """A EvalResponsesSource object describing a run data source configuration.""" + + type: Literal["responses"] + """The type of run data source. Always `responses`.""" + + created_after: Optional[int] = None + """Only include items created after this timestamp (inclusive). + + This is a query parameter used to select responses. + """ + + created_before: Optional[int] = None + """Only include items created before this timestamp (inclusive). + + This is a query parameter used to select responses. + """ + + instructions_search: Optional[str] = None + """Optional string to search the 'instructions' field. + + This is a query parameter used to select responses. + """ + + metadata: Optional[object] = None + """Metadata filter for the responses. + + This is a query parameter used to select responses. + """ + + model: Optional[str] = None + """The name of the model to find responses for. + + This is a query parameter used to select responses. + """ + + reasoning_effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + temperature: Optional[float] = None + """Sampling temperature. This is a query parameter used to select responses.""" + + tools: Optional[List[str]] = None + """List of tool names. This is a query parameter used to select responses.""" + + top_p: Optional[float] = None + """Nucleus sampling parameter. This is a query parameter used to select responses.""" + + users: Optional[List[str]] = None + """List of user identifiers. This is a query parameter used to select responses.""" + + +DataSourceResponsesSource: TypeAlias = Annotated[ + Union[DataSourceResponsesSourceFileContent, DataSourceResponsesSourceFileID, DataSourceResponsesSourceResponses], + PropertyInfo(discriminator="type"), +] + + +class DataSourceResponsesInputMessagesTemplateTemplateChatMessage(BaseModel): + content: str + """The content of the message.""" + + role: str + """The role of the message (e.g. "system", "assistant", "user").""" + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText(BaseModel): + """A text output from the model.""" + + text: str + """The text output from the model.""" + + type: Literal["output_text"] + """The type of the output text. Always `output_text`.""" + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage(BaseModel): + """An image input block used within EvalItem content arrays.""" + + image_url: str + """The URL of the image input.""" + + type: Literal["input_image"] + """The type of the image input. Always `input_image`.""" + + detail: Optional[str] = None + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent: TypeAlias = Union[ + str, + ResponseInputText, + DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText, + DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage, + ResponseInputAudio, + GraderInputs, +] + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItem(BaseModel): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Literal["user", "assistant", "system", "developer"] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Optional[Literal["message"]] = None + """The type of the message input. Always `message`.""" + + +DataSourceResponsesInputMessagesTemplateTemplate: TypeAlias = Union[ + DataSourceResponsesInputMessagesTemplateTemplateChatMessage, + DataSourceResponsesInputMessagesTemplateTemplateEvalItem, +] + + +class DataSourceResponsesInputMessagesTemplate(BaseModel): + template: List[DataSourceResponsesInputMessagesTemplateTemplate] + """A list of chat messages forming the prompt or context. + + May include variable references to the `item` namespace, ie {{item.name}}. + """ + + type: Literal["template"] + """The type of input messages. Always `template`.""" + + +class DataSourceResponsesInputMessagesItemReference(BaseModel): + item_reference: str + """A reference to a variable in the `item` namespace. Ie, "item.name" """ + + type: Literal["item_reference"] + """The type of input messages. Always `item_reference`.""" + + +DataSourceResponsesInputMessages: TypeAlias = Annotated[ + Union[DataSourceResponsesInputMessagesTemplate, DataSourceResponsesInputMessagesItemReference], + PropertyInfo(discriminator="type"), +] + + +class DataSourceResponsesSamplingParamsText(BaseModel): + """Configuration options for a text response from the model. + + Can be plain + text or structured JSON data. Learn more: + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + format: Optional[ResponseFormatTextConfig] = None + """An object specifying the format that the model must output. + + Configuring `{ "type": "json_schema" }` enables Structured Outputs, which + ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + The default format is `{ "type": "text" }` with no additional options. + + **Not recommended for gpt-4o and newer models:** + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ + + +class DataSourceResponsesSamplingParams(BaseModel): + max_completion_tokens: Optional[int] = None + """The maximum number of tokens in the generated output.""" + + reasoning_effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + seed: Optional[int] = None + """A seed value to initialize the randomness, during sampling.""" + + temperature: Optional[float] = None + """A higher temperature increases randomness in the outputs.""" + + text: Optional[DataSourceResponsesSamplingParamsText] = None + """Configuration options for a text response from the model. + + Can be plain text or structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + tools: Optional[List[Tool]] = None + """An array of tools the model may call while generating a response. + + You can specify which tool to use by setting the `tool_choice` parameter. + + The two categories of tools you can provide the model are: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code. Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + """ + + top_p: Optional[float] = None + """An alternative to temperature for nucleus sampling; 1.0 includes all tokens.""" + + +class DataSourceResponses(BaseModel): + """A ResponsesRunDataSource object describing a model sampling configuration.""" + + source: DataSourceResponsesSource + """Determines what populates the `item` namespace in this run's data source.""" + + type: Literal["responses"] + """The type of run data source. Always `responses`.""" + + input_messages: Optional[DataSourceResponsesInputMessages] = None + """Used when sampling from a model. + + Dictates the structure of the messages passed into the model. Can either be a + reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template + with variable references to the `item` namespace. + """ + + model: Optional[str] = None + """The name of the model to use for generating completions (e.g. "o3-mini").""" + + sampling_params: Optional[DataSourceResponsesSamplingParams] = None + + +DataSource: TypeAlias = Annotated[ + Union[CreateEvalJSONLRunDataSource, CreateEvalCompletionsRunDataSource, DataSourceResponses], + PropertyInfo(discriminator="type"), +] + + +class PerModelUsage(BaseModel): + cached_tokens: int + """The number of tokens retrieved from cache.""" + + completion_tokens: int + """The number of completion tokens generated.""" + + invocation_count: int + """The number of invocations.""" + + run_model_name: str = FieldInfo(alias="model_name") + """The name of the model.""" + + prompt_tokens: int + """The number of prompt tokens used.""" + + total_tokens: int + """The total number of tokens used.""" + + +class PerTestingCriteriaResult(BaseModel): + failed: int + """Number of tests failed for this criteria.""" + + passed: int + """Number of tests passed for this criteria.""" + + testing_criteria: str + """A description of the testing criteria.""" + + +class ResultCounts(BaseModel): + """Counters summarizing the outcomes of the evaluation run.""" + + errored: int + """Number of output items that resulted in an error.""" + + failed: int + """Number of output items that failed to pass the evaluation.""" + + passed: int + """Number of output items that passed the evaluation.""" + + total: int + """Total number of executed output items.""" + + +class RunListResponse(BaseModel): + """A schema representing an evaluation run.""" + + id: str + """Unique identifier for the evaluation run.""" + + created_at: int + """Unix timestamp (in seconds) when the evaluation run was created.""" + + data_source: DataSource + """Information about the run's data source.""" + + error: EvalAPIError + """An object representing an error response from the Eval API.""" + + eval_id: str + """The identifier of the associated evaluation.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: str + """The model that is evaluated, if applicable.""" + + name: str + """The name of the evaluation run.""" + + object: Literal["eval.run"] + """The type of the object. Always "eval.run".""" + + per_model_usage: List[PerModelUsage] + """Usage statistics for each model during the evaluation run.""" + + per_testing_criteria_results: List[PerTestingCriteriaResult] + """Results per testing criteria applied during the evaluation run.""" + + report_url: str + """The URL to the rendered evaluation run report on the UI dashboard.""" + + result_counts: ResultCounts + """Counters summarizing the outcomes of the evaluation run.""" + + status: str + """The status of the evaluation run.""" diff --git a/src/openai/types/evals/run_retrieve_response.py b/src/openai/types/evals/run_retrieve_response.py new file mode 100644 index 0000000000..4c218a0510 --- /dev/null +++ b/src/openai/types/evals/run_retrieve_response.py @@ -0,0 +1,452 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .eval_api_error import EvalAPIError +from ..responses.tool import Tool +from ..shared.metadata import Metadata +from ..graders.grader_inputs import GraderInputs +from ..shared.reasoning_effort import ReasoningEffort +from ..responses.response_input_text import ResponseInputText +from ..responses.response_input_audio import ResponseInputAudio +from .create_eval_jsonl_run_data_source import CreateEvalJSONLRunDataSource +from ..responses.response_format_text_config import ResponseFormatTextConfig +from .create_eval_completions_run_data_source import CreateEvalCompletionsRunDataSource + +__all__ = [ + "RunRetrieveResponse", + "DataSource", + "DataSourceResponses", + "DataSourceResponsesSource", + "DataSourceResponsesSourceFileContent", + "DataSourceResponsesSourceFileContentContent", + "DataSourceResponsesSourceFileID", + "DataSourceResponsesSourceResponses", + "DataSourceResponsesInputMessages", + "DataSourceResponsesInputMessagesTemplate", + "DataSourceResponsesInputMessagesTemplateTemplate", + "DataSourceResponsesInputMessagesTemplateTemplateChatMessage", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItem", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText", + "DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage", + "DataSourceResponsesInputMessagesItemReference", + "DataSourceResponsesSamplingParams", + "DataSourceResponsesSamplingParamsText", + "PerModelUsage", + "PerTestingCriteriaResult", + "ResultCounts", +] + + +class DataSourceResponsesSourceFileContentContent(BaseModel): + item: Dict[str, object] + + sample: Optional[Dict[str, object]] = None + + +class DataSourceResponsesSourceFileContent(BaseModel): + content: List[DataSourceResponsesSourceFileContentContent] + """The content of the jsonl file.""" + + type: Literal["file_content"] + """The type of jsonl source. Always `file_content`.""" + + +class DataSourceResponsesSourceFileID(BaseModel): + id: str + """The identifier of the file.""" + + type: Literal["file_id"] + """The type of jsonl source. Always `file_id`.""" + + +class DataSourceResponsesSourceResponses(BaseModel): + """A EvalResponsesSource object describing a run data source configuration.""" + + type: Literal["responses"] + """The type of run data source. Always `responses`.""" + + created_after: Optional[int] = None + """Only include items created after this timestamp (inclusive). + + This is a query parameter used to select responses. + """ + + created_before: Optional[int] = None + """Only include items created before this timestamp (inclusive). + + This is a query parameter used to select responses. + """ + + instructions_search: Optional[str] = None + """Optional string to search the 'instructions' field. + + This is a query parameter used to select responses. + """ + + metadata: Optional[object] = None + """Metadata filter for the responses. + + This is a query parameter used to select responses. + """ + + model: Optional[str] = None + """The name of the model to find responses for. + + This is a query parameter used to select responses. + """ + + reasoning_effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + temperature: Optional[float] = None + """Sampling temperature. This is a query parameter used to select responses.""" + + tools: Optional[List[str]] = None + """List of tool names. This is a query parameter used to select responses.""" + + top_p: Optional[float] = None + """Nucleus sampling parameter. This is a query parameter used to select responses.""" + + users: Optional[List[str]] = None + """List of user identifiers. This is a query parameter used to select responses.""" + + +DataSourceResponsesSource: TypeAlias = Annotated[ + Union[DataSourceResponsesSourceFileContent, DataSourceResponsesSourceFileID, DataSourceResponsesSourceResponses], + PropertyInfo(discriminator="type"), +] + + +class DataSourceResponsesInputMessagesTemplateTemplateChatMessage(BaseModel): + content: str + """The content of the message.""" + + role: str + """The role of the message (e.g. "system", "assistant", "user").""" + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText(BaseModel): + """A text output from the model.""" + + text: str + """The text output from the model.""" + + type: Literal["output_text"] + """The type of the output text. Always `output_text`.""" + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage(BaseModel): + """An image input block used within EvalItem content arrays.""" + + image_url: str + """The URL of the image input.""" + + type: Literal["input_image"] + """The type of the image input. Always `input_image`.""" + + detail: Optional[str] = None + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent: TypeAlias = Union[ + str, + ResponseInputText, + DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentOutputText, + DataSourceResponsesInputMessagesTemplateTemplateEvalItemContentInputImage, + ResponseInputAudio, + GraderInputs, +] + + +class DataSourceResponsesInputMessagesTemplateTemplateEvalItem(BaseModel): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: DataSourceResponsesInputMessagesTemplateTemplateEvalItemContent + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Literal["user", "assistant", "system", "developer"] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Optional[Literal["message"]] = None + """The type of the message input. Always `message`.""" + + +DataSourceResponsesInputMessagesTemplateTemplate: TypeAlias = Union[ + DataSourceResponsesInputMessagesTemplateTemplateChatMessage, + DataSourceResponsesInputMessagesTemplateTemplateEvalItem, +] + + +class DataSourceResponsesInputMessagesTemplate(BaseModel): + template: List[DataSourceResponsesInputMessagesTemplateTemplate] + """A list of chat messages forming the prompt or context. + + May include variable references to the `item` namespace, ie {{item.name}}. + """ + + type: Literal["template"] + """The type of input messages. Always `template`.""" + + +class DataSourceResponsesInputMessagesItemReference(BaseModel): + item_reference: str + """A reference to a variable in the `item` namespace. Ie, "item.name" """ + + type: Literal["item_reference"] + """The type of input messages. Always `item_reference`.""" + + +DataSourceResponsesInputMessages: TypeAlias = Annotated[ + Union[DataSourceResponsesInputMessagesTemplate, DataSourceResponsesInputMessagesItemReference], + PropertyInfo(discriminator="type"), +] + + +class DataSourceResponsesSamplingParamsText(BaseModel): + """Configuration options for a text response from the model. + + Can be plain + text or structured JSON data. Learn more: + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + format: Optional[ResponseFormatTextConfig] = None + """An object specifying the format that the model must output. + + Configuring `{ "type": "json_schema" }` enables Structured Outputs, which + ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + The default format is `{ "type": "text" }` with no additional options. + + **Not recommended for gpt-4o and newer models:** + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ + + +class DataSourceResponsesSamplingParams(BaseModel): + max_completion_tokens: Optional[int] = None + """The maximum number of tokens in the generated output.""" + + reasoning_effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + seed: Optional[int] = None + """A seed value to initialize the randomness, during sampling.""" + + temperature: Optional[float] = None + """A higher temperature increases randomness in the outputs.""" + + text: Optional[DataSourceResponsesSamplingParamsText] = None + """Configuration options for a text response from the model. + + Can be plain text or structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + tools: Optional[List[Tool]] = None + """An array of tools the model may call while generating a response. + + You can specify which tool to use by setting the `tool_choice` parameter. + + The two categories of tools you can provide the model are: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code. Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + """ + + top_p: Optional[float] = None + """An alternative to temperature for nucleus sampling; 1.0 includes all tokens.""" + + +class DataSourceResponses(BaseModel): + """A ResponsesRunDataSource object describing a model sampling configuration.""" + + source: DataSourceResponsesSource + """Determines what populates the `item` namespace in this run's data source.""" + + type: Literal["responses"] + """The type of run data source. Always `responses`.""" + + input_messages: Optional[DataSourceResponsesInputMessages] = None + """Used when sampling from a model. + + Dictates the structure of the messages passed into the model. Can either be a + reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template + with variable references to the `item` namespace. + """ + + model: Optional[str] = None + """The name of the model to use for generating completions (e.g. "o3-mini").""" + + sampling_params: Optional[DataSourceResponsesSamplingParams] = None + + +DataSource: TypeAlias = Annotated[ + Union[CreateEvalJSONLRunDataSource, CreateEvalCompletionsRunDataSource, DataSourceResponses], + PropertyInfo(discriminator="type"), +] + + +class PerModelUsage(BaseModel): + cached_tokens: int + """The number of tokens retrieved from cache.""" + + completion_tokens: int + """The number of completion tokens generated.""" + + invocation_count: int + """The number of invocations.""" + + run_model_name: str = FieldInfo(alias="model_name") + """The name of the model.""" + + prompt_tokens: int + """The number of prompt tokens used.""" + + total_tokens: int + """The total number of tokens used.""" + + +class PerTestingCriteriaResult(BaseModel): + failed: int + """Number of tests failed for this criteria.""" + + passed: int + """Number of tests passed for this criteria.""" + + testing_criteria: str + """A description of the testing criteria.""" + + +class ResultCounts(BaseModel): + """Counters summarizing the outcomes of the evaluation run.""" + + errored: int + """Number of output items that resulted in an error.""" + + failed: int + """Number of output items that failed to pass the evaluation.""" + + passed: int + """Number of output items that passed the evaluation.""" + + total: int + """Total number of executed output items.""" + + +class RunRetrieveResponse(BaseModel): + """A schema representing an evaluation run.""" + + id: str + """Unique identifier for the evaluation run.""" + + created_at: int + """Unix timestamp (in seconds) when the evaluation run was created.""" + + data_source: DataSource + """Information about the run's data source.""" + + error: EvalAPIError + """An object representing an error response from the Eval API.""" + + eval_id: str + """The identifier of the associated evaluation.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: str + """The model that is evaluated, if applicable.""" + + name: str + """The name of the evaluation run.""" + + object: Literal["eval.run"] + """The type of the object. Always "eval.run".""" + + per_model_usage: List[PerModelUsage] + """Usage statistics for each model during the evaluation run.""" + + per_testing_criteria_results: List[PerTestingCriteriaResult] + """Results per testing criteria applied during the evaluation run.""" + + report_url: str + """The URL to the rendered evaluation run report on the UI dashboard.""" + + result_counts: ResultCounts + """Counters summarizing the outcomes of the evaluation run.""" + + status: str + """The status of the evaluation run.""" diff --git a/src/openai/types/evals/runs/__init__.py b/src/openai/types/evals/runs/__init__.py new file mode 100644 index 0000000000..b77cbb6acd --- /dev/null +++ b/src/openai/types/evals/runs/__init__.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .output_item_list_params import OutputItemListParams as OutputItemListParams +from .output_item_list_response import OutputItemListResponse as OutputItemListResponse +from .output_item_retrieve_response import OutputItemRetrieveResponse as OutputItemRetrieveResponse diff --git a/src/openai/types/evals/runs/output_item_list_params.py b/src/openai/types/evals/runs/output_item_list_params.py new file mode 100644 index 0000000000..073bfc69a7 --- /dev/null +++ b/src/openai/types/evals/runs/output_item_list_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["OutputItemListParams"] + + +class OutputItemListParams(TypedDict, total=False): + eval_id: Required[str] + + after: str + """Identifier for the last output item from the previous pagination request.""" + + limit: int + """Number of output items to retrieve.""" + + order: Literal["asc", "desc"] + """Sort order for output items by timestamp. + + Use `asc` for ascending order or `desc` for descending order. Defaults to `asc`. + """ + + status: Literal["fail", "pass"] + """Filter output items by status. + + Use `failed` to filter by failed output items or `pass` to filter by passed + output items. + """ diff --git a/src/openai/types/evals/runs/output_item_list_response.py b/src/openai/types/evals/runs/output_item_list_response.py new file mode 100644 index 0000000000..a906a29df7 --- /dev/null +++ b/src/openai/types/evals/runs/output_item_list_response.py @@ -0,0 +1,144 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, Dict, List, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ...._models import BaseModel +from ..eval_api_error import EvalAPIError + +__all__ = ["OutputItemListResponse", "Result", "Sample", "SampleInput", "SampleOutput", "SampleUsage"] + + +class Result(BaseModel): + """A single grader result for an evaluation run output item.""" + + name: str + """The name of the grader.""" + + passed: bool + """Whether the grader considered the output a pass.""" + + score: float + """The numeric score produced by the grader.""" + + sample: Optional[Dict[str, object]] = None + """Optional sample or intermediate data produced by the grader.""" + + type: Optional[str] = None + """The grader type (for example, "string-check-grader").""" + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class SampleInput(BaseModel): + """An input message.""" + + content: str + """The content of the message.""" + + role: str + """The role of the message sender (e.g., system, user, developer).""" + + +class SampleOutput(BaseModel): + content: Optional[str] = None + """The content of the message.""" + + role: Optional[str] = None + """The role of the message (e.g. "system", "assistant", "user").""" + + +class SampleUsage(BaseModel): + """Token usage details for the sample.""" + + cached_tokens: int + """The number of tokens retrieved from cache.""" + + completion_tokens: int + """The number of completion tokens generated.""" + + prompt_tokens: int + """The number of prompt tokens used.""" + + total_tokens: int + """The total number of tokens used.""" + + +class Sample(BaseModel): + """A sample containing the input and output of the evaluation run.""" + + error: EvalAPIError + """An object representing an error response from the Eval API.""" + + finish_reason: str + """The reason why the sample generation was finished.""" + + input: List[SampleInput] + """An array of input messages.""" + + max_completion_tokens: int + """The maximum number of tokens allowed for completion.""" + + model: str + """The model used for generating the sample.""" + + output: List[SampleOutput] + """An array of output messages.""" + + seed: int + """The seed used for generating the sample.""" + + temperature: float + """The sampling temperature used.""" + + top_p: float + """The top_p value used for sampling.""" + + usage: SampleUsage + """Token usage details for the sample.""" + + +class OutputItemListResponse(BaseModel): + """A schema representing an evaluation run output item.""" + + id: str + """Unique identifier for the evaluation run output item.""" + + created_at: int + """Unix timestamp (in seconds) when the evaluation run was created.""" + + datasource_item: Dict[str, object] + """Details of the input data source item.""" + + datasource_item_id: int + """The identifier for the data source item.""" + + eval_id: str + """The identifier of the evaluation group.""" + + object: Literal["eval.run.output_item"] + """The type of the object. Always "eval.run.output_item".""" + + results: List[Result] + """A list of grader results for this output item.""" + + run_id: str + """The identifier of the evaluation run associated with this output item.""" + + sample: Sample + """A sample containing the input and output of the evaluation run.""" + + status: str + """The status of the evaluation run.""" diff --git a/src/openai/types/evals/runs/output_item_retrieve_response.py b/src/openai/types/evals/runs/output_item_retrieve_response.py new file mode 100644 index 0000000000..42ba4b2864 --- /dev/null +++ b/src/openai/types/evals/runs/output_item_retrieve_response.py @@ -0,0 +1,144 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, Dict, List, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ...._models import BaseModel +from ..eval_api_error import EvalAPIError + +__all__ = ["OutputItemRetrieveResponse", "Result", "Sample", "SampleInput", "SampleOutput", "SampleUsage"] + + +class Result(BaseModel): + """A single grader result for an evaluation run output item.""" + + name: str + """The name of the grader.""" + + passed: bool + """Whether the grader considered the output a pass.""" + + score: float + """The numeric score produced by the grader.""" + + sample: Optional[Dict[str, object]] = None + """Optional sample or intermediate data produced by the grader.""" + + type: Optional[str] = None + """The grader type (for example, "string-check-grader").""" + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class SampleInput(BaseModel): + """An input message.""" + + content: str + """The content of the message.""" + + role: str + """The role of the message sender (e.g., system, user, developer).""" + + +class SampleOutput(BaseModel): + content: Optional[str] = None + """The content of the message.""" + + role: Optional[str] = None + """The role of the message (e.g. "system", "assistant", "user").""" + + +class SampleUsage(BaseModel): + """Token usage details for the sample.""" + + cached_tokens: int + """The number of tokens retrieved from cache.""" + + completion_tokens: int + """The number of completion tokens generated.""" + + prompt_tokens: int + """The number of prompt tokens used.""" + + total_tokens: int + """The total number of tokens used.""" + + +class Sample(BaseModel): + """A sample containing the input and output of the evaluation run.""" + + error: EvalAPIError + """An object representing an error response from the Eval API.""" + + finish_reason: str + """The reason why the sample generation was finished.""" + + input: List[SampleInput] + """An array of input messages.""" + + max_completion_tokens: int + """The maximum number of tokens allowed for completion.""" + + model: str + """The model used for generating the sample.""" + + output: List[SampleOutput] + """An array of output messages.""" + + seed: int + """The seed used for generating the sample.""" + + temperature: float + """The sampling temperature used.""" + + top_p: float + """The top_p value used for sampling.""" + + usage: SampleUsage + """Token usage details for the sample.""" + + +class OutputItemRetrieveResponse(BaseModel): + """A schema representing an evaluation run output item.""" + + id: str + """Unique identifier for the evaluation run output item.""" + + created_at: int + """Unix timestamp (in seconds) when the evaluation run was created.""" + + datasource_item: Dict[str, object] + """Details of the input data source item.""" + + datasource_item_id: int + """The identifier for the data source item.""" + + eval_id: str + """The identifier of the evaluation group.""" + + object: Literal["eval.run.output_item"] + """The type of the object. Always "eval.run.output_item".""" + + results: List[Result] + """A list of grader results for this output item.""" + + run_id: str + """The identifier of the evaluation run associated with this output item.""" + + sample: Sample + """A sample containing the input and output of the evaluation run.""" + + status: str + """The status of the evaluation run.""" diff --git a/src/openai/types/beta/file_chunking_strategy.py b/src/openai/types/file_chunking_strategy.py similarity index 93% rename from src/openai/types/beta/file_chunking_strategy.py rename to src/openai/types/file_chunking_strategy.py index 406d69dd0e..ee96bd7884 100644 --- a/src/openai/types/beta/file_chunking_strategy.py +++ b/src/openai/types/file_chunking_strategy.py @@ -3,7 +3,7 @@ from typing import Union from typing_extensions import Annotated, TypeAlias -from ..._utils import PropertyInfo +from .._utils import PropertyInfo from .other_file_chunking_strategy_object import OtherFileChunkingStrategyObject from .static_file_chunking_strategy_object import StaticFileChunkingStrategyObject diff --git a/src/openai/types/beta/file_chunking_strategy_param.py b/src/openai/types/file_chunking_strategy_param.py similarity index 71% rename from src/openai/types/beta/file_chunking_strategy_param.py rename to src/openai/types/file_chunking_strategy_param.py index 46383358e5..25d94286d8 100644 --- a/src/openai/types/beta/file_chunking_strategy_param.py +++ b/src/openai/types/file_chunking_strategy_param.py @@ -6,8 +6,8 @@ from typing_extensions import TypeAlias from .auto_file_chunking_strategy_param import AutoFileChunkingStrategyParam -from .static_file_chunking_strategy_param import StaticFileChunkingStrategyParam +from .static_file_chunking_strategy_object_param import StaticFileChunkingStrategyObjectParam __all__ = ["FileChunkingStrategyParam"] -FileChunkingStrategyParam: TypeAlias = Union[AutoFileChunkingStrategyParam, StaticFileChunkingStrategyParam] +FileChunkingStrategyParam: TypeAlias = Union[AutoFileChunkingStrategyParam, StaticFileChunkingStrategyObjectParam] diff --git a/src/openai/types/file_create_params.py b/src/openai/types/file_create_params.py index ecf7503358..5e2afc0655 100644 --- a/src/openai/types/file_create_params.py +++ b/src/openai/types/file_create_params.py @@ -2,12 +2,12 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import Literal, Required, TypedDict from .._types import FileTypes from .file_purpose import FilePurpose -__all__ = ["FileCreateParams"] +__all__ = ["FileCreateParams", "ExpiresAfter"] class FileCreateParams(TypedDict, total=False): @@ -15,12 +15,38 @@ class FileCreateParams(TypedDict, total=False): """The File object (not file name) to be uploaded.""" purpose: Required[FilePurpose] - """The intended purpose of the uploaded file. - - Use "assistants" for - [Assistants](https://platform.openai.com/docs/api-reference/assistants) and - [Message](https://platform.openai.com/docs/api-reference/messages) files, - "vision" for Assistants image file inputs, "batch" for - [Batch API](https://platform.openai.com/docs/guides/batch), and "fine-tune" for - [Fine-tuning](https://platform.openai.com/docs/api-reference/fine-tuning). + """The intended purpose of the uploaded file. One of: + + - `assistants`: Used in the Assistants API + - `batch`: Used in the Batch API + - `fine-tune`: Used for fine-tuning + - `vision`: Images used for vision fine-tuning + - `user_data`: Flexible file type for any purpose + - `evals`: Used for eval data sets + """ + + expires_after: ExpiresAfter + """The expiration policy for a file. + + By default, files with `purpose=batch` expire after 30 days and all other files + are persisted until they are manually deleted. + """ + + +class ExpiresAfter(TypedDict, total=False): + """The expiration policy for a file. + + By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted. + """ + + anchor: Required[Literal["created_at"]] + """Anchor timestamp after which the expiration policy applies. + + Supported anchors: `created_at`. + """ + + seconds: Required[int] + """The number of seconds after the anchor time that the file will expire. + + Must be between 3600 (1 hour) and 2592000 (30 days). """ diff --git a/src/openai/types/file_list_params.py b/src/openai/types/file_list_params.py index 212eca13c0..058d874c29 100644 --- a/src/openai/types/file_list_params.py +++ b/src/openai/types/file_list_params.py @@ -2,11 +2,32 @@ from __future__ import annotations -from typing_extensions import TypedDict +from typing_extensions import Literal, TypedDict __all__ = ["FileListParams"] class FileListParams(TypedDict, total=False): + after: str + """A cursor for use in pagination. + + `after` is an object ID that defines your place in the list. For instance, if + you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include after=obj_foo in order to fetch the next page of the + list. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 10,000, and the default is 10,000. + """ + + order: Literal["asc", "desc"] + """Sort order by the `created_at` timestamp of the objects. + + `asc` for ascending order and `desc` for descending order. + """ + purpose: str """Only return files with the given purpose.""" diff --git a/src/openai/types/file_object.py b/src/openai/types/file_object.py index 6e2bf310a4..4a9901fd3f 100644 --- a/src/openai/types/file_object.py +++ b/src/openai/types/file_object.py @@ -9,6 +9,8 @@ class FileObject(BaseModel): + """The `File` object represents a document that has been uploaded to OpenAI.""" + id: str """The file identifier, which can be referenced in the API endpoints.""" @@ -25,12 +27,19 @@ class FileObject(BaseModel): """The object type, which is always `file`.""" purpose: Literal[ - "assistants", "assistants_output", "batch", "batch_output", "fine-tune", "fine-tune-results", "vision" + "assistants", + "assistants_output", + "batch", + "batch_output", + "fine-tune", + "fine-tune-results", + "vision", + "user_data", ] """The intended purpose of the file. Supported values are `assistants`, `assistants_output`, `batch`, `batch_output`, - `fine-tune`, `fine-tune-results` and `vision`. + `fine-tune`, `fine-tune-results`, `vision`, and `user_data`. """ status: Literal["uploaded", "processed", "error"] @@ -40,6 +49,9 @@ class FileObject(BaseModel): `error`. """ + expires_at: Optional[int] = None + """The Unix timestamp (in seconds) for when the file will expire.""" + status_details: Optional[str] = None """Deprecated. diff --git a/src/openai/types/file_purpose.py b/src/openai/types/file_purpose.py index 32dc352c62..b2c2d5f9fc 100644 --- a/src/openai/types/file_purpose.py +++ b/src/openai/types/file_purpose.py @@ -4,4 +4,4 @@ __all__ = ["FilePurpose"] -FilePurpose: TypeAlias = Literal["assistants", "batch", "fine-tune", "vision"] +FilePurpose: TypeAlias = Literal["assistants", "batch", "fine-tune", "vision", "user_data", "evals"] diff --git a/src/openai/types/fine_tuning/__init__.py b/src/openai/types/fine_tuning/__init__.py index 92b81329b1..cc664eacea 100644 --- a/src/openai/types/fine_tuning/__init__.py +++ b/src/openai/types/fine_tuning/__init__.py @@ -2,13 +2,25 @@ from __future__ import annotations +from .dpo_method import DpoMethod as DpoMethod from .fine_tuning_job import FineTuningJob as FineTuningJob from .job_list_params import JobListParams as JobListParams +from .dpo_method_param import DpoMethodParam as DpoMethodParam from .job_create_params import JobCreateParams as JobCreateParams +from .supervised_method import SupervisedMethod as SupervisedMethod +from .dpo_hyperparameters import DpoHyperparameters as DpoHyperparameters +from .reinforcement_method import ReinforcementMethod as ReinforcementMethod from .fine_tuning_job_event import FineTuningJobEvent as FineTuningJobEvent from .job_list_events_params import JobListEventsParams as JobListEventsParams +from .supervised_method_param import SupervisedMethodParam as SupervisedMethodParam +from .dpo_hyperparameters_param import DpoHyperparametersParam as DpoHyperparametersParam +from .reinforcement_method_param import ReinforcementMethodParam as ReinforcementMethodParam +from .supervised_hyperparameters import SupervisedHyperparameters as SupervisedHyperparameters from .fine_tuning_job_integration import FineTuningJobIntegration as FineTuningJobIntegration +from .reinforcement_hyperparameters import ReinforcementHyperparameters as ReinforcementHyperparameters +from .supervised_hyperparameters_param import SupervisedHyperparametersParam as SupervisedHyperparametersParam from .fine_tuning_job_wandb_integration import FineTuningJobWandbIntegration as FineTuningJobWandbIntegration +from .reinforcement_hyperparameters_param import ReinforcementHyperparametersParam as ReinforcementHyperparametersParam from .fine_tuning_job_wandb_integration_object import ( FineTuningJobWandbIntegrationObject as FineTuningJobWandbIntegrationObject, ) diff --git a/src/openai/types/fine_tuning/alpha/__init__.py b/src/openai/types/fine_tuning/alpha/__init__.py new file mode 100644 index 0000000000..6394961b0b --- /dev/null +++ b/src/openai/types/fine_tuning/alpha/__init__.py @@ -0,0 +1,8 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .grader_run_params import GraderRunParams as GraderRunParams +from .grader_run_response import GraderRunResponse as GraderRunResponse +from .grader_validate_params import GraderValidateParams as GraderValidateParams +from .grader_validate_response import GraderValidateResponse as GraderValidateResponse diff --git a/src/openai/types/fine_tuning/alpha/grader_run_params.py b/src/openai/types/fine_tuning/alpha/grader_run_params.py new file mode 100644 index 0000000000..646407fe09 --- /dev/null +++ b/src/openai/types/fine_tuning/alpha/grader_run_params.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Required, TypeAlias, TypedDict + +from ...graders.multi_grader_param import MultiGraderParam +from ...graders.python_grader_param import PythonGraderParam +from ...graders.score_model_grader_param import ScoreModelGraderParam +from ...graders.string_check_grader_param import StringCheckGraderParam +from ...graders.text_similarity_grader_param import TextSimilarityGraderParam + +__all__ = ["GraderRunParams", "Grader"] + + +class GraderRunParams(TypedDict, total=False): + grader: Required[Grader] + """The grader used for the fine-tuning job.""" + + model_sample: Required[str] + """The model sample to be evaluated. + + This value will be used to populate the `sample` namespace. See + [the guide](https://platform.openai.com/docs/guides/graders) for more details. + The `output_json` variable will be populated if the model sample is a valid JSON + string. + """ + + item: object + """The dataset item provided to the grader. + + This will be used to populate the `item` namespace. See + [the guide](https://platform.openai.com/docs/guides/graders) for more details. + """ + + +Grader: TypeAlias = Union[ + StringCheckGraderParam, TextSimilarityGraderParam, PythonGraderParam, ScoreModelGraderParam, MultiGraderParam +] diff --git a/src/openai/types/fine_tuning/alpha/grader_run_response.py b/src/openai/types/fine_tuning/alpha/grader_run_response.py new file mode 100644 index 0000000000..8ef046d133 --- /dev/null +++ b/src/openai/types/fine_tuning/alpha/grader_run_response.py @@ -0,0 +1,67 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from pydantic import Field as FieldInfo + +from ...._models import BaseModel + +__all__ = ["GraderRunResponse", "Metadata", "MetadataErrors"] + + +class MetadataErrors(BaseModel): + formula_parse_error: bool + + invalid_variable_error: bool + + api_model_grader_parse_error: bool = FieldInfo(alias="model_grader_parse_error") + + api_model_grader_refusal_error: bool = FieldInfo(alias="model_grader_refusal_error") + + api_model_grader_server_error: bool = FieldInfo(alias="model_grader_server_error") + + api_model_grader_server_error_details: Optional[str] = FieldInfo( + alias="model_grader_server_error_details", default=None + ) + + other_error: bool + + python_grader_runtime_error: bool + + python_grader_runtime_error_details: Optional[str] = None + + python_grader_server_error: bool + + python_grader_server_error_type: Optional[str] = None + + sample_parse_error: bool + + truncated_observation_error: bool + + unresponsive_reward_error: bool + + +class Metadata(BaseModel): + errors: MetadataErrors + + execution_time: float + + name: str + + sampled_model_name: Optional[str] = None + + scores: Dict[str, object] + + token_usage: Optional[int] = None + + type: str + + +class GraderRunResponse(BaseModel): + metadata: Metadata + + api_model_grader_token_usage_per_model: Dict[str, object] = FieldInfo(alias="model_grader_token_usage_per_model") + + reward: float + + sub_rewards: Dict[str, object] diff --git a/src/openai/types/fine_tuning/alpha/grader_validate_params.py b/src/openai/types/fine_tuning/alpha/grader_validate_params.py new file mode 100644 index 0000000000..fe9eb44e32 --- /dev/null +++ b/src/openai/types/fine_tuning/alpha/grader_validate_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Required, TypeAlias, TypedDict + +from ...graders.multi_grader_param import MultiGraderParam +from ...graders.python_grader_param import PythonGraderParam +from ...graders.score_model_grader_param import ScoreModelGraderParam +from ...graders.string_check_grader_param import StringCheckGraderParam +from ...graders.text_similarity_grader_param import TextSimilarityGraderParam + +__all__ = ["GraderValidateParams", "Grader"] + + +class GraderValidateParams(TypedDict, total=False): + grader: Required[Grader] + """The grader used for the fine-tuning job.""" + + +Grader: TypeAlias = Union[ + StringCheckGraderParam, TextSimilarityGraderParam, PythonGraderParam, ScoreModelGraderParam, MultiGraderParam +] diff --git a/src/openai/types/fine_tuning/alpha/grader_validate_response.py b/src/openai/types/fine_tuning/alpha/grader_validate_response.py new file mode 100644 index 0000000000..b373292d80 --- /dev/null +++ b/src/openai/types/fine_tuning/alpha/grader_validate_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import TypeAlias + +from ...._models import BaseModel +from ...graders.multi_grader import MultiGrader +from ...graders.python_grader import PythonGrader +from ...graders.score_model_grader import ScoreModelGrader +from ...graders.string_check_grader import StringCheckGrader +from ...graders.text_similarity_grader import TextSimilarityGrader + +__all__ = ["GraderValidateResponse", "Grader"] + +Grader: TypeAlias = Union[StringCheckGrader, TextSimilarityGrader, PythonGrader, ScoreModelGrader, MultiGrader] + + +class GraderValidateResponse(BaseModel): + grader: Optional[Grader] = None + """The grader used for the fine-tuning job.""" diff --git a/src/openai/types/fine_tuning/checkpoints/__init__.py b/src/openai/types/fine_tuning/checkpoints/__init__.py new file mode 100644 index 0000000000..5447b4d818 --- /dev/null +++ b/src/openai/types/fine_tuning/checkpoints/__init__.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .permission_list_params import PermissionListParams as PermissionListParams +from .permission_create_params import PermissionCreateParams as PermissionCreateParams +from .permission_list_response import PermissionListResponse as PermissionListResponse +from .permission_create_response import PermissionCreateResponse as PermissionCreateResponse +from .permission_delete_response import PermissionDeleteResponse as PermissionDeleteResponse +from .permission_retrieve_params import PermissionRetrieveParams as PermissionRetrieveParams +from .permission_retrieve_response import PermissionRetrieveResponse as PermissionRetrieveResponse diff --git a/src/openai/types/fine_tuning/checkpoints/permission_create_params.py b/src/openai/types/fine_tuning/checkpoints/permission_create_params.py new file mode 100644 index 0000000000..e7cf4e4ee4 --- /dev/null +++ b/src/openai/types/fine_tuning/checkpoints/permission_create_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ...._types import SequenceNotStr + +__all__ = ["PermissionCreateParams"] + + +class PermissionCreateParams(TypedDict, total=False): + project_ids: Required[SequenceNotStr[str]] + """The project identifiers to grant access to.""" diff --git a/src/openai/types/fine_tuning/checkpoints/permission_create_response.py b/src/openai/types/fine_tuning/checkpoints/permission_create_response.py new file mode 100644 index 0000000000..459fa9dee7 --- /dev/null +++ b/src/openai/types/fine_tuning/checkpoints/permission_create_response.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["PermissionCreateResponse"] + + +class PermissionCreateResponse(BaseModel): + """ + The `checkpoint.permission` object represents a permission for a fine-tuned model checkpoint. + """ + + id: str + """The permission identifier, which can be referenced in the API endpoints.""" + + created_at: int + """The Unix timestamp (in seconds) for when the permission was created.""" + + object: Literal["checkpoint.permission"] + """The object type, which is always "checkpoint.permission".""" + + project_id: str + """The project identifier that the permission is for.""" diff --git a/src/openai/types/fine_tuning/checkpoints/permission_delete_response.py b/src/openai/types/fine_tuning/checkpoints/permission_delete_response.py new file mode 100644 index 0000000000..1a92d912fa --- /dev/null +++ b/src/openai/types/fine_tuning/checkpoints/permission_delete_response.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["PermissionDeleteResponse"] + + +class PermissionDeleteResponse(BaseModel): + id: str + """The ID of the fine-tuned model checkpoint permission that was deleted.""" + + deleted: bool + """Whether the fine-tuned model checkpoint permission was successfully deleted.""" + + object: Literal["checkpoint.permission"] + """The object type, which is always "checkpoint.permission".""" diff --git a/src/openai/types/fine_tuning/checkpoints/permission_list_params.py b/src/openai/types/fine_tuning/checkpoints/permission_list_params.py new file mode 100644 index 0000000000..1f389920aa --- /dev/null +++ b/src/openai/types/fine_tuning/checkpoints/permission_list_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["PermissionListParams"] + + +class PermissionListParams(TypedDict, total=False): + after: str + """Identifier for the last permission ID from the previous pagination request.""" + + limit: int + """Number of permissions to retrieve.""" + + order: Literal["ascending", "descending"] + """The order in which to retrieve permissions.""" + + project_id: str + """The ID of the project to get permissions for.""" diff --git a/src/openai/types/fine_tuning/checkpoints/permission_list_response.py b/src/openai/types/fine_tuning/checkpoints/permission_list_response.py new file mode 100644 index 0000000000..26e913e0c2 --- /dev/null +++ b/src/openai/types/fine_tuning/checkpoints/permission_list_response.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["PermissionListResponse"] + + +class PermissionListResponse(BaseModel): + """ + The `checkpoint.permission` object represents a permission for a fine-tuned model checkpoint. + """ + + id: str + """The permission identifier, which can be referenced in the API endpoints.""" + + created_at: int + """The Unix timestamp (in seconds) for when the permission was created.""" + + object: Literal["checkpoint.permission"] + """The object type, which is always "checkpoint.permission".""" + + project_id: str + """The project identifier that the permission is for.""" diff --git a/src/openai/types/fine_tuning/checkpoints/permission_retrieve_params.py b/src/openai/types/fine_tuning/checkpoints/permission_retrieve_params.py new file mode 100644 index 0000000000..6e66a867ca --- /dev/null +++ b/src/openai/types/fine_tuning/checkpoints/permission_retrieve_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["PermissionRetrieveParams"] + + +class PermissionRetrieveParams(TypedDict, total=False): + after: str + """Identifier for the last permission ID from the previous pagination request.""" + + limit: int + """Number of permissions to retrieve.""" + + order: Literal["ascending", "descending"] + """The order in which to retrieve permissions.""" + + project_id: str + """The ID of the project to get permissions for.""" diff --git a/src/openai/types/fine_tuning/checkpoints/permission_retrieve_response.py b/src/openai/types/fine_tuning/checkpoints/permission_retrieve_response.py new file mode 100644 index 0000000000..34208958ef --- /dev/null +++ b/src/openai/types/fine_tuning/checkpoints/permission_retrieve_response.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["PermissionRetrieveResponse", "Data"] + + +class Data(BaseModel): + """ + The `checkpoint.permission` object represents a permission for a fine-tuned model checkpoint. + """ + + id: str + """The permission identifier, which can be referenced in the API endpoints.""" + + created_at: int + """The Unix timestamp (in seconds) for when the permission was created.""" + + object: Literal["checkpoint.permission"] + """The object type, which is always "checkpoint.permission".""" + + project_id: str + """The project identifier that the permission is for.""" + + +class PermissionRetrieveResponse(BaseModel): + data: List[Data] + + has_more: bool + + object: Literal["list"] + + first_id: Optional[str] = None + + last_id: Optional[str] = None diff --git a/src/openai/types/fine_tuning/dpo_hyperparameters.py b/src/openai/types/fine_tuning/dpo_hyperparameters.py new file mode 100644 index 0000000000..cd39f308a4 --- /dev/null +++ b/src/openai/types/fine_tuning/dpo_hyperparameters.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["DpoHyperparameters"] + + +class DpoHyperparameters(BaseModel): + """The hyperparameters used for the DPO fine-tuning job.""" + + batch_size: Union[Literal["auto"], int, None] = None + """Number of examples in each batch. + + A larger batch size means that model parameters are updated less frequently, but + with lower variance. + """ + + beta: Union[Literal["auto"], float, None] = None + """The beta value for the DPO method. + + A higher beta value will increase the weight of the penalty between the policy + and reference model. + """ + + learning_rate_multiplier: Union[Literal["auto"], float, None] = None + """Scaling factor for the learning rate. + + A smaller learning rate may be useful to avoid overfitting. + """ + + n_epochs: Union[Literal["auto"], int, None] = None + """The number of epochs to train the model for. + + An epoch refers to one full cycle through the training dataset. + """ diff --git a/src/openai/types/fine_tuning/dpo_hyperparameters_param.py b/src/openai/types/fine_tuning/dpo_hyperparameters_param.py new file mode 100644 index 0000000000..12b2c41ca8 --- /dev/null +++ b/src/openai/types/fine_tuning/dpo_hyperparameters_param.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypedDict + +__all__ = ["DpoHyperparametersParam"] + + +class DpoHyperparametersParam(TypedDict, total=False): + """The hyperparameters used for the DPO fine-tuning job.""" + + batch_size: Union[Literal["auto"], int] + """Number of examples in each batch. + + A larger batch size means that model parameters are updated less frequently, but + with lower variance. + """ + + beta: Union[Literal["auto"], float] + """The beta value for the DPO method. + + A higher beta value will increase the weight of the penalty between the policy + and reference model. + """ + + learning_rate_multiplier: Union[Literal["auto"], float] + """Scaling factor for the learning rate. + + A smaller learning rate may be useful to avoid overfitting. + """ + + n_epochs: Union[Literal["auto"], int] + """The number of epochs to train the model for. + + An epoch refers to one full cycle through the training dataset. + """ diff --git a/src/openai/types/fine_tuning/dpo_method.py b/src/openai/types/fine_tuning/dpo_method.py new file mode 100644 index 0000000000..452c182016 --- /dev/null +++ b/src/openai/types/fine_tuning/dpo_method.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .dpo_hyperparameters import DpoHyperparameters + +__all__ = ["DpoMethod"] + + +class DpoMethod(BaseModel): + """Configuration for the DPO fine-tuning method.""" + + hyperparameters: Optional[DpoHyperparameters] = None + """The hyperparameters used for the DPO fine-tuning job.""" diff --git a/src/openai/types/fine_tuning/dpo_method_param.py b/src/openai/types/fine_tuning/dpo_method_param.py new file mode 100644 index 0000000000..6bd74d9760 --- /dev/null +++ b/src/openai/types/fine_tuning/dpo_method_param.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +from .dpo_hyperparameters_param import DpoHyperparametersParam + +__all__ = ["DpoMethodParam"] + + +class DpoMethodParam(TypedDict, total=False): + """Configuration for the DPO fine-tuning method.""" + + hyperparameters: DpoHyperparametersParam + """The hyperparameters used for the DPO fine-tuning job.""" diff --git a/src/openai/types/fine_tuning/fine_tuning_job.py b/src/openai/types/fine_tuning/fine_tuning_job.py index 7ac8792787..bb8a4d597b 100644 --- a/src/openai/types/fine_tuning/fine_tuning_job.py +++ b/src/openai/types/fine_tuning/fine_tuning_job.py @@ -4,12 +4,20 @@ from typing_extensions import Literal from ..._models import BaseModel +from .dpo_method import DpoMethod +from ..shared.metadata import Metadata +from .supervised_method import SupervisedMethod +from .reinforcement_method import ReinforcementMethod from .fine_tuning_job_wandb_integration_object import FineTuningJobWandbIntegrationObject -__all__ = ["FineTuningJob", "Error", "Hyperparameters"] +__all__ = ["FineTuningJob", "Error", "Hyperparameters", "Method"] class Error(BaseModel): + """ + For fine-tuning jobs that have `failed`, this will contain more information on the cause of the failure. + """ + code: str """A machine-readable error code.""" @@ -24,16 +32,52 @@ class Error(BaseModel): class Hyperparameters(BaseModel): - n_epochs: Union[Literal["auto"], int] + """The hyperparameters used for the fine-tuning job. + + This value will only be returned when running `supervised` jobs. + """ + + batch_size: Union[Literal["auto"], int, None] = None + """Number of examples in each batch. + + A larger batch size means that model parameters are updated less frequently, but + with lower variance. + """ + + learning_rate_multiplier: Union[Literal["auto"], float, None] = None + """Scaling factor for the learning rate. + + A smaller learning rate may be useful to avoid overfitting. + """ + + n_epochs: Union[Literal["auto"], int, None] = None """The number of epochs to train the model for. - An epoch refers to one full cycle through the training dataset. "auto" decides - the optimal number of epochs based on the size of the dataset. If setting the - number manually, we support any number between 1 and 50 epochs. + An epoch refers to one full cycle through the training dataset. """ +class Method(BaseModel): + """The method used for fine-tuning.""" + + type: Literal["supervised", "dpo", "reinforcement"] + """The type of method. Is either `supervised`, `dpo`, or `reinforcement`.""" + + dpo: Optional[DpoMethod] = None + """Configuration for the DPO fine-tuning method.""" + + reinforcement: Optional[ReinforcementMethod] = None + """Configuration for the reinforcement fine-tuning method.""" + + supervised: Optional[SupervisedMethod] = None + """Configuration for the supervised fine-tuning method.""" + + class FineTuningJob(BaseModel): + """ + The `fine_tuning.job` object represents a fine-tuning job that has been created through the API. + """ + id: str """The object identifier, which can be referenced in the API endpoints.""" @@ -61,8 +105,7 @@ class FineTuningJob(BaseModel): hyperparameters: Hyperparameters """The hyperparameters used for the fine-tuning job. - See the [fine-tuning guide](https://platform.openai.com/docs/guides/fine-tuning) - for more details. + This value will only be returned when running `supervised` jobs. """ model: str @@ -118,3 +161,16 @@ class FineTuningJob(BaseModel): integrations: Optional[List[FineTuningJobWandbIntegrationObject]] = None """A list of integrations to enable for this fine-tuning job.""" + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + method: Optional[Method] = None + """The method used for fine-tuning.""" diff --git a/src/openai/types/fine_tuning/fine_tuning_job_event.py b/src/openai/types/fine_tuning/fine_tuning_job_event.py index 2d204bb980..7452b818c6 100644 --- a/src/openai/types/fine_tuning/fine_tuning_job_event.py +++ b/src/openai/types/fine_tuning/fine_tuning_job_event.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import builtins +from typing import Optional from typing_extensions import Literal from ..._models import BaseModel @@ -8,12 +10,25 @@ class FineTuningJobEvent(BaseModel): + """Fine-tuning job event object""" + id: str + """The object identifier.""" created_at: int + """The Unix timestamp (in seconds) for when the fine-tuning job was created.""" level: Literal["info", "warn", "error"] + """The log level of the event.""" message: str + """The message of the event.""" object: Literal["fine_tuning.job.event"] + """The object type, which is always "fine_tuning.job.event".""" + + data: Optional[builtins.object] = None + """The data associated with the event.""" + + type: Optional[Literal["message", "metrics"]] = None + """The type of event.""" diff --git a/src/openai/types/fine_tuning/fine_tuning_job_integration.py b/src/openai/types/fine_tuning/fine_tuning_job_integration.py index 9a66aa4f17..2af73fbffb 100644 --- a/src/openai/types/fine_tuning/fine_tuning_job_integration.py +++ b/src/openai/types/fine_tuning/fine_tuning_job_integration.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .fine_tuning_job_wandb_integration_object import FineTuningJobWandbIntegrationObject FineTuningJobIntegration = FineTuningJobWandbIntegrationObject diff --git a/src/openai/types/fine_tuning/fine_tuning_job_wandb_integration.py b/src/openai/types/fine_tuning/fine_tuning_job_wandb_integration.py index 4ac282eb54..0e33aa84c8 100644 --- a/src/openai/types/fine_tuning/fine_tuning_job_wandb_integration.py +++ b/src/openai/types/fine_tuning/fine_tuning_job_wandb_integration.py @@ -8,6 +8,13 @@ class FineTuningJobWandbIntegration(BaseModel): + """The settings for your integration with Weights and Biases. + + This payload specifies the project that + metrics will be sent to. Optionally, you can set an explicit display name for your run, add tags + to your run, and set a default entity (team, username, etc) to be associated with your run. + """ + project: str """The name of the project that the new run will be created under.""" diff --git a/src/openai/types/fine_tuning/job_create_params.py b/src/openai/types/fine_tuning/job_create_params.py index 8f5ea86274..181bede2d9 100644 --- a/src/openai/types/fine_tuning/job_create_params.py +++ b/src/openai/types/fine_tuning/job_create_params.py @@ -2,10 +2,16 @@ from __future__ import annotations -from typing import List, Union, Iterable, Optional +from typing import Union, Iterable, Optional from typing_extensions import Literal, Required, TypedDict -__all__ = ["JobCreateParams", "Hyperparameters", "Integration", "IntegrationWandb"] +from ..._types import SequenceNotStr +from .dpo_method_param import DpoMethodParam +from ..shared_params.metadata import Metadata +from .supervised_method_param import SupervisedMethodParam +from .reinforcement_method_param import ReinforcementMethodParam + +__all__ = ["JobCreateParams", "Hyperparameters", "Integration", "IntegrationWandb", "Method"] class JobCreateParams(TypedDict, total=False): @@ -13,7 +19,7 @@ class JobCreateParams(TypedDict, total=False): """The name of the model to fine-tune. You can select one of the - [supported models](https://platform.openai.com/docs/guides/fine-tuning/which-models-can-be-fine-tuned). + [supported models](https://platform.openai.com/docs/guides/fine-tuning#which-models-can-be-fine-tuned). """ training_file: Required[str] @@ -26,20 +32,39 @@ class JobCreateParams(TypedDict, total=False): your file with the purpose `fine-tune`. The contents of the file should differ depending on if the model uses the - [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input) or + [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input), [completions](https://platform.openai.com/docs/api-reference/fine-tuning/completions-input) + format, or if the fine-tuning method uses the + [preference](https://platform.openai.com/docs/api-reference/fine-tuning/preference-input) format. - See the [fine-tuning guide](https://platform.openai.com/docs/guides/fine-tuning) + See the + [fine-tuning guide](https://platform.openai.com/docs/guides/model-optimization) for more details. """ hyperparameters: Hyperparameters - """The hyperparameters used for the fine-tuning job.""" + """ + The hyperparameters used for the fine-tuning job. This value is now deprecated + in favor of `method`, and should be passed in under the `method` parameter. + """ integrations: Optional[Iterable[Integration]] """A list of integrations to enable for your fine-tuning job.""" + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + method: Method + """The method used for fine-tuning.""" + seed: Optional[int] """The seed controls the reproducibility of the job. @@ -68,12 +93,18 @@ class JobCreateParams(TypedDict, total=False): Your dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`. - See the [fine-tuning guide](https://platform.openai.com/docs/guides/fine-tuning) + See the + [fine-tuning guide](https://platform.openai.com/docs/guides/model-optimization) for more details. """ class Hyperparameters(TypedDict, total=False): + """ + The hyperparameters used for the fine-tuning job. + This value is now deprecated in favor of `method`, and should be passed in under the `method` parameter. + """ + batch_size: Union[Literal["auto"], int] """Number of examples in each batch. @@ -95,6 +126,13 @@ class Hyperparameters(TypedDict, total=False): class IntegrationWandb(TypedDict, total=False): + """The settings for your integration with Weights and Biases. + + This payload specifies the project that + metrics will be sent to. Optionally, you can set an explicit display name for your run, add tags + to your run, and set a default entity (team, username, etc) to be associated with your run. + """ + project: Required[str] """The name of the project that the new run will be created under.""" @@ -112,7 +150,7 @@ class IntegrationWandb(TypedDict, total=False): If not set, we will use the Job ID as the name. """ - tags: List[str] + tags: SequenceNotStr[str] """A list of tags to be attached to the newly created run. These tags are passed through directly to WandB. Some default tags are generated @@ -134,3 +172,19 @@ class Integration(TypedDict, total=False): can set an explicit display name for your run, add tags to your run, and set a default entity (team, username, etc) to be associated with your run. """ + + +class Method(TypedDict, total=False): + """The method used for fine-tuning.""" + + type: Required[Literal["supervised", "dpo", "reinforcement"]] + """The type of method. Is either `supervised`, `dpo`, or `reinforcement`.""" + + dpo: DpoMethodParam + """Configuration for the DPO fine-tuning method.""" + + reinforcement: ReinforcementMethodParam + """Configuration for the reinforcement fine-tuning method.""" + + supervised: SupervisedMethodParam + """Configuration for the supervised fine-tuning method.""" diff --git a/src/openai/types/fine_tuning/job_list_params.py b/src/openai/types/fine_tuning/job_list_params.py index 5c075ca33f..b79f3ce86a 100644 --- a/src/openai/types/fine_tuning/job_list_params.py +++ b/src/openai/types/fine_tuning/job_list_params.py @@ -2,6 +2,7 @@ from __future__ import annotations +from typing import Dict, Optional from typing_extensions import TypedDict __all__ = ["JobListParams"] @@ -13,3 +14,10 @@ class JobListParams(TypedDict, total=False): limit: int """Number of fine-tuning jobs to retrieve.""" + + metadata: Optional[Dict[str, str]] + """Optional metadata filter. + + To filter, use the syntax `metadata[k]=v`. Alternatively, set `metadata=null` to + indicate no metadata. + """ diff --git a/src/openai/types/fine_tuning/jobs/fine_tuning_job_checkpoint.py b/src/openai/types/fine_tuning/jobs/fine_tuning_job_checkpoint.py index bd07317a3e..f8a04b6395 100644 --- a/src/openai/types/fine_tuning/jobs/fine_tuning_job_checkpoint.py +++ b/src/openai/types/fine_tuning/jobs/fine_tuning_job_checkpoint.py @@ -9,6 +9,8 @@ class Metrics(BaseModel): + """Metrics at the step number during the fine-tuning job.""" + full_valid_loss: Optional[float] = None full_valid_mean_token_accuracy: Optional[float] = None @@ -25,6 +27,10 @@ class Metrics(BaseModel): class FineTuningJobCheckpoint(BaseModel): + """ + The `fine_tuning.job.checkpoint` object represents a model checkpoint for a fine-tuning job that is ready to use. + """ + id: str """The checkpoint identifier, which can be referenced in the API endpoints.""" diff --git a/src/openai/types/fine_tuning/reinforcement_hyperparameters.py b/src/openai/types/fine_tuning/reinforcement_hyperparameters.py new file mode 100644 index 0000000000..4c289fd659 --- /dev/null +++ b/src/openai/types/fine_tuning/reinforcement_hyperparameters.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ReinforcementHyperparameters"] + + +class ReinforcementHyperparameters(BaseModel): + """The hyperparameters used for the reinforcement fine-tuning job.""" + + batch_size: Union[Literal["auto"], int, None] = None + """Number of examples in each batch. + + A larger batch size means that model parameters are updated less frequently, but + with lower variance. + """ + + compute_multiplier: Union[Literal["auto"], float, None] = None + """ + Multiplier on amount of compute used for exploring search space during training. + """ + + eval_interval: Union[Literal["auto"], int, None] = None + """The number of training steps between evaluation runs.""" + + eval_samples: Union[Literal["auto"], int, None] = None + """Number of evaluation samples to generate per training step.""" + + learning_rate_multiplier: Union[Literal["auto"], float, None] = None + """Scaling factor for the learning rate. + + A smaller learning rate may be useful to avoid overfitting. + """ + + n_epochs: Union[Literal["auto"], int, None] = None + """The number of epochs to train the model for. + + An epoch refers to one full cycle through the training dataset. + """ + + reasoning_effort: Optional[Literal["default", "low", "medium", "high"]] = None + """Level of reasoning effort.""" diff --git a/src/openai/types/fine_tuning/reinforcement_hyperparameters_param.py b/src/openai/types/fine_tuning/reinforcement_hyperparameters_param.py new file mode 100644 index 0000000000..7be716f143 --- /dev/null +++ b/src/openai/types/fine_tuning/reinforcement_hyperparameters_param.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypedDict + +__all__ = ["ReinforcementHyperparametersParam"] + + +class ReinforcementHyperparametersParam(TypedDict, total=False): + """The hyperparameters used for the reinforcement fine-tuning job.""" + + batch_size: Union[Literal["auto"], int] + """Number of examples in each batch. + + A larger batch size means that model parameters are updated less frequently, but + with lower variance. + """ + + compute_multiplier: Union[Literal["auto"], float] + """ + Multiplier on amount of compute used for exploring search space during training. + """ + + eval_interval: Union[Literal["auto"], int] + """The number of training steps between evaluation runs.""" + + eval_samples: Union[Literal["auto"], int] + """Number of evaluation samples to generate per training step.""" + + learning_rate_multiplier: Union[Literal["auto"], float] + """Scaling factor for the learning rate. + + A smaller learning rate may be useful to avoid overfitting. + """ + + n_epochs: Union[Literal["auto"], int] + """The number of epochs to train the model for. + + An epoch refers to one full cycle through the training dataset. + """ + + reasoning_effort: Literal["default", "low", "medium", "high"] + """Level of reasoning effort.""" diff --git a/src/openai/types/fine_tuning/reinforcement_method.py b/src/openai/types/fine_tuning/reinforcement_method.py new file mode 100644 index 0000000000..a8a3685148 --- /dev/null +++ b/src/openai/types/fine_tuning/reinforcement_method.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import TypeAlias + +from ..._models import BaseModel +from ..graders.multi_grader import MultiGrader +from ..graders.python_grader import PythonGrader +from ..graders.score_model_grader import ScoreModelGrader +from ..graders.string_check_grader import StringCheckGrader +from .reinforcement_hyperparameters import ReinforcementHyperparameters +from ..graders.text_similarity_grader import TextSimilarityGrader + +__all__ = ["ReinforcementMethod", "Grader"] + +Grader: TypeAlias = Union[StringCheckGrader, TextSimilarityGrader, PythonGrader, ScoreModelGrader, MultiGrader] + + +class ReinforcementMethod(BaseModel): + """Configuration for the reinforcement fine-tuning method.""" + + grader: Grader + """The grader used for the fine-tuning job.""" + + hyperparameters: Optional[ReinforcementHyperparameters] = None + """The hyperparameters used for the reinforcement fine-tuning job.""" diff --git a/src/openai/types/fine_tuning/reinforcement_method_param.py b/src/openai/types/fine_tuning/reinforcement_method_param.py new file mode 100644 index 0000000000..ea75bfeb69 --- /dev/null +++ b/src/openai/types/fine_tuning/reinforcement_method_param.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Required, TypeAlias, TypedDict + +from ..graders.multi_grader_param import MultiGraderParam +from ..graders.python_grader_param import PythonGraderParam +from ..graders.score_model_grader_param import ScoreModelGraderParam +from ..graders.string_check_grader_param import StringCheckGraderParam +from .reinforcement_hyperparameters_param import ReinforcementHyperparametersParam +from ..graders.text_similarity_grader_param import TextSimilarityGraderParam + +__all__ = ["ReinforcementMethodParam", "Grader"] + +Grader: TypeAlias = Union[ + StringCheckGraderParam, TextSimilarityGraderParam, PythonGraderParam, ScoreModelGraderParam, MultiGraderParam +] + + +class ReinforcementMethodParam(TypedDict, total=False): + """Configuration for the reinforcement fine-tuning method.""" + + grader: Required[Grader] + """The grader used for the fine-tuning job.""" + + hyperparameters: ReinforcementHyperparametersParam + """The hyperparameters used for the reinforcement fine-tuning job.""" diff --git a/src/openai/types/fine_tuning/supervised_hyperparameters.py b/src/openai/types/fine_tuning/supervised_hyperparameters.py new file mode 100644 index 0000000000..1231bbdd80 --- /dev/null +++ b/src/openai/types/fine_tuning/supervised_hyperparameters.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["SupervisedHyperparameters"] + + +class SupervisedHyperparameters(BaseModel): + """The hyperparameters used for the fine-tuning job.""" + + batch_size: Union[Literal["auto"], int, None] = None + """Number of examples in each batch. + + A larger batch size means that model parameters are updated less frequently, but + with lower variance. + """ + + learning_rate_multiplier: Union[Literal["auto"], float, None] = None + """Scaling factor for the learning rate. + + A smaller learning rate may be useful to avoid overfitting. + """ + + n_epochs: Union[Literal["auto"], int, None] = None + """The number of epochs to train the model for. + + An epoch refers to one full cycle through the training dataset. + """ diff --git a/src/openai/types/fine_tuning/supervised_hyperparameters_param.py b/src/openai/types/fine_tuning/supervised_hyperparameters_param.py new file mode 100644 index 0000000000..de0e021dea --- /dev/null +++ b/src/openai/types/fine_tuning/supervised_hyperparameters_param.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypedDict + +__all__ = ["SupervisedHyperparametersParam"] + + +class SupervisedHyperparametersParam(TypedDict, total=False): + """The hyperparameters used for the fine-tuning job.""" + + batch_size: Union[Literal["auto"], int] + """Number of examples in each batch. + + A larger batch size means that model parameters are updated less frequently, but + with lower variance. + """ + + learning_rate_multiplier: Union[Literal["auto"], float] + """Scaling factor for the learning rate. + + A smaller learning rate may be useful to avoid overfitting. + """ + + n_epochs: Union[Literal["auto"], int] + """The number of epochs to train the model for. + + An epoch refers to one full cycle through the training dataset. + """ diff --git a/src/openai/types/fine_tuning/supervised_method.py b/src/openai/types/fine_tuning/supervised_method.py new file mode 100644 index 0000000000..96e102582d --- /dev/null +++ b/src/openai/types/fine_tuning/supervised_method.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .supervised_hyperparameters import SupervisedHyperparameters + +__all__ = ["SupervisedMethod"] + + +class SupervisedMethod(BaseModel): + """Configuration for the supervised fine-tuning method.""" + + hyperparameters: Optional[SupervisedHyperparameters] = None + """The hyperparameters used for the fine-tuning job.""" diff --git a/src/openai/types/fine_tuning/supervised_method_param.py b/src/openai/types/fine_tuning/supervised_method_param.py new file mode 100644 index 0000000000..4381cd184b --- /dev/null +++ b/src/openai/types/fine_tuning/supervised_method_param.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +from .supervised_hyperparameters_param import SupervisedHyperparametersParam + +__all__ = ["SupervisedMethodParam"] + + +class SupervisedMethodParam(TypedDict, total=False): + """Configuration for the supervised fine-tuning method.""" + + hyperparameters: SupervisedHyperparametersParam + """The hyperparameters used for the fine-tuning job.""" diff --git a/src/openai/types/graders/__init__.py b/src/openai/types/graders/__init__.py new file mode 100644 index 0000000000..4f70eb6c2f --- /dev/null +++ b/src/openai/types/graders/__init__.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .multi_grader import MultiGrader as MultiGrader +from .grader_inputs import GraderInputs as GraderInputs +from .python_grader import PythonGrader as PythonGrader +from .label_model_grader import LabelModelGrader as LabelModelGrader +from .multi_grader_param import MultiGraderParam as MultiGraderParam +from .score_model_grader import ScoreModelGrader as ScoreModelGrader +from .grader_inputs_param import GraderInputsParam as GraderInputsParam +from .python_grader_param import PythonGraderParam as PythonGraderParam +from .string_check_grader import StringCheckGrader as StringCheckGrader +from .text_similarity_grader import TextSimilarityGrader as TextSimilarityGrader +from .label_model_grader_param import LabelModelGraderParam as LabelModelGraderParam +from .score_model_grader_param import ScoreModelGraderParam as ScoreModelGraderParam +from .string_check_grader_param import StringCheckGraderParam as StringCheckGraderParam +from .text_similarity_grader_param import TextSimilarityGraderParam as TextSimilarityGraderParam diff --git a/src/openai/types/graders/grader_inputs.py b/src/openai/types/graders/grader_inputs.py new file mode 100644 index 0000000000..edc966d889 --- /dev/null +++ b/src/openai/types/graders/grader_inputs.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from ..responses.response_input_text import ResponseInputText +from ..responses.response_input_audio import ResponseInputAudio + +__all__ = ["GraderInputs", "GraderInputItem", "GraderInputItemOutputText", "GraderInputItemInputImage"] + + +class GraderInputItemOutputText(BaseModel): + """A text output from the model.""" + + text: str + """The text output from the model.""" + + type: Literal["output_text"] + """The type of the output text. Always `output_text`.""" + + +class GraderInputItemInputImage(BaseModel): + """An image input block used within EvalItem content arrays.""" + + image_url: str + """The URL of the image input.""" + + type: Literal["input_image"] + """The type of the image input. Always `input_image`.""" + + detail: Optional[str] = None + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +GraderInputItem: TypeAlias = Union[ + str, ResponseInputText, GraderInputItemOutputText, GraderInputItemInputImage, ResponseInputAudio +] + +GraderInputs: TypeAlias = List[GraderInputItem] diff --git a/src/openai/types/graders/grader_inputs_param.py b/src/openai/types/graders/grader_inputs_param.py new file mode 100644 index 0000000000..7d8341eb32 --- /dev/null +++ b/src/openai/types/graders/grader_inputs_param.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..responses.response_input_text_param import ResponseInputTextParam +from ..responses.response_input_audio_param import ResponseInputAudioParam + +__all__ = [ + "GraderInputsParam", + "GraderInputsParamItem", + "GraderInputsParamItemOutputText", + "GraderInputsParamItemInputImage", +] + + +class GraderInputsParamItemOutputText(TypedDict, total=False): + """A text output from the model.""" + + text: Required[str] + """The text output from the model.""" + + type: Required[Literal["output_text"]] + """The type of the output text. Always `output_text`.""" + + +class GraderInputsParamItemInputImage(TypedDict, total=False): + """An image input block used within EvalItem content arrays.""" + + image_url: Required[str] + """The URL of the image input.""" + + type: Required[Literal["input_image"]] + """The type of the image input. Always `input_image`.""" + + detail: str + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +GraderInputsParamItem: TypeAlias = Union[ + str, + ResponseInputTextParam, + GraderInputsParamItemOutputText, + GraderInputsParamItemInputImage, + ResponseInputAudioParam, +] + +GraderInputsParam: TypeAlias = List[GraderInputsParamItem] diff --git a/src/openai/types/graders/label_model_grader.py b/src/openai/types/graders/label_model_grader.py new file mode 100644 index 0000000000..d3c942235e --- /dev/null +++ b/src/openai/types/graders/label_model_grader.py @@ -0,0 +1,92 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .grader_inputs import GraderInputs +from ..responses.response_input_text import ResponseInputText +from ..responses.response_input_audio import ResponseInputAudio + +__all__ = ["LabelModelGrader", "Input", "InputContent", "InputContentOutputText", "InputContentInputImage"] + + +class InputContentOutputText(BaseModel): + """A text output from the model.""" + + text: str + """The text output from the model.""" + + type: Literal["output_text"] + """The type of the output text. Always `output_text`.""" + + +class InputContentInputImage(BaseModel): + """An image input block used within EvalItem content arrays.""" + + image_url: str + """The URL of the image input.""" + + type: Literal["input_image"] + """The type of the image input. Always `input_image`.""" + + detail: Optional[str] = None + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +InputContent: TypeAlias = Union[ + str, ResponseInputText, InputContentOutputText, InputContentInputImage, ResponseInputAudio, GraderInputs +] + + +class Input(BaseModel): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: InputContent + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Literal["user", "assistant", "system", "developer"] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Optional[Literal["message"]] = None + """The type of the message input. Always `message`.""" + + +class LabelModelGrader(BaseModel): + """ + A LabelModelGrader object which uses a model to assign labels to each item + in the evaluation. + """ + + input: List[Input] + + labels: List[str] + """The labels to assign to each item in the evaluation.""" + + model: str + """The model to use for the evaluation. Must support structured outputs.""" + + name: str + """The name of the grader.""" + + passing_labels: List[str] + """The labels that indicate a passing result. Must be a subset of labels.""" + + type: Literal["label_model"] + """The object type, which is always `label_model`.""" diff --git a/src/openai/types/graders/label_model_grader_param.py b/src/openai/types/graders/label_model_grader_param.py new file mode 100644 index 0000000000..a5b6959cff --- /dev/null +++ b/src/openai/types/graders/label_model_grader_param.py @@ -0,0 +1,99 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from .grader_inputs_param import GraderInputsParam +from ..responses.response_input_text_param import ResponseInputTextParam +from ..responses.response_input_audio_param import ResponseInputAudioParam + +__all__ = ["LabelModelGraderParam", "Input", "InputContent", "InputContentOutputText", "InputContentInputImage"] + + +class InputContentOutputText(TypedDict, total=False): + """A text output from the model.""" + + text: Required[str] + """The text output from the model.""" + + type: Required[Literal["output_text"]] + """The type of the output text. Always `output_text`.""" + + +class InputContentInputImage(TypedDict, total=False): + """An image input block used within EvalItem content arrays.""" + + image_url: Required[str] + """The URL of the image input.""" + + type: Required[Literal["input_image"]] + """The type of the image input. Always `input_image`.""" + + detail: str + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +InputContent: TypeAlias = Union[ + str, + ResponseInputTextParam, + InputContentOutputText, + InputContentInputImage, + ResponseInputAudioParam, + GraderInputsParam, +] + + +class Input(TypedDict, total=False): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: Required[InputContent] + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Required[Literal["user", "assistant", "system", "developer"]] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Literal["message"] + """The type of the message input. Always `message`.""" + + +class LabelModelGraderParam(TypedDict, total=False): + """ + A LabelModelGrader object which uses a model to assign labels to each item + in the evaluation. + """ + + input: Required[Iterable[Input]] + + labels: Required[SequenceNotStr[str]] + """The labels to assign to each item in the evaluation.""" + + model: Required[str] + """The model to use for the evaluation. Must support structured outputs.""" + + name: Required[str] + """The name of the grader.""" + + passing_labels: Required[SequenceNotStr[str]] + """The labels that indicate a passing result. Must be a subset of labels.""" + + type: Required[Literal["label_model"]] + """The object type, which is always `label_model`.""" diff --git a/src/openai/types/graders/multi_grader.py b/src/openai/types/graders/multi_grader.py new file mode 100644 index 0000000000..022ddb406a --- /dev/null +++ b/src/openai/types/graders/multi_grader.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .python_grader import PythonGrader +from .label_model_grader import LabelModelGrader +from .score_model_grader import ScoreModelGrader +from .string_check_grader import StringCheckGrader +from .text_similarity_grader import TextSimilarityGrader + +__all__ = ["MultiGrader", "Graders"] + +Graders: TypeAlias = Union[StringCheckGrader, TextSimilarityGrader, PythonGrader, ScoreModelGrader, LabelModelGrader] + + +class MultiGrader(BaseModel): + """ + A MultiGrader object combines the output of multiple graders to produce a single score. + """ + + calculate_output: str + """A formula to calculate the output based on grader results.""" + + graders: Graders + """ + A StringCheckGrader object that performs a string comparison between input and + reference using a specified operation. + """ + + name: str + """The name of the grader.""" + + type: Literal["multi"] + """The object type, which is always `multi`.""" diff --git a/src/openai/types/graders/multi_grader_param.py b/src/openai/types/graders/multi_grader_param.py new file mode 100644 index 0000000000..064267a5aa --- /dev/null +++ b/src/openai/types/graders/multi_grader_param.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .python_grader_param import PythonGraderParam +from .label_model_grader_param import LabelModelGraderParam +from .score_model_grader_param import ScoreModelGraderParam +from .string_check_grader_param import StringCheckGraderParam +from .text_similarity_grader_param import TextSimilarityGraderParam + +__all__ = ["MultiGraderParam", "Graders"] + +Graders: TypeAlias = Union[ + StringCheckGraderParam, TextSimilarityGraderParam, PythonGraderParam, ScoreModelGraderParam, LabelModelGraderParam +] + + +class MultiGraderParam(TypedDict, total=False): + """ + A MultiGrader object combines the output of multiple graders to produce a single score. + """ + + calculate_output: Required[str] + """A formula to calculate the output based on grader results.""" + + graders: Required[Graders] + """ + A StringCheckGrader object that performs a string comparison between input and + reference using a specified operation. + """ + + name: Required[str] + """The name of the grader.""" + + type: Required[Literal["multi"]] + """The object type, which is always `multi`.""" diff --git a/src/openai/types/graders/python_grader.py b/src/openai/types/graders/python_grader.py new file mode 100644 index 0000000000..81aafdae0a --- /dev/null +++ b/src/openai/types/graders/python_grader.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["PythonGrader"] + + +class PythonGrader(BaseModel): + """A PythonGrader object that runs a python script on the input.""" + + name: str + """The name of the grader.""" + + source: str + """The source code of the python script.""" + + type: Literal["python"] + """The object type, which is always `python`.""" + + image_tag: Optional[str] = None + """The image tag to use for the python script.""" diff --git a/src/openai/types/graders/python_grader_param.py b/src/openai/types/graders/python_grader_param.py new file mode 100644 index 0000000000..3be7bab432 --- /dev/null +++ b/src/openai/types/graders/python_grader_param.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["PythonGraderParam"] + + +class PythonGraderParam(TypedDict, total=False): + """A PythonGrader object that runs a python script on the input.""" + + name: Required[str] + """The name of the grader.""" + + source: Required[str] + """The source code of the python script.""" + + type: Required[Literal["python"]] + """The object type, which is always `python`.""" + + image_tag: str + """The image tag to use for the python script.""" diff --git a/src/openai/types/graders/score_model_grader.py b/src/openai/types/graders/score_model_grader.py new file mode 100644 index 0000000000..85d11e8666 --- /dev/null +++ b/src/openai/types/graders/score_model_grader.py @@ -0,0 +1,135 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .grader_inputs import GraderInputs +from ..shared.reasoning_effort import ReasoningEffort +from ..responses.response_input_text import ResponseInputText +from ..responses.response_input_audio import ResponseInputAudio + +__all__ = [ + "ScoreModelGrader", + "Input", + "InputContent", + "InputContentOutputText", + "InputContentInputImage", + "SamplingParams", +] + + +class InputContentOutputText(BaseModel): + """A text output from the model.""" + + text: str + """The text output from the model.""" + + type: Literal["output_text"] + """The type of the output text. Always `output_text`.""" + + +class InputContentInputImage(BaseModel): + """An image input block used within EvalItem content arrays.""" + + image_url: str + """The URL of the image input.""" + + type: Literal["input_image"] + """The type of the image input. Always `input_image`.""" + + detail: Optional[str] = None + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +InputContent: TypeAlias = Union[ + str, ResponseInputText, InputContentOutputText, InputContentInputImage, ResponseInputAudio, GraderInputs +] + + +class Input(BaseModel): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: InputContent + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Literal["user", "assistant", "system", "developer"] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Optional[Literal["message"]] = None + """The type of the message input. Always `message`.""" + + +class SamplingParams(BaseModel): + """The sampling parameters for the model.""" + + max_completions_tokens: Optional[int] = None + """The maximum number of tokens the grader model may generate in its response.""" + + reasoning_effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + seed: Optional[int] = None + """A seed value to initialize the randomness, during sampling.""" + + temperature: Optional[float] = None + """A higher temperature increases randomness in the outputs.""" + + top_p: Optional[float] = None + """An alternative to temperature for nucleus sampling; 1.0 includes all tokens.""" + + +class ScoreModelGrader(BaseModel): + """A ScoreModelGrader object that uses a model to assign a score to the input.""" + + input: List[Input] + """The input messages evaluated by the grader. + + Supports text, output text, input image, and input audio content blocks, and may + include template strings. + """ + + model: str + """The model to use for the evaluation.""" + + name: str + """The name of the grader.""" + + type: Literal["score_model"] + """The object type, which is always `score_model`.""" + + range: Optional[List[float]] = None + """The range of the score. Defaults to `[0, 1]`.""" + + sampling_params: Optional[SamplingParams] = None + """The sampling parameters for the model.""" diff --git a/src/openai/types/graders/score_model_grader_param.py b/src/openai/types/graders/score_model_grader_param.py new file mode 100644 index 0000000000..9f1c42e051 --- /dev/null +++ b/src/openai/types/graders/score_model_grader_param.py @@ -0,0 +1,141 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .grader_inputs_param import GraderInputsParam +from ..shared.reasoning_effort import ReasoningEffort +from ..responses.response_input_text_param import ResponseInputTextParam +from ..responses.response_input_audio_param import ResponseInputAudioParam + +__all__ = [ + "ScoreModelGraderParam", + "Input", + "InputContent", + "InputContentOutputText", + "InputContentInputImage", + "SamplingParams", +] + + +class InputContentOutputText(TypedDict, total=False): + """A text output from the model.""" + + text: Required[str] + """The text output from the model.""" + + type: Required[Literal["output_text"]] + """The type of the output text. Always `output_text`.""" + + +class InputContentInputImage(TypedDict, total=False): + """An image input block used within EvalItem content arrays.""" + + image_url: Required[str] + """The URL of the image input.""" + + type: Required[Literal["input_image"]] + """The type of the image input. Always `input_image`.""" + + detail: str + """The detail level of the image to be sent to the model. + + One of `high`, `low`, or `auto`. Defaults to `auto`. + """ + + +InputContent: TypeAlias = Union[ + str, + ResponseInputTextParam, + InputContentOutputText, + InputContentInputImage, + ResponseInputAudioParam, + GraderInputsParam, +] + + +class Input(TypedDict, total=False): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: Required[InputContent] + """Inputs to the model - can contain template strings. + + Supports text, output text, input images, and input audio, either as a single + item or an array of items. + """ + + role: Required[Literal["user", "assistant", "system", "developer"]] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + type: Literal["message"] + """The type of the message input. Always `message`.""" + + +class SamplingParams(TypedDict, total=False): + """The sampling parameters for the model.""" + + max_completions_tokens: Optional[int] + """The maximum number of tokens the grader model may generate in its response.""" + + reasoning_effort: Optional[ReasoningEffort] + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + seed: Optional[int] + """A seed value to initialize the randomness, during sampling.""" + + temperature: Optional[float] + """A higher temperature increases randomness in the outputs.""" + + top_p: Optional[float] + """An alternative to temperature for nucleus sampling; 1.0 includes all tokens.""" + + +class ScoreModelGraderParam(TypedDict, total=False): + """A ScoreModelGrader object that uses a model to assign a score to the input.""" + + input: Required[Iterable[Input]] + """The input messages evaluated by the grader. + + Supports text, output text, input image, and input audio content blocks, and may + include template strings. + """ + + model: Required[str] + """The model to use for the evaluation.""" + + name: Required[str] + """The name of the grader.""" + + type: Required[Literal["score_model"]] + """The object type, which is always `score_model`.""" + + range: Iterable[float] + """The range of the score. Defaults to `[0, 1]`.""" + + sampling_params: SamplingParams + """The sampling parameters for the model.""" diff --git a/src/openai/types/graders/string_check_grader.py b/src/openai/types/graders/string_check_grader.py new file mode 100644 index 0000000000..efd3679da9 --- /dev/null +++ b/src/openai/types/graders/string_check_grader.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["StringCheckGrader"] + + +class StringCheckGrader(BaseModel): + """ + A StringCheckGrader object that performs a string comparison between input and reference using a specified operation. + """ + + input: str + """The input text. This may include template strings.""" + + name: str + """The name of the grader.""" + + operation: Literal["eq", "ne", "like", "ilike"] + """The string check operation to perform. One of `eq`, `ne`, `like`, or `ilike`.""" + + reference: str + """The reference text. This may include template strings.""" + + type: Literal["string_check"] + """The object type, which is always `string_check`.""" diff --git a/src/openai/types/graders/string_check_grader_param.py b/src/openai/types/graders/string_check_grader_param.py new file mode 100644 index 0000000000..da9e961568 --- /dev/null +++ b/src/openai/types/graders/string_check_grader_param.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["StringCheckGraderParam"] + + +class StringCheckGraderParam(TypedDict, total=False): + """ + A StringCheckGrader object that performs a string comparison between input and reference using a specified operation. + """ + + input: Required[str] + """The input text. This may include template strings.""" + + name: Required[str] + """The name of the grader.""" + + operation: Required[Literal["eq", "ne", "like", "ilike"]] + """The string check operation to perform. One of `eq`, `ne`, `like`, or `ilike`.""" + + reference: Required[str] + """The reference text. This may include template strings.""" + + type: Required[Literal["string_check"]] + """The object type, which is always `string_check`.""" diff --git a/src/openai/types/graders/text_similarity_grader.py b/src/openai/types/graders/text_similarity_grader.py new file mode 100644 index 0000000000..a9d39a2fbd --- /dev/null +++ b/src/openai/types/graders/text_similarity_grader.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["TextSimilarityGrader"] + + +class TextSimilarityGrader(BaseModel): + """A TextSimilarityGrader object which grades text based on similarity metrics.""" + + evaluation_metric: Literal[ + "cosine", + "fuzzy_match", + "bleu", + "gleu", + "meteor", + "rouge_1", + "rouge_2", + "rouge_3", + "rouge_4", + "rouge_5", + "rouge_l", + ] + """The evaluation metric to use. + + One of `cosine`, `fuzzy_match`, `bleu`, `gleu`, `meteor`, `rouge_1`, `rouge_2`, + `rouge_3`, `rouge_4`, `rouge_5`, or `rouge_l`. + """ + + input: str + """The text being graded.""" + + name: str + """The name of the grader.""" + + reference: str + """The text being graded against.""" + + type: Literal["text_similarity"] + """The type of grader.""" diff --git a/src/openai/types/graders/text_similarity_grader_param.py b/src/openai/types/graders/text_similarity_grader_param.py new file mode 100644 index 0000000000..0907c3c2a7 --- /dev/null +++ b/src/openai/types/graders/text_similarity_grader_param.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["TextSimilarityGraderParam"] + + +class TextSimilarityGraderParam(TypedDict, total=False): + """A TextSimilarityGrader object which grades text based on similarity metrics.""" + + evaluation_metric: Required[ + Literal[ + "cosine", + "fuzzy_match", + "bleu", + "gleu", + "meteor", + "rouge_1", + "rouge_2", + "rouge_3", + "rouge_4", + "rouge_5", + "rouge_l", + ] + ] + """The evaluation metric to use. + + One of `cosine`, `fuzzy_match`, `bleu`, `gleu`, `meteor`, `rouge_1`, `rouge_2`, + `rouge_3`, `rouge_4`, `rouge_5`, or `rouge_l`. + """ + + input: Required[str] + """The text being graded.""" + + name: Required[str] + """The name of the grader.""" + + reference: Required[str] + """The text being graded against.""" + + type: Required[Literal["text_similarity"]] + """The type of grader.""" diff --git a/src/openai/types/image.py b/src/openai/types/image.py index f48aa2c702..dcbdb2aceb 100644 --- a/src/openai/types/image.py +++ b/src/openai/types/image.py @@ -8,17 +8,21 @@ class Image(BaseModel): + """Represents the content or the URL of an image generated by the OpenAI API.""" + b64_json: Optional[str] = None - """ - The base64-encoded JSON of the generated image, if `response_format` is - `b64_json`. + """The base64-encoded JSON of the generated image. + + Returned by default for the GPT image models, and only present if + `response_format` is set to `b64_json` for `dall-e-2` and `dall-e-3`. """ revised_prompt: Optional[str] = None - """ - The prompt that was used to generate the image, if there was any revision to the - prompt. - """ + """For `dall-e-3` only, the revised prompt that was used to generate the image.""" url: Optional[str] = None - """The URL of the generated image, if `response_format` is `url` (default).""" + """ + When using `dall-e-2` or `dall-e-3`, the URL of the generated image if + `response_format` is set to `url` (default value). Unsupported for the GPT image + models. + """ diff --git a/src/openai/types/image_create_variation_params.py b/src/openai/types/image_create_variation_params.py index d6ecf0f1ae..d10b74b2c2 100644 --- a/src/openai/types/image_create_variation_params.py +++ b/src/openai/types/image_create_variation_params.py @@ -25,10 +25,7 @@ class ImageCreateVariationParams(TypedDict, total=False): """ n: Optional[int] - """The number of images to generate. - - Must be between 1 and 10. For `dall-e-3`, only `n=1` is supported. - """ + """The number of images to generate. Must be between 1 and 10.""" response_format: Optional[Literal["url", "b64_json"]] """The format in which the generated images are returned. @@ -47,5 +44,5 @@ class ImageCreateVariationParams(TypedDict, total=False): """ A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). """ diff --git a/src/openai/types/image_edit_completed_event.py b/src/openai/types/image_edit_completed_event.py new file mode 100644 index 0000000000..e2e193413f --- /dev/null +++ b/src/openai/types/image_edit_completed_event.py @@ -0,0 +1,66 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ImageEditCompletedEvent", "Usage", "UsageInputTokensDetails"] + + +class UsageInputTokensDetails(BaseModel): + """The input tokens detailed information for the image generation.""" + + image_tokens: int + """The number of image tokens in the input prompt.""" + + text_tokens: int + """The number of text tokens in the input prompt.""" + + +class Usage(BaseModel): + """ + For the GPT image models only, the token usage information for the image generation. + """ + + input_tokens: int + """The number of tokens (images and text) in the input prompt.""" + + input_tokens_details: UsageInputTokensDetails + """The input tokens detailed information for the image generation.""" + + output_tokens: int + """The number of image tokens in the output image.""" + + total_tokens: int + """The total number of tokens (images and text) used for the image generation.""" + + +class ImageEditCompletedEvent(BaseModel): + """Emitted when image editing has completed and the final image is available.""" + + b64_json: str + """Base64-encoded final edited image data, suitable for rendering as an image.""" + + background: Literal["transparent", "opaque", "auto"] + """The background setting for the edited image.""" + + created_at: int + """The Unix timestamp when the event was created.""" + + output_format: Literal["png", "webp", "jpeg"] + """The output format for the edited image.""" + + quality: Literal["low", "medium", "high", "auto"] + """The quality setting for the edited image.""" + + size: Literal["1024x1024", "1024x1536", "1536x1024", "auto"] + """The size of the edited image.""" + + type: Literal["image_edit.completed"] + """The type of the event. Always `image_edit.completed`.""" + + usage: Usage + """ + For the GPT image models only, the token usage information for the image + generation. + """ diff --git a/src/openai/types/image_edit_params.py b/src/openai/types/image_edit_params.py index a596a8692b..1bd6dfd320 100644 --- a/src/openai/types/image_edit_params.py +++ b/src/openai/types/image_edit_params.py @@ -5,58 +5,156 @@ from typing import Union, Optional from typing_extensions import Literal, Required, TypedDict -from .._types import FileTypes +from .._types import FileTypes, SequenceNotStr from .image_model import ImageModel -__all__ = ["ImageEditParams"] +__all__ = ["ImageEditParamsBase", "ImageEditParamsNonStreaming", "ImageEditParamsStreaming"] -class ImageEditParams(TypedDict, total=False): - image: Required[FileTypes] - """The image to edit. +class ImageEditParamsBase(TypedDict, total=False): + image: Required[Union[FileTypes, SequenceNotStr[FileTypes]]] + """The image(s) to edit. Must be a supported image file or an array of images. - Must be a valid PNG file, less than 4MB, and square. If mask is not provided, - image must have transparency, which will be used as the mask. + For the GPT image models (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`, + `gpt-image-2`, `gpt-image-2-2026-04-21`, and `chatgpt-image-latest`), each image + should be a `png`, `webp`, or `jpg` file less than 50MB. You can provide up to + 16 images. + + For `dall-e-2`, you can only provide one image, and it should be a square `png` + file less than 4MB. """ prompt: Required[str] """A text description of the desired image(s). - The maximum length is 1000 characters. + The maximum length is 1000 characters for `dall-e-2`, and 32000 characters for + the GPT image models. + """ + + background: Optional[Literal["transparent", "opaque", "auto"]] + """ + Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + """ + + input_fidelity: Optional[Literal["high", "low"]] + """ + Control how much effort the model will exert to match the style and features, + especially facial features, of input images. This parameter is only supported + for `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for + `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`. """ mask: FileTypes """An additional image whose fully transparent areas (e.g. - where alpha is zero) indicate where `image` should be edited. Must be a valid - PNG file, less than 4MB, and have the same dimensions as `image`. + where alpha is zero) indicate where `image` should be edited. If there are + multiple images provided, the mask will be applied on the first image. Must be a + valid PNG file, less than 4MB, and have the same dimensions as `image`. """ model: Union[str, ImageModel, None] """The model to use for image generation. - Only `dall-e-2` is supported at this time. + One of `dall-e-2` or a GPT image model (`gpt-image-1`, `gpt-image-1-mini`, + `gpt-image-1.5`, `gpt-image-2`, `gpt-image-2-2026-04-21`, or + `chatgpt-image-latest`). Defaults to `gpt-image-1.5`. """ n: Optional[int] """The number of images to generate. Must be between 1 and 10.""" + output_compression: Optional[int] + """The compression level (0-100%) for the generated images. + + This parameter is only supported for the GPT image models with the `webp` or + `jpeg` output formats, and defaults to 100. + """ + + output_format: Optional[Literal["png", "jpeg", "webp"]] + """The format in which the generated images are returned. + + This parameter is only supported for the GPT image models. Must be one of `png`, + `jpeg`, or `webp`. The default value is `png`. + """ + + partial_images: Optional[int] + """The number of partial images to generate. + + This parameter is used for streaming responses that return partial images. Value + must be between 0 and 3. When set to 0, the response will be a single image sent + in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + """ + + quality: Optional[Literal["standard", "low", "medium", "high", "auto"]] + """The quality of the image that will be generated for GPT image models. + + Defaults to `auto`. + """ + response_format: Optional[Literal["url", "b64_json"]] """The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the - image has been generated. + image has been generated. This parameter is only supported for `dall-e-2` + (default is `url` for `dall-e-2`), as GPT image models always return + base64-encoded images. """ - size: Optional[Literal["256x256", "512x512", "1024x1024"]] + size: Union[str, Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"], None] """The size of the generated images. - Must be one of `256x256`, `512x512`, or `1024x1024`. + For `gpt-image-2` and `gpt-image-2-2026-04-21`, arbitrary resolutions are + supported as `WIDTHxHEIGHT` strings, for example `1536x864`. Width and height + must both be divisible by 16 and the requested aspect ratio must be between 1:3 + and 3:1. Resolutions above `2560x1440` are experimental, and the maximum + supported resolution is `3840x2160`. The requested size must also satisfy the + model's current pixel and edge limits. The standard sizes `1024x1024`, + `1536x1024`, and `1024x1536` are supported by the GPT image models; `auto` is + supported for models that allow automatic sizing. For `dall-e-2`, use one of + `256x256`, `512x512`, or `1024x1024`. For `dall-e-3`, use one of `1024x1024`, + `1792x1024`, or `1024x1792`. """ user: str """ A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). """ + + +class ImageEditParamsNonStreaming(ImageEditParamsBase, total=False): + stream: Optional[Literal[False]] + """Edit the image in streaming mode. + + Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. + """ + + +class ImageEditParamsStreaming(ImageEditParamsBase): + stream: Required[Literal[True]] + """Edit the image in streaming mode. + + Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. + """ + + +ImageEditParams = Union[ImageEditParamsNonStreaming, ImageEditParamsStreaming] diff --git a/src/openai/types/image_edit_partial_image_event.py b/src/openai/types/image_edit_partial_image_event.py new file mode 100644 index 0000000000..7bbd8c9b13 --- /dev/null +++ b/src/openai/types/image_edit_partial_image_event.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ImageEditPartialImageEvent"] + + +class ImageEditPartialImageEvent(BaseModel): + """Emitted when a partial image is available during image editing streaming.""" + + b64_json: str + """Base64-encoded partial image data, suitable for rendering as an image.""" + + background: Literal["transparent", "opaque", "auto"] + """The background setting for the requested edited image.""" + + created_at: int + """The Unix timestamp when the event was created.""" + + output_format: Literal["png", "webp", "jpeg"] + """The output format for the requested edited image.""" + + partial_image_index: int + """0-based index for the partial image (streaming).""" + + quality: Literal["low", "medium", "high", "auto"] + """The quality setting for the requested edited image.""" + + size: Literal["1024x1024", "1024x1536", "1536x1024", "auto"] + """The size of the requested edited image.""" + + type: Literal["image_edit.partial_image"] + """The type of the event. Always `image_edit.partial_image`.""" diff --git a/src/openai/types/image_edit_stream_event.py b/src/openai/types/image_edit_stream_event.py new file mode 100644 index 0000000000..759f6c6db5 --- /dev/null +++ b/src/openai/types/image_edit_stream_event.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from .._utils import PropertyInfo +from .image_edit_completed_event import ImageEditCompletedEvent +from .image_edit_partial_image_event import ImageEditPartialImageEvent + +__all__ = ["ImageEditStreamEvent"] + +ImageEditStreamEvent: TypeAlias = Annotated[ + Union[ImageEditPartialImageEvent, ImageEditCompletedEvent], PropertyInfo(discriminator="type") +] diff --git a/src/openai/types/image_gen_completed_event.py b/src/openai/types/image_gen_completed_event.py new file mode 100644 index 0000000000..813ed889d8 --- /dev/null +++ b/src/openai/types/image_gen_completed_event.py @@ -0,0 +1,66 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ImageGenCompletedEvent", "Usage", "UsageInputTokensDetails"] + + +class UsageInputTokensDetails(BaseModel): + """The input tokens detailed information for the image generation.""" + + image_tokens: int + """The number of image tokens in the input prompt.""" + + text_tokens: int + """The number of text tokens in the input prompt.""" + + +class Usage(BaseModel): + """ + For the GPT image models only, the token usage information for the image generation. + """ + + input_tokens: int + """The number of tokens (images and text) in the input prompt.""" + + input_tokens_details: UsageInputTokensDetails + """The input tokens detailed information for the image generation.""" + + output_tokens: int + """The number of image tokens in the output image.""" + + total_tokens: int + """The total number of tokens (images and text) used for the image generation.""" + + +class ImageGenCompletedEvent(BaseModel): + """Emitted when image generation has completed and the final image is available.""" + + b64_json: str + """Base64-encoded image data, suitable for rendering as an image.""" + + background: Literal["transparent", "opaque", "auto"] + """The background setting for the generated image.""" + + created_at: int + """The Unix timestamp when the event was created.""" + + output_format: Literal["png", "webp", "jpeg"] + """The output format for the generated image.""" + + quality: Literal["low", "medium", "high", "auto"] + """The quality setting for the generated image.""" + + size: Literal["1024x1024", "1024x1536", "1536x1024", "auto"] + """The size of the generated image.""" + + type: Literal["image_generation.completed"] + """The type of the event. Always `image_generation.completed`.""" + + usage: Usage + """ + For the GPT image models only, the token usage information for the image + generation. + """ diff --git a/src/openai/types/image_gen_partial_image_event.py b/src/openai/types/image_gen_partial_image_event.py new file mode 100644 index 0000000000..df29c00a63 --- /dev/null +++ b/src/openai/types/image_gen_partial_image_event.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ImageGenPartialImageEvent"] + + +class ImageGenPartialImageEvent(BaseModel): + """Emitted when a partial image is available during image generation streaming.""" + + b64_json: str + """Base64-encoded partial image data, suitable for rendering as an image.""" + + background: Literal["transparent", "opaque", "auto"] + """The background setting for the requested image.""" + + created_at: int + """The Unix timestamp when the event was created.""" + + output_format: Literal["png", "webp", "jpeg"] + """The output format for the requested image.""" + + partial_image_index: int + """0-based index for the partial image (streaming).""" + + quality: Literal["low", "medium", "high", "auto"] + """The quality setting for the requested image.""" + + size: Literal["1024x1024", "1024x1536", "1536x1024", "auto"] + """The size of the requested image.""" + + type: Literal["image_generation.partial_image"] + """The type of the event. Always `image_generation.partial_image`.""" diff --git a/src/openai/types/image_gen_stream_event.py b/src/openai/types/image_gen_stream_event.py new file mode 100644 index 0000000000..7dde5d5245 --- /dev/null +++ b/src/openai/types/image_gen_stream_event.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from .._utils import PropertyInfo +from .image_gen_completed_event import ImageGenCompletedEvent +from .image_gen_partial_image_event import ImageGenPartialImageEvent + +__all__ = ["ImageGenStreamEvent"] + +ImageGenStreamEvent: TypeAlias = Annotated[ + Union[ImageGenPartialImageEvent, ImageGenCompletedEvent], PropertyInfo(discriminator="type") +] diff --git a/src/openai/types/image_generate_params.py b/src/openai/types/image_generate_params.py index 307adeb3da..0c5070b130 100644 --- a/src/openai/types/image_generate_params.py +++ b/src/openai/types/image_generate_params.py @@ -7,19 +7,48 @@ from .image_model import ImageModel -__all__ = ["ImageGenerateParams"] +__all__ = ["ImageGenerateParamsBase", "ImageGenerateParamsNonStreaming", "ImageGenerateParamsStreaming"] -class ImageGenerateParams(TypedDict, total=False): +class ImageGenerateParamsBase(TypedDict, total=False): prompt: Required[str] """A text description of the desired image(s). - The maximum length is 1000 characters for `dall-e-2` and 4000 characters for - `dall-e-3`. + The maximum length is 32000 characters for the GPT image models, 1000 characters + for `dall-e-2` and 4000 characters for `dall-e-3`. + """ + + background: Optional[Literal["transparent", "opaque", "auto"]] + """ + Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. """ model: Union[str, ImageModel, None] - """The model to use for image generation.""" + """The model to use for image generation. + + One of `dall-e-2`, `dall-e-3`, or a GPT image model (`gpt-image-1`, + `gpt-image-1-mini`, `gpt-image-1.5`, `gpt-image-2`, or + `gpt-image-2-2026-04-21`). Defaults to `dall-e-2` unless a parameter specific to + the GPT image models is used. + """ + + moderation: Optional[Literal["low", "auto"]] + """ + Control the content-moderation level for images generated by the GPT image + models. Must be either `low` for less restrictive filtering or `auto` (default + value). + """ n: Optional[int] """The number of images to generate. @@ -27,39 +56,104 @@ class ImageGenerateParams(TypedDict, total=False): Must be between 1 and 10. For `dall-e-3`, only `n=1` is supported. """ - quality: Literal["standard", "hd"] + output_compression: Optional[int] + """The compression level (0-100%) for the generated images. + + This parameter is only supported for the GPT image models with the `webp` or + `jpeg` output formats, and defaults to 100. + """ + + output_format: Optional[Literal["png", "jpeg", "webp"]] + """The format in which the generated images are returned. + + This parameter is only supported for the GPT image models. Must be one of `png`, + `jpeg`, or `webp`. + """ + + partial_images: Optional[int] + """The number of partial images to generate. + + This parameter is used for streaming responses that return partial images. Value + must be between 0 and 3. When set to 0, the response will be a single image sent + in one streaming event. + + Note that the final image may be sent before the full number of partial images + are generated if the full image is generated more quickly. + """ + + quality: Optional[Literal["standard", "hd", "low", "medium", "high", "auto"]] """The quality of the image that will be generated. - `hd` creates images with finer details and greater consistency across the image. - This param is only supported for `dall-e-3`. + - `auto` (default value) will automatically select the best quality for the + given model. + - `high`, `medium` and `low` are supported for the GPT image models. + - `hd` and `standard` are supported for `dall-e-3`. + - `standard` is the only option for `dall-e-2`. """ response_format: Optional[Literal["url", "b64_json"]] - """The format in which the generated images are returned. + """The format in which generated images with `dall-e-2` and `dall-e-3` are + returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the - image has been generated. + image has been generated. This parameter isn't supported for the GPT image + models, which always return base64-encoded images. """ - size: Optional[Literal["256x256", "512x512", "1024x1024", "1792x1024", "1024x1792"]] + size: Union[ + str, + Literal["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792"], + None, + ] """The size of the generated images. - Must be one of `256x256`, `512x512`, or `1024x1024` for `dall-e-2`. Must be one - of `1024x1024`, `1792x1024`, or `1024x1792` for `dall-e-3` models. + For `gpt-image-2` and `gpt-image-2-2026-04-21`, arbitrary resolutions are + supported as `WIDTHxHEIGHT` strings, for example `1536x864`. Width and height + must both be divisible by 16 and the requested aspect ratio must be between 1:3 + and 3:1. Resolutions above `2560x1440` are experimental, and the maximum + supported resolution is `3840x2160`. The requested size must also satisfy the + model's current pixel and edge limits. The standard sizes `1024x1024`, + `1536x1024`, and `1024x1536` are supported by the GPT image models; `auto` is + supported for models that allow automatic sizing. For `dall-e-2`, use one of + `256x256`, `512x512`, or `1024x1024`. For `dall-e-3`, use one of `1024x1024`, + `1792x1024`, or `1024x1792`. """ style: Optional[Literal["vivid", "natural"]] """The style of the generated images. - Must be one of `vivid` or `natural`. Vivid causes the model to lean towards - generating hyper-real and dramatic images. Natural causes the model to produce - more natural, less hyper-real looking images. This param is only supported for - `dall-e-3`. + This parameter is only supported for `dall-e-3`. Must be one of `vivid` or + `natural`. Vivid causes the model to lean towards generating hyper-real and + dramatic images. Natural causes the model to produce more natural, less + hyper-real looking images. """ user: str """ A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. - [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids). + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids). """ + + +class ImageGenerateParamsNonStreaming(ImageGenerateParamsBase, total=False): + stream: Optional[Literal[False]] + """Generate the image in streaming mode. + + Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. This parameter is only supported for the GPT image models. + """ + + +class ImageGenerateParamsStreaming(ImageGenerateParamsBase): + stream: Required[Literal[True]] + """Generate the image in streaming mode. + + Defaults to `false`. See the + [Image generation guide](https://platform.openai.com/docs/guides/image-generation) + for more information. This parameter is only supported for the GPT image models. + """ + + +ImageGenerateParams = Union[ImageGenerateParamsNonStreaming, ImageGenerateParamsStreaming] diff --git a/src/openai/types/image_input_reference_param.py b/src/openai/types/image_input_reference_param.py new file mode 100644 index 0000000000..1065632910 --- /dev/null +++ b/src/openai/types/image_input_reference_param.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ImageInputReferenceParam"] + + +class ImageInputReferenceParam(TypedDict, total=False): + file_id: str + + image_url: str + """A fully qualified URL or base64-encoded data URL.""" diff --git a/src/openai/types/image_model.py b/src/openai/types/image_model.py index 1672369bea..4586fdebdf 100644 --- a/src/openai/types/image_model.py +++ b/src/openai/types/image_model.py @@ -4,4 +4,13 @@ __all__ = ["ImageModel"] -ImageModel: TypeAlias = Literal["dall-e-2", "dall-e-3"] +ImageModel: TypeAlias = Literal[ + "gpt-image-1", + "gpt-image-1-mini", + "gpt-image-2", + "gpt-image-2-2026-04-21", + "gpt-image-1.5", + "chatgpt-image-latest", + "dall-e-2", + "dall-e-3", +] diff --git a/src/openai/types/images_response.py b/src/openai/types/images_response.py index 7cee813184..3e832aadf2 100644 --- a/src/openai/types/images_response.py +++ b/src/openai/types/images_response.py @@ -1,14 +1,79 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional +from typing_extensions import Literal from .image import Image from .._models import BaseModel -__all__ = ["ImagesResponse"] +__all__ = ["ImagesResponse", "Usage", "UsageInputTokensDetails", "UsageOutputTokensDetails"] + + +class UsageInputTokensDetails(BaseModel): + """The input tokens detailed information for the image generation.""" + + image_tokens: int + """The number of image tokens in the input prompt.""" + + text_tokens: int + """The number of text tokens in the input prompt.""" + + +class UsageOutputTokensDetails(BaseModel): + """The output token details for the image generation.""" + + image_tokens: int + """The number of image output tokens generated by the model.""" + + text_tokens: int + """The number of text output tokens generated by the model.""" + + +class Usage(BaseModel): + """For `gpt-image-1` only, the token usage information for the image generation.""" + + input_tokens: int + """The number of tokens (images and text) in the input prompt.""" + + input_tokens_details: UsageInputTokensDetails + """The input tokens detailed information for the image generation.""" + + output_tokens: int + """The number of output tokens generated by the model.""" + + total_tokens: int + """The total number of tokens (images and text) used for the image generation.""" + + output_tokens_details: Optional[UsageOutputTokensDetails] = None + """The output token details for the image generation.""" class ImagesResponse(BaseModel): + """The response from the image generation endpoint.""" + created: int + """The Unix timestamp (in seconds) of when the image was created.""" + + background: Optional[Literal["transparent", "opaque"]] = None + """The background parameter used for the image generation. + + Either `transparent` or `opaque`. + """ + + data: Optional[List[Image]] = None + """The list of generated images.""" + + output_format: Optional[Literal["png", "webp", "jpeg"]] = None + """The output format of the image generation. Either `png`, `webp`, or `jpeg`.""" + + quality: Optional[Literal["low", "medium", "high"]] = None + """The quality of the image generated. Either `low`, `medium`, or `high`.""" + + size: Optional[Literal["1024x1024", "1024x1536", "1536x1024"]] = None + """The size of the image generated. + + Either `1024x1024`, `1024x1536`, or `1536x1024`. + """ - data: List[Image] + usage: Optional[Usage] = None + """For `gpt-image-1` only, the token usage information for the image generation.""" diff --git a/src/openai/types/model.py b/src/openai/types/model.py index 2631ee8d1a..6506224a20 100644 --- a/src/openai/types/model.py +++ b/src/openai/types/model.py @@ -8,6 +8,8 @@ class Model(BaseModel): + """Describes an OpenAI model offering that can be used with the API.""" + id: str """The model identifier, which can be referenced in the API endpoints.""" diff --git a/src/openai/types/model_deleted.py b/src/openai/types/model_deleted.py index 7f81e1b380..e7601f74e4 100644 --- a/src/openai/types/model_deleted.py +++ b/src/openai/types/model_deleted.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["ModelDeleted"] diff --git a/src/openai/types/moderation.py b/src/openai/types/moderation.py index 5aa691823a..a6acc26db4 100644 --- a/src/openai/types/moderation.py +++ b/src/openai/types/moderation.py @@ -1,14 +1,18 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import List, Optional +from typing_extensions import Literal from pydantic import Field as FieldInfo from .._models import BaseModel -__all__ = ["Moderation", "Categories", "CategoryScores"] +__all__ = ["Moderation", "Categories", "CategoryAppliedInputTypes", "CategoryScores"] class Categories(BaseModel): + """A list of the categories, and whether they are flagged or not.""" + harassment: bool """ Content that expresses, incites, or promotes harassing language towards any @@ -36,6 +40,20 @@ class Categories(BaseModel): orientation, disability status, or caste. """ + illicit: Optional[bool] = None + """ + Content that includes instructions or advice that facilitate the planning or + execution of wrongdoing, or that gives advice or instruction on how to commit + illicit acts. For example, "how to shoplift" would fit this category. + """ + + illicit_violent: Optional[bool] = FieldInfo(alias="illicit/violent", default=None) + """ + Content that includes instructions or advice that facilitate the planning or + execution of wrongdoing that also includes violence, or that gives advice or + instruction on the procurement of any weapon. + """ + self_harm: bool = FieldInfo(alias="self-harm") """ Content that promotes, encourages, or depicts acts of self-harm, such as @@ -72,7 +90,54 @@ class Categories(BaseModel): """Content that depicts death, violence, or physical injury in graphic detail.""" +class CategoryAppliedInputTypes(BaseModel): + """ + A list of the categories along with the input type(s) that the score applies to. + """ + + harassment: List[Literal["text"]] + """The applied input type(s) for the category 'harassment'.""" + + harassment_threatening: List[Literal["text"]] = FieldInfo(alias="harassment/threatening") + """The applied input type(s) for the category 'harassment/threatening'.""" + + hate: List[Literal["text"]] + """The applied input type(s) for the category 'hate'.""" + + hate_threatening: List[Literal["text"]] = FieldInfo(alias="hate/threatening") + """The applied input type(s) for the category 'hate/threatening'.""" + + illicit: List[Literal["text"]] + """The applied input type(s) for the category 'illicit'.""" + + illicit_violent: List[Literal["text"]] = FieldInfo(alias="illicit/violent") + """The applied input type(s) for the category 'illicit/violent'.""" + + self_harm: List[Literal["text", "image"]] = FieldInfo(alias="self-harm") + """The applied input type(s) for the category 'self-harm'.""" + + self_harm_instructions: List[Literal["text", "image"]] = FieldInfo(alias="self-harm/instructions") + """The applied input type(s) for the category 'self-harm/instructions'.""" + + self_harm_intent: List[Literal["text", "image"]] = FieldInfo(alias="self-harm/intent") + """The applied input type(s) for the category 'self-harm/intent'.""" + + sexual: List[Literal["text", "image"]] + """The applied input type(s) for the category 'sexual'.""" + + sexual_minors: List[Literal["text"]] = FieldInfo(alias="sexual/minors") + """The applied input type(s) for the category 'sexual/minors'.""" + + violence: List[Literal["text", "image"]] + """The applied input type(s) for the category 'violence'.""" + + violence_graphic: List[Literal["text", "image"]] = FieldInfo(alias="violence/graphic") + """The applied input type(s) for the category 'violence/graphic'.""" + + class CategoryScores(BaseModel): + """A list of the categories along with their scores as predicted by model.""" + harassment: float """The score for the category 'harassment'.""" @@ -85,6 +150,12 @@ class CategoryScores(BaseModel): hate_threatening: float = FieldInfo(alias="hate/threatening") """The score for the category 'hate/threatening'.""" + illicit: float + """The score for the category 'illicit'.""" + + illicit_violent: float = FieldInfo(alias="illicit/violent") + """The score for the category 'illicit/violent'.""" + self_harm: float = FieldInfo(alias="self-harm") """The score for the category 'self-harm'.""" @@ -111,6 +182,11 @@ class Moderation(BaseModel): categories: Categories """A list of the categories, and whether they are flagged or not.""" + category_applied_input_types: CategoryAppliedInputTypes + """ + A list of the categories along with the input type(s) that the score applies to. + """ + category_scores: CategoryScores """A list of the categories along with their scores as predicted by model.""" diff --git a/src/openai/types/moderation_create_params.py b/src/openai/types/moderation_create_params.py index 337682194d..65d9b7e561 100644 --- a/src/openai/types/moderation_create_params.py +++ b/src/openai/types/moderation_create_params.py @@ -2,26 +2,29 @@ from __future__ import annotations -from typing import List, Union +from typing import Union, Iterable from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr from .moderation_model import ModerationModel +from .moderation_multi_modal_input_param import ModerationMultiModalInputParam __all__ = ["ModerationCreateParams"] class ModerationCreateParams(TypedDict, total=False): - input: Required[Union[str, List[str]]] - """The input text to classify""" + input: Required[Union[str, SequenceNotStr[str], Iterable[ModerationMultiModalInputParam]]] + """Input (or inputs) to classify. - model: Union[str, ModerationModel] + Can be a single string, an array of strings, or an array of multi-modal input + objects similar to other models. """ - Two content moderations models are available: `text-moderation-stable` and - `text-moderation-latest`. - - The default is `text-moderation-latest` which will be automatically upgraded - over time. This ensures you are always using our most accurate model. If you use - `text-moderation-stable`, we will provide advanced notice before updating the - model. Accuracy of `text-moderation-stable` may be slightly lower than for - `text-moderation-latest`. + + model: Union[str, ModerationModel] + """The content moderation model you would like to use. + + Learn more in + [the moderation guide](https://platform.openai.com/docs/guides/moderation), and + learn about available models + [here](https://platform.openai.com/docs/models#moderation). """ diff --git a/src/openai/types/moderation_create_response.py b/src/openai/types/moderation_create_response.py index 79684f8a70..23c03875bf 100644 --- a/src/openai/types/moderation_create_response.py +++ b/src/openai/types/moderation_create_response.py @@ -9,6 +9,8 @@ class ModerationCreateResponse(BaseModel): + """Represents if a given text input is potentially harmful.""" + id: str """The unique identifier for the moderation request.""" diff --git a/src/openai/types/moderation_image_url_input_param.py b/src/openai/types/moderation_image_url_input_param.py new file mode 100644 index 0000000000..9c0fe25685 --- /dev/null +++ b/src/openai/types/moderation_image_url_input_param.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ModerationImageURLInputParam", "ImageURL"] + + +class ImageURL(TypedDict, total=False): + """Contains either an image URL or a data URL for a base64 encoded image.""" + + url: Required[str] + """Either a URL of the image or the base64 encoded image data.""" + + +class ModerationImageURLInputParam(TypedDict, total=False): + """An object describing an image to classify.""" + + image_url: Required[ImageURL] + """Contains either an image URL or a data URL for a base64 encoded image.""" + + type: Required[Literal["image_url"]] + """Always `image_url`.""" diff --git a/src/openai/types/moderation_model.py b/src/openai/types/moderation_model.py index f549aeeb7a..64954c4547 100644 --- a/src/openai/types/moderation_model.py +++ b/src/openai/types/moderation_model.py @@ -4,4 +4,6 @@ __all__ = ["ModerationModel"] -ModerationModel: TypeAlias = Literal["text-moderation-latest", "text-moderation-stable"] +ModerationModel: TypeAlias = Literal[ + "omni-moderation-latest", "omni-moderation-2024-09-26", "text-moderation-latest", "text-moderation-stable" +] diff --git a/src/openai/types/moderation_multi_modal_input_param.py b/src/openai/types/moderation_multi_modal_input_param.py new file mode 100644 index 0000000000..4314e7b031 --- /dev/null +++ b/src/openai/types/moderation_multi_modal_input_param.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypeAlias + +from .moderation_text_input_param import ModerationTextInputParam +from .moderation_image_url_input_param import ModerationImageURLInputParam + +__all__ = ["ModerationMultiModalInputParam"] + +ModerationMultiModalInputParam: TypeAlias = Union[ModerationImageURLInputParam, ModerationTextInputParam] diff --git a/src/openai/types/moderation_text_input_param.py b/src/openai/types/moderation_text_input_param.py new file mode 100644 index 0000000000..786ecbe625 --- /dev/null +++ b/src/openai/types/moderation_text_input_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ModerationTextInputParam"] + + +class ModerationTextInputParam(TypedDict, total=False): + """An object describing text to classify.""" + + text: Required[str] + """A string of text to classify.""" + + type: Required[Literal["text"]] + """Always `text`.""" diff --git a/src/openai/types/beta/other_file_chunking_strategy_object.py b/src/openai/types/other_file_chunking_strategy_object.py similarity index 55% rename from src/openai/types/beta/other_file_chunking_strategy_object.py rename to src/openai/types/other_file_chunking_strategy_object.py index 89da560be4..a5371425d7 100644 --- a/src/openai/types/beta/other_file_chunking_strategy_object.py +++ b/src/openai/types/other_file_chunking_strategy_object.py @@ -2,11 +2,16 @@ from typing_extensions import Literal -from ..._models import BaseModel +from .._models import BaseModel __all__ = ["OtherFileChunkingStrategyObject"] class OtherFileChunkingStrategyObject(BaseModel): + """This is returned when the chunking strategy is unknown. + + Typically, this is because the file was indexed before the `chunking_strategy` concept was introduced in the API. + """ + type: Literal["other"] """Always `other`.""" diff --git a/src/openai/types/realtime/__init__.py b/src/openai/types/realtime/__init__.py new file mode 100644 index 0000000000..d7a087ba9a --- /dev/null +++ b/src/openai/types/realtime/__init__.py @@ -0,0 +1,242 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .realtime_error import RealtimeError as RealtimeError +from .call_refer_params import CallReferParams as CallReferParams +from .conversation_item import ConversationItem as ConversationItem +from .realtime_response import RealtimeResponse as RealtimeResponse +from .call_accept_params import CallAcceptParams as CallAcceptParams +from .call_create_params import CallCreateParams as CallCreateParams +from .call_reject_params import CallRejectParams as CallRejectParams +from .realtime_reasoning import RealtimeReasoning as RealtimeReasoning +from .audio_transcription import AudioTranscription as AudioTranscription +from .log_prob_properties import LogProbProperties as LogProbProperties +from .realtime_truncation import RealtimeTruncation as RealtimeTruncation +from .response_done_event import ResponseDoneEvent as ResponseDoneEvent +from .noise_reduction_type import NoiseReductionType as NoiseReductionType +from .realtime_error_event import RealtimeErrorEvent as RealtimeErrorEvent +from .session_update_event import SessionUpdateEvent as SessionUpdateEvent +from .mcp_list_tools_failed import McpListToolsFailed as McpListToolsFailed +from .realtime_audio_config import RealtimeAudioConfig as RealtimeAudioConfig +from .realtime_client_event import RealtimeClientEvent as RealtimeClientEvent +from .realtime_server_event import RealtimeServerEvent as RealtimeServerEvent +from .realtime_tools_config import RealtimeToolsConfig as RealtimeToolsConfig +from .response_cancel_event import ResponseCancelEvent as ResponseCancelEvent +from .response_create_event import ResponseCreateEvent as ResponseCreateEvent +from .session_created_event import SessionCreatedEvent as SessionCreatedEvent +from .session_updated_event import SessionUpdatedEvent as SessionUpdatedEvent +from .conversation_item_done import ConversationItemDone as ConversationItemDone +from .realtime_audio_formats import RealtimeAudioFormats as RealtimeAudioFormats +from .realtime_function_tool import RealtimeFunctionTool as RealtimeFunctionTool +from .realtime_mcp_tool_call import RealtimeMcpToolCall as RealtimeMcpToolCall +from .realtime_mcphttp_error import RealtimeMcphttpError as RealtimeMcphttpError +from .response_created_event import ResponseCreatedEvent as ResponseCreatedEvent +from .conversation_item_added import ConversationItemAdded as ConversationItemAdded +from .conversation_item_param import ConversationItemParam as ConversationItemParam +from .realtime_connect_params import RealtimeConnectParams as RealtimeConnectParams +from .realtime_mcp_list_tools import RealtimeMcpListTools as RealtimeMcpListTools +from .realtime_response_usage import RealtimeResponseUsage as RealtimeResponseUsage +from .realtime_tracing_config import RealtimeTracingConfig as RealtimeTracingConfig +from .mcp_list_tools_completed import McpListToolsCompleted as McpListToolsCompleted +from .realtime_reasoning_param import RealtimeReasoningParam as RealtimeReasoningParam +from .realtime_response_status import RealtimeResponseStatus as RealtimeResponseStatus +from .response_mcp_call_failed import ResponseMcpCallFailed as ResponseMcpCallFailed +from .response_text_done_event import ResponseTextDoneEvent as ResponseTextDoneEvent +from .audio_transcription_param import AudioTranscriptionParam as AudioTranscriptionParam +from .rate_limits_updated_event import RateLimitsUpdatedEvent as RateLimitsUpdatedEvent +from .realtime_reasoning_effort import RealtimeReasoningEffort as RealtimeReasoningEffort +from .realtime_truncation_param import RealtimeTruncationParam as RealtimeTruncationParam +from .response_audio_done_event import ResponseAudioDoneEvent as ResponseAudioDoneEvent +from .response_text_delta_event import ResponseTextDeltaEvent as ResponseTextDeltaEvent +from .conversation_created_event import ConversationCreatedEvent as ConversationCreatedEvent +from .mcp_list_tools_in_progress import McpListToolsInProgress as McpListToolsInProgress +from .response_audio_delta_event import ResponseAudioDeltaEvent as ResponseAudioDeltaEvent +from .session_update_event_param import SessionUpdateEventParam as SessionUpdateEventParam +from .client_secret_create_params import ClientSecretCreateParams as ClientSecretCreateParams +from .realtime_audio_config_input import RealtimeAudioConfigInput as RealtimeAudioConfigInput +from .realtime_audio_config_param import RealtimeAudioConfigParam as RealtimeAudioConfigParam +from .realtime_client_event_param import RealtimeClientEventParam as RealtimeClientEventParam +from .realtime_mcp_protocol_error import RealtimeMcpProtocolError as RealtimeMcpProtocolError +from .realtime_tool_choice_config import RealtimeToolChoiceConfig as RealtimeToolChoiceConfig +from .realtime_tools_config_param import RealtimeToolsConfigParam as RealtimeToolsConfigParam +from .realtime_tools_config_union import RealtimeToolsConfigUnion as RealtimeToolsConfigUnion +from .response_cancel_event_param import ResponseCancelEventParam as ResponseCancelEventParam +from .response_create_event_param import ResponseCreateEventParam as ResponseCreateEventParam +from .response_mcp_call_completed import ResponseMcpCallCompleted as ResponseMcpCallCompleted +from .realtime_audio_config_output import RealtimeAudioConfigOutput as RealtimeAudioConfigOutput +from .realtime_audio_formats_param import RealtimeAudioFormatsParam as RealtimeAudioFormatsParam +from .realtime_function_tool_param import RealtimeFunctionToolParam as RealtimeFunctionToolParam +from .realtime_mcp_tool_call_param import RealtimeMcpToolCallParam as RealtimeMcpToolCallParam +from .realtime_mcphttp_error_param import RealtimeMcphttpErrorParam as RealtimeMcphttpErrorParam +from .client_secret_create_response import ClientSecretCreateResponse as ClientSecretCreateResponse +from .realtime_mcp_approval_request import RealtimeMcpApprovalRequest as RealtimeMcpApprovalRequest +from .realtime_mcp_list_tools_param import RealtimeMcpListToolsParam as RealtimeMcpListToolsParam +from .realtime_tracing_config_param import RealtimeTracingConfigParam as RealtimeTracingConfigParam +from .response_mcp_call_in_progress import ResponseMcpCallInProgress as ResponseMcpCallInProgress +from .conversation_item_create_event import ConversationItemCreateEvent as ConversationItemCreateEvent +from .conversation_item_delete_event import ConversationItemDeleteEvent as ConversationItemDeleteEvent +from .input_audio_buffer_clear_event import InputAudioBufferClearEvent as InputAudioBufferClearEvent +from .realtime_mcp_approval_response import RealtimeMcpApprovalResponse as RealtimeMcpApprovalResponse +from .conversation_item_created_event import ConversationItemCreatedEvent as ConversationItemCreatedEvent +from .conversation_item_deleted_event import ConversationItemDeletedEvent as ConversationItemDeletedEvent +from .input_audio_buffer_append_event import InputAudioBufferAppendEvent as InputAudioBufferAppendEvent +from .input_audio_buffer_commit_event import InputAudioBufferCommitEvent as InputAudioBufferCommitEvent +from .output_audio_buffer_clear_event import OutputAudioBufferClearEvent as OutputAudioBufferClearEvent +from .realtime_response_create_params import RealtimeResponseCreateParams as RealtimeResponseCreateParams +from .realtime_session_create_request import RealtimeSessionCreateRequest as RealtimeSessionCreateRequest +from .response_output_item_done_event import ResponseOutputItemDoneEvent as ResponseOutputItemDoneEvent +from .conversation_item_retrieve_event import ConversationItemRetrieveEvent as ConversationItemRetrieveEvent +from .conversation_item_truncate_event import ConversationItemTruncateEvent as ConversationItemTruncateEvent +from .input_audio_buffer_cleared_event import InputAudioBufferClearedEvent as InputAudioBufferClearedEvent +from .realtime_session_create_response import RealtimeSessionCreateResponse as RealtimeSessionCreateResponse +from .response_content_part_done_event import ResponseContentPartDoneEvent as ResponseContentPartDoneEvent +from .response_mcp_call_arguments_done import ResponseMcpCallArgumentsDone as ResponseMcpCallArgumentsDone +from .response_output_item_added_event import ResponseOutputItemAddedEvent as ResponseOutputItemAddedEvent +from .conversation_item_truncated_event import ConversationItemTruncatedEvent as ConversationItemTruncatedEvent +from .realtime_audio_config_input_param import RealtimeAudioConfigInputParam as RealtimeAudioConfigInputParam +from .realtime_mcp_protocol_error_param import RealtimeMcpProtocolErrorParam as RealtimeMcpProtocolErrorParam +from .realtime_mcp_tool_execution_error import RealtimeMcpToolExecutionError as RealtimeMcpToolExecutionError +from .realtime_response_create_mcp_tool import RealtimeResponseCreateMcpTool as RealtimeResponseCreateMcpTool +from .realtime_tool_choice_config_param import RealtimeToolChoiceConfigParam as RealtimeToolChoiceConfigParam +from .realtime_tools_config_union_param import RealtimeToolsConfigUnionParam as RealtimeToolsConfigUnionParam +from .response_content_part_added_event import ResponseContentPartAddedEvent as ResponseContentPartAddedEvent +from .response_mcp_call_arguments_delta import ResponseMcpCallArgumentsDelta as ResponseMcpCallArgumentsDelta +from .input_audio_buffer_committed_event import InputAudioBufferCommittedEvent as InputAudioBufferCommittedEvent +from .realtime_audio_config_output_param import RealtimeAudioConfigOutputParam as RealtimeAudioConfigOutputParam +from .realtime_audio_input_turn_detection import RealtimeAudioInputTurnDetection as RealtimeAudioInputTurnDetection +from .realtime_mcp_approval_request_param import RealtimeMcpApprovalRequestParam as RealtimeMcpApprovalRequestParam +from .realtime_truncation_retention_ratio import RealtimeTruncationRetentionRatio as RealtimeTruncationRetentionRatio +from .conversation_item_create_event_param import ConversationItemCreateEventParam as ConversationItemCreateEventParam +from .conversation_item_delete_event_param import ConversationItemDeleteEventParam as ConversationItemDeleteEventParam +from .input_audio_buffer_clear_event_param import InputAudioBufferClearEventParam as InputAudioBufferClearEventParam +from .input_audio_buffer_timeout_triggered import InputAudioBufferTimeoutTriggered as InputAudioBufferTimeoutTriggered +from .realtime_mcp_approval_response_param import RealtimeMcpApprovalResponseParam as RealtimeMcpApprovalResponseParam +from .realtime_transcription_session_audio import RealtimeTranscriptionSessionAudio as RealtimeTranscriptionSessionAudio +from .response_audio_transcript_done_event import ResponseAudioTranscriptDoneEvent as ResponseAudioTranscriptDoneEvent +from .input_audio_buffer_append_event_param import InputAudioBufferAppendEventParam as InputAudioBufferAppendEventParam +from .input_audio_buffer_commit_event_param import InputAudioBufferCommitEventParam as InputAudioBufferCommitEventParam +from .output_audio_buffer_clear_event_param import OutputAudioBufferClearEventParam as OutputAudioBufferClearEventParam +from .realtime_response_create_audio_output import ( + RealtimeResponseCreateAudioOutput as RealtimeResponseCreateAudioOutput, +) +from .realtime_response_create_params_param import ( + RealtimeResponseCreateParamsParam as RealtimeResponseCreateParamsParam, +) +from .realtime_session_create_request_param import ( + RealtimeSessionCreateRequestParam as RealtimeSessionCreateRequestParam, +) +from .response_audio_transcript_delta_event import ( + ResponseAudioTranscriptDeltaEvent as ResponseAudioTranscriptDeltaEvent, +) +from .conversation_item_retrieve_event_param import ( + ConversationItemRetrieveEventParam as ConversationItemRetrieveEventParam, +) +from .conversation_item_truncate_event_param import ( + ConversationItemTruncateEventParam as ConversationItemTruncateEventParam, +) +from .input_audio_buffer_speech_started_event import ( + InputAudioBufferSpeechStartedEvent as InputAudioBufferSpeechStartedEvent, +) +from .input_audio_buffer_speech_stopped_event import ( + InputAudioBufferSpeechStoppedEvent as InputAudioBufferSpeechStoppedEvent, +) +from .realtime_conversation_item_user_message import ( + RealtimeConversationItemUserMessage as RealtimeConversationItemUserMessage, +) +from .realtime_mcp_tool_execution_error_param import ( + RealtimeMcpToolExecutionErrorParam as RealtimeMcpToolExecutionErrorParam, +) +from .realtime_response_create_mcp_tool_param import ( + RealtimeResponseCreateMcpToolParam as RealtimeResponseCreateMcpToolParam, +) +from .realtime_conversation_item_function_call import ( + RealtimeConversationItemFunctionCall as RealtimeConversationItemFunctionCall, +) +from .realtime_audio_input_turn_detection_param import ( + RealtimeAudioInputTurnDetectionParam as RealtimeAudioInputTurnDetectionParam, +) +from .realtime_conversation_item_system_message import ( + RealtimeConversationItemSystemMessage as RealtimeConversationItemSystemMessage, +) +from .realtime_truncation_retention_ratio_param import ( + RealtimeTruncationRetentionRatioParam as RealtimeTruncationRetentionRatioParam, +) +from .realtime_transcription_session_audio_input import ( + RealtimeTranscriptionSessionAudioInput as RealtimeTranscriptionSessionAudioInput, +) +from .realtime_transcription_session_audio_param import ( + RealtimeTranscriptionSessionAudioParam as RealtimeTranscriptionSessionAudioParam, +) +from .realtime_response_create_audio_output_param import ( + RealtimeResponseCreateAudioOutputParam as RealtimeResponseCreateAudioOutputParam, +) +from .realtime_response_usage_input_token_details import ( + RealtimeResponseUsageInputTokenDetails as RealtimeResponseUsageInputTokenDetails, +) +from .response_function_call_arguments_done_event import ( + ResponseFunctionCallArgumentsDoneEvent as ResponseFunctionCallArgumentsDoneEvent, +) +from .input_audio_buffer_dtmf_event_received_event import ( + InputAudioBufferDtmfEventReceivedEvent as InputAudioBufferDtmfEventReceivedEvent, +) +from .realtime_conversation_item_assistant_message import ( + RealtimeConversationItemAssistantMessage as RealtimeConversationItemAssistantMessage, +) +from .realtime_response_usage_output_token_details import ( + RealtimeResponseUsageOutputTokenDetails as RealtimeResponseUsageOutputTokenDetails, +) +from .response_function_call_arguments_delta_event import ( + ResponseFunctionCallArgumentsDeltaEvent as ResponseFunctionCallArgumentsDeltaEvent, +) +from .realtime_conversation_item_user_message_param import ( + RealtimeConversationItemUserMessageParam as RealtimeConversationItemUserMessageParam, +) +from .realtime_transcription_session_create_request import ( + RealtimeTranscriptionSessionCreateRequest as RealtimeTranscriptionSessionCreateRequest, +) +from .realtime_transcription_session_turn_detection import ( + RealtimeTranscriptionSessionTurnDetection as RealtimeTranscriptionSessionTurnDetection, +) +from .realtime_conversation_item_function_call_param import ( + RealtimeConversationItemFunctionCallParam as RealtimeConversationItemFunctionCallParam, +) +from .realtime_transcription_session_create_response import ( + RealtimeTranscriptionSessionCreateResponse as RealtimeTranscriptionSessionCreateResponse, +) +from .realtime_conversation_item_function_call_output import ( + RealtimeConversationItemFunctionCallOutput as RealtimeConversationItemFunctionCallOutput, +) +from .realtime_conversation_item_system_message_param import ( + RealtimeConversationItemSystemMessageParam as RealtimeConversationItemSystemMessageParam, +) +from .realtime_transcription_session_audio_input_param import ( + RealtimeTranscriptionSessionAudioInputParam as RealtimeTranscriptionSessionAudioInputParam, +) +from .realtime_conversation_item_assistant_message_param import ( + RealtimeConversationItemAssistantMessageParam as RealtimeConversationItemAssistantMessageParam, +) +from .conversation_item_input_audio_transcription_segment import ( + ConversationItemInputAudioTranscriptionSegment as ConversationItemInputAudioTranscriptionSegment, +) +from .realtime_transcription_session_create_request_param import ( + RealtimeTranscriptionSessionCreateRequestParam as RealtimeTranscriptionSessionCreateRequestParam, +) +from .realtime_conversation_item_function_call_output_param import ( + RealtimeConversationItemFunctionCallOutputParam as RealtimeConversationItemFunctionCallOutputParam, +) +from .conversation_item_input_audio_transcription_delta_event import ( + ConversationItemInputAudioTranscriptionDeltaEvent as ConversationItemInputAudioTranscriptionDeltaEvent, +) +from .conversation_item_input_audio_transcription_failed_event import ( + ConversationItemInputAudioTranscriptionFailedEvent as ConversationItemInputAudioTranscriptionFailedEvent, +) +from .realtime_transcription_session_audio_input_turn_detection import ( + RealtimeTranscriptionSessionAudioInputTurnDetection as RealtimeTranscriptionSessionAudioInputTurnDetection, +) +from .conversation_item_input_audio_transcription_completed_event import ( + ConversationItemInputAudioTranscriptionCompletedEvent as ConversationItemInputAudioTranscriptionCompletedEvent, +) +from .realtime_transcription_session_audio_input_turn_detection_param import ( + RealtimeTranscriptionSessionAudioInputTurnDetectionParam as RealtimeTranscriptionSessionAudioInputTurnDetectionParam, +) diff --git a/src/openai/types/realtime/audio_transcription.py b/src/openai/types/realtime/audio_transcription.py new file mode 100644 index 0000000000..45e2e388ca --- /dev/null +++ b/src/openai/types/realtime/audio_transcription.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["AudioTranscription"] + + +class AudioTranscription(BaseModel): + delay: Optional[Literal["minimal", "low", "medium", "high", "xhigh"]] = None + """ + Controls how long the model waits before emitting transcription text. Higher + values can improve transcription accuracy at the cost of latency. Only supported + with `gpt-realtime-whisper` in GA Realtime sessions. + """ + + language: Optional[str] = None + """The language of the input audio. + + Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + """ + + model: Union[ + str, + Literal[ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper", + ], + None, + ] = None + """The model to use for transcription. + + Current options are `whisper-1`, `gpt-4o-mini-transcribe`, + `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, + `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`. Use + `gpt-4o-transcribe-diarize` when you need diarization with speaker labels. + """ + + prompt: Optional[str] = None + """ + An optional text to guide the model's style or continue a previous audio + segment. For `whisper-1`, the + [prompt is a list of keywords](https://platform.openai.com/docs/guides/speech-to-text#prompting). + For `gpt-4o-transcribe` models (excluding `gpt-4o-transcribe-diarize`), the + prompt is a free text string, for example "expect words related to technology". + Prompt is not supported with `gpt-realtime-whisper` in GA Realtime sessions. + """ diff --git a/src/openai/types/realtime/audio_transcription_param.py b/src/openai/types/realtime/audio_transcription_param.py new file mode 100644 index 0000000000..9206921542 --- /dev/null +++ b/src/openai/types/realtime/audio_transcription_param.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypedDict + +__all__ = ["AudioTranscriptionParam"] + + +class AudioTranscriptionParam(TypedDict, total=False): + delay: Literal["minimal", "low", "medium", "high", "xhigh"] + """ + Controls how long the model waits before emitting transcription text. Higher + values can improve transcription accuracy at the cost of latency. Only supported + with `gpt-realtime-whisper` in GA Realtime sessions. + """ + + language: str + """The language of the input audio. + + Supplying the input language in + [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) + format will improve accuracy and latency. + """ + + model: Union[ + str, + Literal[ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper", + ], + ] + """The model to use for transcription. + + Current options are `whisper-1`, `gpt-4o-mini-transcribe`, + `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, + `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`. Use + `gpt-4o-transcribe-diarize` when you need diarization with speaker labels. + """ + + prompt: str + """ + An optional text to guide the model's style or continue a previous audio + segment. For `whisper-1`, the + [prompt is a list of keywords](https://platform.openai.com/docs/guides/speech-to-text#prompting). + For `gpt-4o-transcribe` models (excluding `gpt-4o-transcribe-diarize`), the + prompt is a free text string, for example "expect words related to technology". + Prompt is not supported with `gpt-realtime-whisper` in GA Realtime sessions. + """ diff --git a/src/openai/types/realtime/call_accept_params.py b/src/openai/types/realtime/call_accept_params.py new file mode 100644 index 0000000000..b4a48fc8b5 --- /dev/null +++ b/src/openai/types/realtime/call_accept_params.py @@ -0,0 +1,143 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from typing_extensions import Literal, Required, TypedDict + +from .realtime_reasoning_param import RealtimeReasoningParam +from .realtime_truncation_param import RealtimeTruncationParam +from .realtime_audio_config_param import RealtimeAudioConfigParam +from .realtime_tools_config_param import RealtimeToolsConfigParam +from .realtime_tracing_config_param import RealtimeTracingConfigParam +from ..responses.response_prompt_param import ResponsePromptParam +from .realtime_tool_choice_config_param import RealtimeToolChoiceConfigParam + +__all__ = ["CallAcceptParams"] + + +class CallAcceptParams(TypedDict, total=False): + type: Required[Literal["realtime"]] + """The type of session to create. Always `realtime` for the Realtime API.""" + + audio: RealtimeAudioConfigParam + """Configuration for input and output audio.""" + + include: List[Literal["item.input_audio_transcription.logprobs"]] + """Additional fields to include in server outputs. + + `item.input_audio_transcription.logprobs`: Include logprobs for input audio + transcription. + """ + + instructions: str + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_output_tokens: Union[int, Literal["inf"]] + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + model: Union[ + str, + Literal[ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15", + ], + ] + """The Realtime model used for this session.""" + + output_modalities: List[Literal["text", "audio"]] + """The set of modalities the model can respond with. + + It defaults to `["audio"]`, indicating that the model will respond with audio + plus a transcript. `["text"]` can be used to make the model respond with text + only. It is not possible to request both `text` and `audio` at the same time. + """ + + parallel_tool_calls: bool + """Whether the model may call multiple tools in parallel. + + Only supported by reasoning Realtime models such as `gpt-realtime-2`. + """ + + prompt: Optional[ResponsePromptParam] + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + reasoning: RealtimeReasoningParam + """Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`.""" + + tool_choice: RealtimeToolChoiceConfigParam + """How the model chooses tools. + + Provide one of the string modes or force a specific function/MCP tool. + """ + + tools: RealtimeToolsConfigParam + """Tools available to the model.""" + + tracing: Optional[RealtimeTracingConfigParam] + """ + Realtime API can write session traces to the + [Traces Dashboard](https://platform.openai.com/logs?api=traces). Set to null to + disable tracing. Once tracing is enabled for a session, the configuration cannot + be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + """ + + truncation: RealtimeTruncationParam + """ + When the number of tokens in a conversation exceeds the model's input token + limit, the conversation be truncated, meaning messages (starting from the + oldest) will not be included in the model's context. A 32k context model with + 4,096 max output tokens can only include 28,224 tokens in the context before + truncation occurs. + + Clients can configure truncation behavior to truncate with a lower max token + limit, which is an effective way to control token usage and cost. + + Truncation will reduce the number of cached tokens on the next turn (busting the + cache), since messages are dropped from the beginning of the context. However, + clients can also configure truncation to retain messages up to a fraction of the + maximum context size, which will reduce the need for future truncations and thus + improve the cache rate. + + Truncation can be disabled entirely, which means the server will never truncate + but would instead return an error if the conversation exceeds the model's input + token limit. + """ diff --git a/src/openai/types/realtime/call_create_params.py b/src/openai/types/realtime/call_create_params.py new file mode 100644 index 0000000000..a378092a66 --- /dev/null +++ b/src/openai/types/realtime/call_create_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from .realtime_session_create_request_param import RealtimeSessionCreateRequestParam + +__all__ = ["CallCreateParams"] + + +class CallCreateParams(TypedDict, total=False): + sdp: Required[str] + """WebRTC Session Description Protocol (SDP) offer generated by the caller.""" + + session: RealtimeSessionCreateRequestParam + """Realtime session object configuration.""" diff --git a/src/openai/types/realtime/call_refer_params.py b/src/openai/types/realtime/call_refer_params.py new file mode 100644 index 0000000000..3d8623855b --- /dev/null +++ b/src/openai/types/realtime/call_refer_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["CallReferParams"] + + +class CallReferParams(TypedDict, total=False): + target_uri: Required[str] + """URI that should appear in the SIP Refer-To header. + + Supports values like `tel:+14155550123` or `sip:agent@example.com`. + """ diff --git a/src/openai/types/realtime/call_reject_params.py b/src/openai/types/realtime/call_reject_params.py new file mode 100644 index 0000000000..f12222cded --- /dev/null +++ b/src/openai/types/realtime/call_reject_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["CallRejectParams"] + + +class CallRejectParams(TypedDict, total=False): + status_code: int + """SIP response code to send back to the caller. + + Defaults to `603` (Decline) when omitted. + """ diff --git a/src/openai/types/realtime/client_secret_create_params.py b/src/openai/types/realtime/client_secret_create_params.py new file mode 100644 index 0000000000..2297f3f6d2 --- /dev/null +++ b/src/openai/types/realtime/client_secret_create_params.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypeAlias, TypedDict + +from .realtime_session_create_request_param import RealtimeSessionCreateRequestParam +from .realtime_transcription_session_create_request_param import RealtimeTranscriptionSessionCreateRequestParam + +__all__ = ["ClientSecretCreateParams", "ExpiresAfter", "Session"] + + +class ClientSecretCreateParams(TypedDict, total=False): + expires_after: ExpiresAfter + """Configuration for the client secret expiration. + + Expiration refers to the time after which a client secret will no longer be + valid for creating sessions. The session itself may continue after that time + once started. A secret can be used to create multiple sessions until it expires. + """ + + session: Session + """Session configuration to use for the client secret. + + Choose either a realtime session or a transcription session. + """ + + +class ExpiresAfter(TypedDict, total=False): + """Configuration for the client secret expiration. + + Expiration refers to the time after which + a client secret will no longer be valid for creating sessions. The session itself may + continue after that time once started. A secret can be used to create multiple sessions + until it expires. + """ + + anchor: Literal["created_at"] + """ + The anchor point for the client secret expiration, meaning that `seconds` will + be added to the `created_at` time of the client secret to produce an expiration + timestamp. Only `created_at` is currently supported. + """ + + seconds: int + """The number of seconds from the anchor point to the expiration. + + Select a value between `10` and `7200` (2 hours). This default to 600 seconds + (10 minutes) if not specified. + """ + + +Session: TypeAlias = Union[RealtimeSessionCreateRequestParam, RealtimeTranscriptionSessionCreateRequestParam] diff --git a/src/openai/types/realtime/client_secret_create_response.py b/src/openai/types/realtime/client_secret_create_response.py new file mode 100644 index 0000000000..3a30b10544 --- /dev/null +++ b/src/openai/types/realtime/client_secret_create_response.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .realtime_session_create_response import RealtimeSessionCreateResponse +from .realtime_transcription_session_create_response import RealtimeTranscriptionSessionCreateResponse + +__all__ = ["ClientSecretCreateResponse", "Session"] + +Session: TypeAlias = Annotated[ + Union[RealtimeSessionCreateResponse, RealtimeTranscriptionSessionCreateResponse], PropertyInfo(discriminator="type") +] + + +class ClientSecretCreateResponse(BaseModel): + """Response from creating a session and client secret for the Realtime API.""" + + expires_at: int + """Expiration timestamp for the client secret, in seconds since epoch.""" + + session: Session + """The session configuration for either a realtime or transcription session.""" + + value: str + """The generated client secret value.""" diff --git a/src/openai/types/realtime/conversation_created_event.py b/src/openai/types/realtime/conversation_created_event.py new file mode 100644 index 0000000000..3026322e86 --- /dev/null +++ b/src/openai/types/realtime/conversation_created_event.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ConversationCreatedEvent", "Conversation"] + + +class Conversation(BaseModel): + """The conversation resource.""" + + id: Optional[str] = None + """The unique ID of the conversation.""" + + object: Optional[Literal["realtime.conversation"]] = None + """The object type, must be `realtime.conversation`.""" + + +class ConversationCreatedEvent(BaseModel): + """Returned when a conversation is created. Emitted right after session creation.""" + + conversation: Conversation + """The conversation resource.""" + + event_id: str + """The unique ID of the server event.""" + + type: Literal["conversation.created"] + """The event type, must be `conversation.created`.""" diff --git a/src/openai/types/realtime/conversation_item.py b/src/openai/types/realtime/conversation_item.py new file mode 100644 index 0000000000..be021520a2 --- /dev/null +++ b/src/openai/types/realtime/conversation_item.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from .realtime_mcp_tool_call import RealtimeMcpToolCall +from .realtime_mcp_list_tools import RealtimeMcpListTools +from .realtime_mcp_approval_request import RealtimeMcpApprovalRequest +from .realtime_mcp_approval_response import RealtimeMcpApprovalResponse +from .realtime_conversation_item_user_message import RealtimeConversationItemUserMessage +from .realtime_conversation_item_function_call import RealtimeConversationItemFunctionCall +from .realtime_conversation_item_system_message import RealtimeConversationItemSystemMessage +from .realtime_conversation_item_assistant_message import RealtimeConversationItemAssistantMessage +from .realtime_conversation_item_function_call_output import RealtimeConversationItemFunctionCallOutput + +__all__ = ["ConversationItem"] + +ConversationItem: TypeAlias = Annotated[ + Union[ + RealtimeConversationItemSystemMessage, + RealtimeConversationItemUserMessage, + RealtimeConversationItemAssistantMessage, + RealtimeConversationItemFunctionCall, + RealtimeConversationItemFunctionCallOutput, + RealtimeMcpApprovalResponse, + RealtimeMcpListTools, + RealtimeMcpToolCall, + RealtimeMcpApprovalRequest, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/realtime/conversation_item_added.py b/src/openai/types/realtime/conversation_item_added.py new file mode 100644 index 0000000000..0e336a9261 --- /dev/null +++ b/src/openai/types/realtime/conversation_item_added.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ConversationItemAdded"] + + +class ConversationItemAdded(BaseModel): + """Sent by the server when an Item is added to the default Conversation. + + This can happen in several cases: + - When the client sends a `conversation.item.create` event. + - When the input audio buffer is committed. In this case the item will be a user message containing the audio from the buffer. + - When the model is generating a Response. In this case the `conversation.item.added` event will be sent when the model starts generating a specific Item, and thus it will not yet have any content (and `status` will be `in_progress`). + + The event will include the full content of the Item (except when model is generating a Response) except for audio data, which can be retrieved separately with a `conversation.item.retrieve` event if necessary. + """ + + event_id: str + """The unique ID of the server event.""" + + item: ConversationItem + """A single item within a Realtime conversation.""" + + type: Literal["conversation.item.added"] + """The event type, must be `conversation.item.added`.""" + + previous_item_id: Optional[str] = None + """The ID of the item that precedes this one, if any. + + This is used to maintain ordering when items are inserted. + """ diff --git a/src/openai/types/realtime/conversation_item_create_event.py b/src/openai/types/realtime/conversation_item_create_event.py new file mode 100644 index 0000000000..fd0fc00fa2 --- /dev/null +++ b/src/openai/types/realtime/conversation_item_create_event.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ConversationItemCreateEvent"] + + +class ConversationItemCreateEvent(BaseModel): + """ + Add a new Item to the Conversation's context, including messages, function + calls, and function call responses. This event can be used both to populate a + "history" of the conversation and to add new items mid-stream, but has the + current limitation that it cannot populate assistant audio messages. + + If successful, the server will respond with a `conversation.item.created` + event, otherwise an `error` event will be sent. + """ + + item: ConversationItem + """A single item within a Realtime conversation.""" + + type: Literal["conversation.item.create"] + """The event type, must be `conversation.item.create`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" + + previous_item_id: Optional[str] = None + """The ID of the preceding item after which the new item will be inserted. + + If not set, the new item will be appended to the end of the conversation. + + If set to `root`, the new item will be added to the beginning of the + conversation. + + If set to an existing ID, it allows an item to be inserted mid-conversation. If + the ID cannot be found, an error will be returned and the item will not be + added. + """ diff --git a/src/openai/types/realtime/conversation_item_create_event_param.py b/src/openai/types/realtime/conversation_item_create_event_param.py new file mode 100644 index 0000000000..e991e37c3b --- /dev/null +++ b/src/openai/types/realtime/conversation_item_create_event_param.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .conversation_item_param import ConversationItemParam + +__all__ = ["ConversationItemCreateEventParam"] + + +class ConversationItemCreateEventParam(TypedDict, total=False): + """ + Add a new Item to the Conversation's context, including messages, function + calls, and function call responses. This event can be used both to populate a + "history" of the conversation and to add new items mid-stream, but has the + current limitation that it cannot populate assistant audio messages. + + If successful, the server will respond with a `conversation.item.created` + event, otherwise an `error` event will be sent. + """ + + item: Required[ConversationItemParam] + """A single item within a Realtime conversation.""" + + type: Required[Literal["conversation.item.create"]] + """The event type, must be `conversation.item.create`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" + + previous_item_id: str + """The ID of the preceding item after which the new item will be inserted. + + If not set, the new item will be appended to the end of the conversation. + + If set to `root`, the new item will be added to the beginning of the + conversation. + + If set to an existing ID, it allows an item to be inserted mid-conversation. If + the ID cannot be found, an error will be returned and the item will not be + added. + """ diff --git a/src/openai/types/realtime/conversation_item_created_event.py b/src/openai/types/realtime/conversation_item_created_event.py new file mode 100644 index 0000000000..6ae6f05ffe --- /dev/null +++ b/src/openai/types/realtime/conversation_item_created_event.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ConversationItemCreatedEvent"] + + +class ConversationItemCreatedEvent(BaseModel): + """Returned when a conversation item is created. + + There are several scenarios that produce this event: + - The server is generating a Response, which if successful will produce + either one or two Items, which will be of type `message` + (role `assistant`) or type `function_call`. + - The input audio buffer has been committed, either by the client or the + server (in `server_vad` mode). The server will take the content of the + input audio buffer and add it to a new user message Item. + - The client has sent a `conversation.item.create` event to add a new Item + to the Conversation. + """ + + event_id: str + """The unique ID of the server event.""" + + item: ConversationItem + """A single item within a Realtime conversation.""" + + type: Literal["conversation.item.created"] + """The event type, must be `conversation.item.created`.""" + + previous_item_id: Optional[str] = None + """ + The ID of the preceding item in the Conversation context, allows the client to + understand the order of the conversation. Can be `null` if the item has no + predecessor. + """ diff --git a/src/openai/types/realtime/conversation_item_delete_event.py b/src/openai/types/realtime/conversation_item_delete_event.py new file mode 100644 index 0000000000..c662f386e3 --- /dev/null +++ b/src/openai/types/realtime/conversation_item_delete_event.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ConversationItemDeleteEvent"] + + +class ConversationItemDeleteEvent(BaseModel): + """Send this event when you want to remove any item from the conversation + history. + + The server will respond with a `conversation.item.deleted` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + + item_id: str + """The ID of the item to delete.""" + + type: Literal["conversation.item.delete"] + """The event type, must be `conversation.item.delete`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/conversation_item_delete_event_param.py b/src/openai/types/realtime/conversation_item_delete_event_param.py new file mode 100644 index 0000000000..e79bb68c9a --- /dev/null +++ b/src/openai/types/realtime/conversation_item_delete_event_param.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ConversationItemDeleteEventParam"] + + +class ConversationItemDeleteEventParam(TypedDict, total=False): + """Send this event when you want to remove any item from the conversation + history. + + The server will respond with a `conversation.item.deleted` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + + item_id: Required[str] + """The ID of the item to delete.""" + + type: Required[Literal["conversation.item.delete"]] + """The event type, must be `conversation.item.delete`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/conversation_item_deleted_event.py b/src/openai/types/realtime/conversation_item_deleted_event.py new file mode 100644 index 0000000000..9826289ebf --- /dev/null +++ b/src/openai/types/realtime/conversation_item_deleted_event.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ConversationItemDeletedEvent"] + + +class ConversationItemDeletedEvent(BaseModel): + """ + Returned when an item in the conversation is deleted by the client with a + `conversation.item.delete` event. This event is used to synchronize the + server's understanding of the conversation history with the client's view. + """ + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item that was deleted.""" + + type: Literal["conversation.item.deleted"] + """The event type, must be `conversation.item.deleted`.""" diff --git a/src/openai/types/realtime/conversation_item_done.py b/src/openai/types/realtime/conversation_item_done.py new file mode 100644 index 0000000000..6a823c65a8 --- /dev/null +++ b/src/openai/types/realtime/conversation_item_done.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ConversationItemDone"] + + +class ConversationItemDone(BaseModel): + """Returned when a conversation item is finalized. + + The event will include the full content of the Item except for audio data, which can be retrieved separately with a `conversation.item.retrieve` event if needed. + """ + + event_id: str + """The unique ID of the server event.""" + + item: ConversationItem + """A single item within a Realtime conversation.""" + + type: Literal["conversation.item.done"] + """The event type, must be `conversation.item.done`.""" + + previous_item_id: Optional[str] = None + """The ID of the item that precedes this one, if any. + + This is used to maintain ordering when items are inserted. + """ diff --git a/src/openai/types/realtime/conversation_item_input_audio_transcription_completed_event.py b/src/openai/types/realtime/conversation_item_input_audio_transcription_completed_event.py new file mode 100644 index 0000000000..3304233f8f --- /dev/null +++ b/src/openai/types/realtime/conversation_item_input_audio_transcription_completed_event.py @@ -0,0 +1,98 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .log_prob_properties import LogProbProperties + +__all__ = [ + "ConversationItemInputAudioTranscriptionCompletedEvent", + "Usage", + "UsageTranscriptTextUsageTokens", + "UsageTranscriptTextUsageTokensInputTokenDetails", + "UsageTranscriptTextUsageDuration", +] + + +class UsageTranscriptTextUsageTokensInputTokenDetails(BaseModel): + """Details about the input tokens billed for this request.""" + + audio_tokens: Optional[int] = None + """Number of audio tokens billed for this request.""" + + text_tokens: Optional[int] = None + """Number of text tokens billed for this request.""" + + +class UsageTranscriptTextUsageTokens(BaseModel): + """Usage statistics for models billed by token usage.""" + + input_tokens: int + """Number of input tokens billed for this request.""" + + output_tokens: int + """Number of output tokens generated.""" + + total_tokens: int + """Total number of tokens used (input + output).""" + + type: Literal["tokens"] + """The type of the usage object. Always `tokens` for this variant.""" + + input_token_details: Optional[UsageTranscriptTextUsageTokensInputTokenDetails] = None + """Details about the input tokens billed for this request.""" + + +class UsageTranscriptTextUsageDuration(BaseModel): + """Usage statistics for models billed by audio input duration.""" + + seconds: float + """Duration of the input audio in seconds.""" + + type: Literal["duration"] + """The type of the usage object. Always `duration` for this variant.""" + + +Usage: TypeAlias = Union[UsageTranscriptTextUsageTokens, UsageTranscriptTextUsageDuration] + + +class ConversationItemInputAudioTranscriptionCompletedEvent(BaseModel): + """ + This event is the output of audio transcription for user audio written to the + user audio buffer. Transcription begins when the input audio buffer is + committed by the client or server (when VAD is enabled). Transcription runs + asynchronously with Response creation, so this event may come before or after + the Response events. + + Realtime API models accept audio natively, and thus input transcription is a + separate process run on a separate ASR (Automatic Speech Recognition) model. + The transcript may diverge somewhat from the model's interpretation, and + should be treated as a rough guide. + """ + + content_index: int + """The index of the content part containing the audio.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item containing the audio that is being transcribed.""" + + transcript: str + """The transcribed text.""" + + type: Literal["conversation.item.input_audio_transcription.completed"] + """ + The event type, must be `conversation.item.input_audio_transcription.completed`. + """ + + usage: Usage + """ + Usage statistics for the transcription, this is billed according to the ASR + model's pricing rather than the realtime model's pricing. + """ + + logprobs: Optional[List[LogProbProperties]] = None + """The log probabilities of the transcription.""" diff --git a/src/openai/types/realtime/conversation_item_input_audio_transcription_delta_event.py b/src/openai/types/realtime/conversation_item_input_audio_transcription_delta_event.py new file mode 100644 index 0000000000..5f3f54810f --- /dev/null +++ b/src/openai/types/realtime/conversation_item_input_audio_transcription_delta_event.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .log_prob_properties import LogProbProperties + +__all__ = ["ConversationItemInputAudioTranscriptionDeltaEvent"] + + +class ConversationItemInputAudioTranscriptionDeltaEvent(BaseModel): + """ + Returned when the text value of an input audio transcription content part is updated with incremental transcription results. + """ + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item containing the audio that is being transcribed.""" + + type: Literal["conversation.item.input_audio_transcription.delta"] + """The event type, must be `conversation.item.input_audio_transcription.delta`.""" + + content_index: Optional[int] = None + """The index of the content part in the item's content array.""" + + delta: Optional[str] = None + """The text delta.""" + + logprobs: Optional[List[LogProbProperties]] = None + """The log probabilities of the transcription. + + These can be enabled by configurating the session with + `"include": ["item.input_audio_transcription.logprobs"]`. Each entry in the + array corresponds a log probability of which token would be selected for this + chunk of transcription. This can help to identify if it was possible there were + multiple valid options for a given chunk of transcription. + """ diff --git a/src/openai/types/realtime/conversation_item_input_audio_transcription_failed_event.py b/src/openai/types/realtime/conversation_item_input_audio_transcription_failed_event.py new file mode 100644 index 0000000000..e8ad05e43c --- /dev/null +++ b/src/openai/types/realtime/conversation_item_input_audio_transcription_failed_event.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ConversationItemInputAudioTranscriptionFailedEvent", "Error"] + + +class Error(BaseModel): + """Details of the transcription error.""" + + code: Optional[str] = None + """Error code, if any.""" + + message: Optional[str] = None + """A human-readable error message.""" + + param: Optional[str] = None + """Parameter related to the error, if any.""" + + type: Optional[str] = None + """The type of error.""" + + +class ConversationItemInputAudioTranscriptionFailedEvent(BaseModel): + """ + Returned when input audio transcription is configured, and a transcription + request for a user message failed. These events are separate from other + `error` events so that the client can identify the related Item. + """ + + content_index: int + """The index of the content part containing the audio.""" + + error: Error + """Details of the transcription error.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the user message item.""" + + type: Literal["conversation.item.input_audio_transcription.failed"] + """The event type, must be `conversation.item.input_audio_transcription.failed`.""" diff --git a/src/openai/types/realtime/conversation_item_input_audio_transcription_segment.py b/src/openai/types/realtime/conversation_item_input_audio_transcription_segment.py new file mode 100644 index 0000000000..dcc4916580 --- /dev/null +++ b/src/openai/types/realtime/conversation_item_input_audio_transcription_segment.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ConversationItemInputAudioTranscriptionSegment"] + + +class ConversationItemInputAudioTranscriptionSegment(BaseModel): + """Returned when an input audio transcription segment is identified for an item.""" + + id: str + """The segment identifier.""" + + content_index: int + """The index of the input audio content part within the item.""" + + end: float + """End time of the segment in seconds.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item containing the input audio content.""" + + speaker: str + """The detected speaker label for this segment.""" + + start: float + """Start time of the segment in seconds.""" + + text: str + """The text for this segment.""" + + type: Literal["conversation.item.input_audio_transcription.segment"] + """The event type, must be `conversation.item.input_audio_transcription.segment`.""" diff --git a/src/openai/types/realtime/conversation_item_param.py b/src/openai/types/realtime/conversation_item_param.py new file mode 100644 index 0000000000..c8b442ecad --- /dev/null +++ b/src/openai/types/realtime/conversation_item_param.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypeAlias + +from .realtime_mcp_tool_call_param import RealtimeMcpToolCallParam +from .realtime_mcp_list_tools_param import RealtimeMcpListToolsParam +from .realtime_mcp_approval_request_param import RealtimeMcpApprovalRequestParam +from .realtime_mcp_approval_response_param import RealtimeMcpApprovalResponseParam +from .realtime_conversation_item_user_message_param import RealtimeConversationItemUserMessageParam +from .realtime_conversation_item_function_call_param import RealtimeConversationItemFunctionCallParam +from .realtime_conversation_item_system_message_param import RealtimeConversationItemSystemMessageParam +from .realtime_conversation_item_assistant_message_param import RealtimeConversationItemAssistantMessageParam +from .realtime_conversation_item_function_call_output_param import RealtimeConversationItemFunctionCallOutputParam + +__all__ = ["ConversationItemParam"] + +ConversationItemParam: TypeAlias = Union[ + RealtimeConversationItemSystemMessageParam, + RealtimeConversationItemUserMessageParam, + RealtimeConversationItemAssistantMessageParam, + RealtimeConversationItemFunctionCallParam, + RealtimeConversationItemFunctionCallOutputParam, + RealtimeMcpApprovalResponseParam, + RealtimeMcpListToolsParam, + RealtimeMcpToolCallParam, + RealtimeMcpApprovalRequestParam, +] diff --git a/src/openai/types/realtime/conversation_item_retrieve_event.py b/src/openai/types/realtime/conversation_item_retrieve_event.py new file mode 100644 index 0000000000..e7d8eb6c49 --- /dev/null +++ b/src/openai/types/realtime/conversation_item_retrieve_event.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ConversationItemRetrieveEvent"] + + +class ConversationItemRetrieveEvent(BaseModel): + """ + Send this event when you want to retrieve the server's representation of a specific item in the conversation history. This is useful, for example, to inspect user audio after noise cancellation and VAD. + The server will respond with a `conversation.item.retrieved` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + + item_id: str + """The ID of the item to retrieve.""" + + type: Literal["conversation.item.retrieve"] + """The event type, must be `conversation.item.retrieve`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/conversation_item_retrieve_event_param.py b/src/openai/types/realtime/conversation_item_retrieve_event_param.py new file mode 100644 index 0000000000..59fdb6fb93 --- /dev/null +++ b/src/openai/types/realtime/conversation_item_retrieve_event_param.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ConversationItemRetrieveEventParam"] + + +class ConversationItemRetrieveEventParam(TypedDict, total=False): + """ + Send this event when you want to retrieve the server's representation of a specific item in the conversation history. This is useful, for example, to inspect user audio after noise cancellation and VAD. + The server will respond with a `conversation.item.retrieved` event, + unless the item does not exist in the conversation history, in which case the + server will respond with an error. + """ + + item_id: Required[str] + """The ID of the item to retrieve.""" + + type: Required[Literal["conversation.item.retrieve"]] + """The event type, must be `conversation.item.retrieve`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/conversation_item_truncate_event.py b/src/openai/types/realtime/conversation_item_truncate_event.py new file mode 100644 index 0000000000..16c82183c4 --- /dev/null +++ b/src/openai/types/realtime/conversation_item_truncate_event.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ConversationItemTruncateEvent"] + + +class ConversationItemTruncateEvent(BaseModel): + """Send this event to truncate a previous assistant message’s audio. + + The server + will produce audio faster than realtime, so this event is useful when the user + interrupts to truncate audio that has already been sent to the client but not + yet played. This will synchronize the server's understanding of the audio with + the client's playback. + + Truncating audio will delete the server-side text transcript to ensure there + is not text in the context that hasn't been heard by the user. + + If successful, the server will respond with a `conversation.item.truncated` + event. + """ + + audio_end_ms: int + """Inclusive duration up to which audio is truncated, in milliseconds. + + If the audio_end_ms is greater than the actual audio duration, the server will + respond with an error. + """ + + content_index: int + """The index of the content part to truncate. Set this to `0`.""" + + item_id: str + """The ID of the assistant message item to truncate. + + Only assistant message items can be truncated. + """ + + type: Literal["conversation.item.truncate"] + """The event type, must be `conversation.item.truncate`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/conversation_item_truncate_event_param.py b/src/openai/types/realtime/conversation_item_truncate_event_param.py new file mode 100644 index 0000000000..e9b41fc980 --- /dev/null +++ b/src/openai/types/realtime/conversation_item_truncate_event_param.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ConversationItemTruncateEventParam"] + + +class ConversationItemTruncateEventParam(TypedDict, total=False): + """Send this event to truncate a previous assistant message’s audio. + + The server + will produce audio faster than realtime, so this event is useful when the user + interrupts to truncate audio that has already been sent to the client but not + yet played. This will synchronize the server's understanding of the audio with + the client's playback. + + Truncating audio will delete the server-side text transcript to ensure there + is not text in the context that hasn't been heard by the user. + + If successful, the server will respond with a `conversation.item.truncated` + event. + """ + + audio_end_ms: Required[int] + """Inclusive duration up to which audio is truncated, in milliseconds. + + If the audio_end_ms is greater than the actual audio duration, the server will + respond with an error. + """ + + content_index: Required[int] + """The index of the content part to truncate. Set this to `0`.""" + + item_id: Required[str] + """The ID of the assistant message item to truncate. + + Only assistant message items can be truncated. + """ + + type: Required[Literal["conversation.item.truncate"]] + """The event type, must be `conversation.item.truncate`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/conversation_item_truncated_event.py b/src/openai/types/realtime/conversation_item_truncated_event.py new file mode 100644 index 0000000000..c78a776d9b --- /dev/null +++ b/src/openai/types/realtime/conversation_item_truncated_event.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ConversationItemTruncatedEvent"] + + +class ConversationItemTruncatedEvent(BaseModel): + """ + Returned when an earlier assistant audio message item is truncated by the + client with a `conversation.item.truncate` event. This event is used to + synchronize the server's understanding of the audio with the client's playback. + + This action will truncate the audio and remove the server-side text transcript + to ensure there is no text in the context that hasn't been heard by the user. + """ + + audio_end_ms: int + """The duration up to which the audio was truncated, in milliseconds.""" + + content_index: int + """The index of the content part that was truncated.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the assistant message item that was truncated.""" + + type: Literal["conversation.item.truncated"] + """The event type, must be `conversation.item.truncated`.""" diff --git a/src/openai/types/realtime/input_audio_buffer_append_event.py b/src/openai/types/realtime/input_audio_buffer_append_event.py new file mode 100644 index 0000000000..4c9e9a544d --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_append_event.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InputAudioBufferAppendEvent"] + + +class InputAudioBufferAppendEvent(BaseModel): + """Send this event to append audio bytes to the input audio buffer. + + The audio + buffer is temporary storage you can write to and later commit. A "commit" will create a new + user message item in the conversation history from the buffer content and clear the buffer. + Input audio transcription (if enabled) will be generated when the buffer is committed. + + If VAD is enabled the audio buffer is used to detect speech and the server will decide + when to commit. When Server VAD is disabled, you must commit the audio buffer + manually. Input audio noise reduction operates on writes to the audio buffer. + + The client may choose how much audio to place in each event up to a maximum + of 15 MiB, for example streaming smaller chunks from the client may allow the + VAD to be more responsive. Unlike most other client events, the server will + not send a confirmation response to this event. + """ + + audio: str + """Base64-encoded audio bytes. + + This must be in the format specified by the `input_audio_format` field in the + session configuration. + """ + + type: Literal["input_audio_buffer.append"] + """The event type, must be `input_audio_buffer.append`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/input_audio_buffer_append_event_param.py b/src/openai/types/realtime/input_audio_buffer_append_event_param.py new file mode 100644 index 0000000000..a0d308e4d9 --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_append_event_param.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["InputAudioBufferAppendEventParam"] + + +class InputAudioBufferAppendEventParam(TypedDict, total=False): + """Send this event to append audio bytes to the input audio buffer. + + The audio + buffer is temporary storage you can write to and later commit. A "commit" will create a new + user message item in the conversation history from the buffer content and clear the buffer. + Input audio transcription (if enabled) will be generated when the buffer is committed. + + If VAD is enabled the audio buffer is used to detect speech and the server will decide + when to commit. When Server VAD is disabled, you must commit the audio buffer + manually. Input audio noise reduction operates on writes to the audio buffer. + + The client may choose how much audio to place in each event up to a maximum + of 15 MiB, for example streaming smaller chunks from the client may allow the + VAD to be more responsive. Unlike most other client events, the server will + not send a confirmation response to this event. + """ + + audio: Required[str] + """Base64-encoded audio bytes. + + This must be in the format specified by the `input_audio_format` field in the + session configuration. + """ + + type: Required[Literal["input_audio_buffer.append"]] + """The event type, must be `input_audio_buffer.append`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/input_audio_buffer_clear_event.py b/src/openai/types/realtime/input_audio_buffer_clear_event.py new file mode 100644 index 0000000000..5526bcbfa9 --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_clear_event.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InputAudioBufferClearEvent"] + + +class InputAudioBufferClearEvent(BaseModel): + """Send this event to clear the audio bytes in the buffer. + + The server will + respond with an `input_audio_buffer.cleared` event. + """ + + type: Literal["input_audio_buffer.clear"] + """The event type, must be `input_audio_buffer.clear`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/input_audio_buffer_clear_event_param.py b/src/openai/types/realtime/input_audio_buffer_clear_event_param.py new file mode 100644 index 0000000000..8e0e9c55fa --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_clear_event_param.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["InputAudioBufferClearEventParam"] + + +class InputAudioBufferClearEventParam(TypedDict, total=False): + """Send this event to clear the audio bytes in the buffer. + + The server will + respond with an `input_audio_buffer.cleared` event. + """ + + type: Required[Literal["input_audio_buffer.clear"]] + """The event type, must be `input_audio_buffer.clear`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/input_audio_buffer_cleared_event.py b/src/openai/types/realtime/input_audio_buffer_cleared_event.py new file mode 100644 index 0000000000..e4775567dc --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_cleared_event.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InputAudioBufferClearedEvent"] + + +class InputAudioBufferClearedEvent(BaseModel): + """ + Returned when the input audio buffer is cleared by the client with a + `input_audio_buffer.clear` event. + """ + + event_id: str + """The unique ID of the server event.""" + + type: Literal["input_audio_buffer.cleared"] + """The event type, must be `input_audio_buffer.cleared`.""" diff --git a/src/openai/types/realtime/input_audio_buffer_commit_event.py b/src/openai/types/realtime/input_audio_buffer_commit_event.py new file mode 100644 index 0000000000..fe2ec01783 --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_commit_event.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InputAudioBufferCommitEvent"] + + +class InputAudioBufferCommitEvent(BaseModel): + """ + Send this event to commit the user input audio buffer, which will create a new user message item in the conversation. This event will produce an error if the input audio buffer is empty. When in Server VAD mode, the client does not need to send this event, the server will commit the audio buffer automatically. + + Committing the input audio buffer will trigger input audio transcription (if enabled in session configuration), but it will not create a response from the model. The server will respond with an `input_audio_buffer.committed` event. + """ + + type: Literal["input_audio_buffer.commit"] + """The event type, must be `input_audio_buffer.commit`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/input_audio_buffer_commit_event_param.py b/src/openai/types/realtime/input_audio_buffer_commit_event_param.py new file mode 100644 index 0000000000..20342795e8 --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_commit_event_param.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["InputAudioBufferCommitEventParam"] + + +class InputAudioBufferCommitEventParam(TypedDict, total=False): + """ + Send this event to commit the user input audio buffer, which will create a new user message item in the conversation. This event will produce an error if the input audio buffer is empty. When in Server VAD mode, the client does not need to send this event, the server will commit the audio buffer automatically. + + Committing the input audio buffer will trigger input audio transcription (if enabled in session configuration), but it will not create a response from the model. The server will respond with an `input_audio_buffer.committed` event. + """ + + type: Required[Literal["input_audio_buffer.commit"]] + """The event type, must be `input_audio_buffer.commit`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" diff --git a/src/openai/types/realtime/input_audio_buffer_committed_event.py b/src/openai/types/realtime/input_audio_buffer_committed_event.py new file mode 100644 index 0000000000..15dc8254f3 --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_committed_event.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InputAudioBufferCommittedEvent"] + + +class InputAudioBufferCommittedEvent(BaseModel): + """ + Returned when an input audio buffer is committed, either by the client or + automatically in server VAD mode. The `item_id` property is the ID of the user + message item that will be created, thus a `conversation.item.created` event + will also be sent to the client. + """ + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the user message item that will be created.""" + + type: Literal["input_audio_buffer.committed"] + """The event type, must be `input_audio_buffer.committed`.""" + + previous_item_id: Optional[str] = None + """ + The ID of the preceding item after which the new item will be inserted. Can be + `null` if the item has no predecessor. + """ diff --git a/src/openai/types/realtime/input_audio_buffer_dtmf_event_received_event.py b/src/openai/types/realtime/input_audio_buffer_dtmf_event_received_event.py new file mode 100644 index 0000000000..c2623cc7b8 --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_dtmf_event_received_event.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InputAudioBufferDtmfEventReceivedEvent"] + + +class InputAudioBufferDtmfEventReceivedEvent(BaseModel): + """**SIP Only:** Returned when an DTMF event is received. + + A DTMF event is a message that + represents a telephone keypad press (0–9, *, #, A–D). The `event` property + is the keypad that the user press. The `received_at` is the UTC Unix Timestamp + that the server received the event. + """ + + event: str + """The telephone keypad that was pressed by the user.""" + + received_at: int + """UTC Unix Timestamp when DTMF Event was received by server.""" + + type: Literal["input_audio_buffer.dtmf_event_received"] + """The event type, must be `input_audio_buffer.dtmf_event_received`.""" diff --git a/src/openai/types/realtime/input_audio_buffer_speech_started_event.py b/src/openai/types/realtime/input_audio_buffer_speech_started_event.py new file mode 100644 index 0000000000..1bd4c74eb0 --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_speech_started_event.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InputAudioBufferSpeechStartedEvent"] + + +class InputAudioBufferSpeechStartedEvent(BaseModel): + """ + Sent by the server when in `server_vad` mode to indicate that speech has been + detected in the audio buffer. This can happen any time audio is added to the + buffer (unless speech is already detected). The client may want to use this + event to interrupt audio playback or provide visual feedback to the user. + + The client should expect to receive a `input_audio_buffer.speech_stopped` event + when speech stops. The `item_id` property is the ID of the user message item + that will be created when speech stops and will also be included in the + `input_audio_buffer.speech_stopped` event (unless the client manually commits + the audio buffer during VAD activation). + """ + + audio_start_ms: int + """ + Milliseconds from the start of all audio written to the buffer during the + session when speech was first detected. This will correspond to the beginning of + audio sent to the model, and thus includes the `prefix_padding_ms` configured in + the Session. + """ + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the user message item that will be created when speech stops.""" + + type: Literal["input_audio_buffer.speech_started"] + """The event type, must be `input_audio_buffer.speech_started`.""" diff --git a/src/openai/types/realtime/input_audio_buffer_speech_stopped_event.py b/src/openai/types/realtime/input_audio_buffer_speech_stopped_event.py new file mode 100644 index 0000000000..b3fb20929a --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_speech_stopped_event.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InputAudioBufferSpeechStoppedEvent"] + + +class InputAudioBufferSpeechStoppedEvent(BaseModel): + """ + Returned in `server_vad` mode when the server detects the end of speech in + the audio buffer. The server will also send an `conversation.item.created` + event with the user message item that is created from the audio buffer. + """ + + audio_end_ms: int + """Milliseconds since the session started when speech stopped. + + This will correspond to the end of audio sent to the model, and thus includes + the `min_silence_duration_ms` configured in the Session. + """ + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the user message item that will be created.""" + + type: Literal["input_audio_buffer.speech_stopped"] + """The event type, must be `input_audio_buffer.speech_stopped`.""" diff --git a/src/openai/types/realtime/input_audio_buffer_timeout_triggered.py b/src/openai/types/realtime/input_audio_buffer_timeout_triggered.py new file mode 100644 index 0000000000..72b107d56e --- /dev/null +++ b/src/openai/types/realtime/input_audio_buffer_timeout_triggered.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InputAudioBufferTimeoutTriggered"] + + +class InputAudioBufferTimeoutTriggered(BaseModel): + """Returned when the Server VAD timeout is triggered for the input audio buffer. + + This is configured + with `idle_timeout_ms` in the `turn_detection` settings of the session, and it indicates that + there hasn't been any speech detected for the configured duration. + + The `audio_start_ms` and `audio_end_ms` fields indicate the segment of audio after the last + model response up to the triggering time, as an offset from the beginning of audio written + to the input audio buffer. This means it demarcates the segment of audio that was silent and + the difference between the start and end values will roughly match the configured timeout. + + The empty audio will be committed to the conversation as an `input_audio` item (there will be a + `input_audio_buffer.committed` event) and a model response will be generated. There may be speech + that didn't trigger VAD but is still detected by the model, so the model may respond with + something relevant to the conversation or a prompt to continue speaking. + """ + + audio_end_ms: int + """ + Millisecond offset of audio written to the input audio buffer at the time the + timeout was triggered. + """ + + audio_start_ms: int + """ + Millisecond offset of audio written to the input audio buffer that was after the + playback time of the last model response. + """ + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item associated with this segment.""" + + type: Literal["input_audio_buffer.timeout_triggered"] + """The event type, must be `input_audio_buffer.timeout_triggered`.""" diff --git a/src/openai/types/realtime/log_prob_properties.py b/src/openai/types/realtime/log_prob_properties.py new file mode 100644 index 0000000000..423af1c492 --- /dev/null +++ b/src/openai/types/realtime/log_prob_properties.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ..._models import BaseModel + +__all__ = ["LogProbProperties"] + + +class LogProbProperties(BaseModel): + """A log probability object.""" + + token: str + """The token that was used to generate the log probability.""" + + bytes: List[int] + """The bytes that were used to generate the log probability.""" + + logprob: float + """The log probability of the token.""" diff --git a/src/openai/types/realtime/mcp_list_tools_completed.py b/src/openai/types/realtime/mcp_list_tools_completed.py new file mode 100644 index 0000000000..2fe64147d6 --- /dev/null +++ b/src/openai/types/realtime/mcp_list_tools_completed.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["McpListToolsCompleted"] + + +class McpListToolsCompleted(BaseModel): + """Returned when listing MCP tools has completed for an item.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the MCP list tools item.""" + + type: Literal["mcp_list_tools.completed"] + """The event type, must be `mcp_list_tools.completed`.""" diff --git a/src/openai/types/realtime/mcp_list_tools_failed.py b/src/openai/types/realtime/mcp_list_tools_failed.py new file mode 100644 index 0000000000..8cad7c0a12 --- /dev/null +++ b/src/openai/types/realtime/mcp_list_tools_failed.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["McpListToolsFailed"] + + +class McpListToolsFailed(BaseModel): + """Returned when listing MCP tools has failed for an item.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the MCP list tools item.""" + + type: Literal["mcp_list_tools.failed"] + """The event type, must be `mcp_list_tools.failed`.""" diff --git a/src/openai/types/realtime/mcp_list_tools_in_progress.py b/src/openai/types/realtime/mcp_list_tools_in_progress.py new file mode 100644 index 0000000000..823bb875a3 --- /dev/null +++ b/src/openai/types/realtime/mcp_list_tools_in_progress.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["McpListToolsInProgress"] + + +class McpListToolsInProgress(BaseModel): + """Returned when listing MCP tools is in progress for an item.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the MCP list tools item.""" + + type: Literal["mcp_list_tools.in_progress"] + """The event type, must be `mcp_list_tools.in_progress`.""" diff --git a/src/openai/types/realtime/noise_reduction_type.py b/src/openai/types/realtime/noise_reduction_type.py new file mode 100644 index 0000000000..f4338991bb --- /dev/null +++ b/src/openai/types/realtime/noise_reduction_type.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["NoiseReductionType"] + +NoiseReductionType: TypeAlias = Literal["near_field", "far_field"] diff --git a/src/openai/types/realtime/output_audio_buffer_clear_event.py b/src/openai/types/realtime/output_audio_buffer_clear_event.py new file mode 100644 index 0000000000..b3fa7620ac --- /dev/null +++ b/src/openai/types/realtime/output_audio_buffer_clear_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["OutputAudioBufferClearEvent"] + + +class OutputAudioBufferClearEvent(BaseModel): + """**WebRTC/SIP Only:** Emit to cut off the current audio response. + + This will trigger the server to + stop generating audio and emit a `output_audio_buffer.cleared` event. This + event should be preceded by a `response.cancel` client event to stop the + generation of the current response. + [Learn more](https://platform.openai.com/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc). + """ + + type: Literal["output_audio_buffer.clear"] + """The event type, must be `output_audio_buffer.clear`.""" + + event_id: Optional[str] = None + """The unique ID of the client event used for error handling.""" diff --git a/src/openai/types/realtime/output_audio_buffer_clear_event_param.py b/src/openai/types/realtime/output_audio_buffer_clear_event_param.py new file mode 100644 index 0000000000..59f897a5c1 --- /dev/null +++ b/src/openai/types/realtime/output_audio_buffer_clear_event_param.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["OutputAudioBufferClearEventParam"] + + +class OutputAudioBufferClearEventParam(TypedDict, total=False): + """**WebRTC/SIP Only:** Emit to cut off the current audio response. + + This will trigger the server to + stop generating audio and emit a `output_audio_buffer.cleared` event. This + event should be preceded by a `response.cancel` client event to stop the + generation of the current response. + [Learn more](https://platform.openai.com/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc). + """ + + type: Required[Literal["output_audio_buffer.clear"]] + """The event type, must be `output_audio_buffer.clear`.""" + + event_id: str + """The unique ID of the client event used for error handling.""" diff --git a/src/openai/types/realtime/rate_limits_updated_event.py b/src/openai/types/realtime/rate_limits_updated_event.py new file mode 100644 index 0000000000..951de103af --- /dev/null +++ b/src/openai/types/realtime/rate_limits_updated_event.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RateLimitsUpdatedEvent", "RateLimit"] + + +class RateLimit(BaseModel): + limit: Optional[int] = None + """The maximum allowed value for the rate limit.""" + + name: Optional[Literal["requests", "tokens"]] = None + """The name of the rate limit (`requests`, `tokens`).""" + + remaining: Optional[int] = None + """The remaining value before the limit is reached.""" + + reset_seconds: Optional[float] = None + """Seconds until the rate limit resets.""" + + +class RateLimitsUpdatedEvent(BaseModel): + """Emitted at the beginning of a Response to indicate the updated rate limits. + + + When a Response is created some tokens will be "reserved" for the output + tokens, the rate limits shown here reflect that reservation, which is then + adjusted accordingly once the Response is completed. + """ + + event_id: str + """The unique ID of the server event.""" + + rate_limits: List[RateLimit] + """List of rate limit information.""" + + type: Literal["rate_limits.updated"] + """The event type, must be `rate_limits.updated`.""" diff --git a/src/openai/types/realtime/realtime_audio_config.py b/src/openai/types/realtime/realtime_audio_config.py new file mode 100644 index 0000000000..daa50358a8 --- /dev/null +++ b/src/openai/types/realtime/realtime_audio_config.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .realtime_audio_config_input import RealtimeAudioConfigInput +from .realtime_audio_config_output import RealtimeAudioConfigOutput + +__all__ = ["RealtimeAudioConfig"] + + +class RealtimeAudioConfig(BaseModel): + """Configuration for input and output audio.""" + + input: Optional[RealtimeAudioConfigInput] = None + + output: Optional[RealtimeAudioConfigOutput] = None diff --git a/src/openai/types/realtime/realtime_audio_config_input.py b/src/openai/types/realtime/realtime_audio_config_input.py new file mode 100644 index 0000000000..ba7d211d0d --- /dev/null +++ b/src/openai/types/realtime/realtime_audio_config_input.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .audio_transcription import AudioTranscription +from .noise_reduction_type import NoiseReductionType +from .realtime_audio_formats import RealtimeAudioFormats +from .realtime_audio_input_turn_detection import RealtimeAudioInputTurnDetection + +__all__ = ["RealtimeAudioConfigInput", "NoiseReduction"] + + +class NoiseReduction(BaseModel): + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. + Noise reduction filters audio added to the input audio buffer before it is sent to VAD and the model. + Filtering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio. + """ + + type: Optional[NoiseReductionType] = None + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class RealtimeAudioConfigInput(BaseModel): + format: Optional[RealtimeAudioFormats] = None + """The format of the input audio.""" + + noise_reduction: Optional[NoiseReduction] = None + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + transcription: Optional[AudioTranscription] = None + """ + Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously through + [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) + and should be treated as guidance of input audio content rather than precisely + what the model heard. The client can optionally set the language and prompt for + transcription, these offer additional guidance to the transcription service. + """ + + turn_detection: Optional[RealtimeAudioInputTurnDetection] = None + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. + + Server VAD means that the model will detect the start and end of speech based on + audio volume and respond at the end of user speech. + + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + + For `gpt-realtime-whisper` transcription sessions, turn detection must be set to + `null`; VAD is not supported. + """ diff --git a/src/openai/types/realtime/realtime_audio_config_input_param.py b/src/openai/types/realtime/realtime_audio_config_input_param.py new file mode 100644 index 0000000000..5cea9f0efe --- /dev/null +++ b/src/openai/types/realtime/realtime_audio_config_input_param.py @@ -0,0 +1,75 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +from .noise_reduction_type import NoiseReductionType +from .audio_transcription_param import AudioTranscriptionParam +from .realtime_audio_formats_param import RealtimeAudioFormatsParam +from .realtime_audio_input_turn_detection_param import RealtimeAudioInputTurnDetectionParam + +__all__ = ["RealtimeAudioConfigInputParam", "NoiseReduction"] + + +class NoiseReduction(TypedDict, total=False): + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. + Noise reduction filters audio added to the input audio buffer before it is sent to VAD and the model. + Filtering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio. + """ + + type: NoiseReductionType + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class RealtimeAudioConfigInputParam(TypedDict, total=False): + format: RealtimeAudioFormatsParam + """The format of the input audio.""" + + noise_reduction: NoiseReduction + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + transcription: AudioTranscriptionParam + """ + Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously through + [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) + and should be treated as guidance of input audio content rather than precisely + what the model heard. The client can optionally set the language and prompt for + transcription, these offer additional guidance to the transcription service. + """ + + turn_detection: Optional[RealtimeAudioInputTurnDetectionParam] + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. + + Server VAD means that the model will detect the start and end of speech based on + audio volume and respond at the end of user speech. + + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + + For `gpt-realtime-whisper` transcription sessions, turn detection must be set to + `null`; VAD is not supported. + """ diff --git a/src/openai/types/realtime/realtime_audio_config_output.py b/src/openai/types/realtime/realtime_audio_config_output.py new file mode 100644 index 0000000000..143cef67a5 --- /dev/null +++ b/src/openai/types/realtime/realtime_audio_config_output.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .realtime_audio_formats import RealtimeAudioFormats + +__all__ = ["RealtimeAudioConfigOutput", "Voice", "VoiceID"] + + +class VoiceID(BaseModel): + """Custom voice reference.""" + + id: str + """The custom voice ID, e.g. `voice_1234`.""" + + +Voice: TypeAlias = Union[ + str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"], VoiceID +] + + +class RealtimeAudioConfigOutput(BaseModel): + format: Optional[RealtimeAudioFormats] = None + """The format of the output audio.""" + + speed: Optional[float] = None + """ + The speed of the model's spoken response as a multiple of the original speed. + 1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. + This value can only be changed in between model turns, not while a response is + in progress. + + This parameter is a post-processing adjustment to the audio after it is + generated, it's also possible to prompt the model to speak faster or slower. + """ + + voice: Optional[Voice] = None + """The voice the model uses to respond. + + Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, + `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice + object with an `id`, for example `{ "id": "voice_1234" }`. Voice cannot be + changed during the session once the model has responded with audio at least + once. We recommend `marin` and `cedar` for best quality. + """ diff --git a/src/openai/types/realtime/realtime_audio_config_output_param.py b/src/openai/types/realtime/realtime_audio_config_output_param.py new file mode 100644 index 0000000000..5d920f69a6 --- /dev/null +++ b/src/openai/types/realtime/realtime_audio_config_output_param.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .realtime_audio_formats_param import RealtimeAudioFormatsParam + +__all__ = ["RealtimeAudioConfigOutputParam", "Voice", "VoiceID"] + + +class VoiceID(TypedDict, total=False): + """Custom voice reference.""" + + id: Required[str] + """The custom voice ID, e.g. `voice_1234`.""" + + +Voice: TypeAlias = Union[ + str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"], VoiceID +] + + +class RealtimeAudioConfigOutputParam(TypedDict, total=False): + format: RealtimeAudioFormatsParam + """The format of the output audio.""" + + speed: float + """ + The speed of the model's spoken response as a multiple of the original speed. + 1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. + This value can only be changed in between model turns, not while a response is + in progress. + + This parameter is a post-processing adjustment to the audio after it is + generated, it's also possible to prompt the model to speak faster or slower. + """ + + voice: Voice + """The voice the model uses to respond. + + Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, + `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice + object with an `id`, for example `{ "id": "voice_1234" }`. Voice cannot be + changed during the session once the model has responded with audio at least + once. We recommend `marin` and `cedar` for best quality. + """ diff --git a/src/openai/types/realtime/realtime_audio_config_param.py b/src/openai/types/realtime/realtime_audio_config_param.py new file mode 100644 index 0000000000..7899fe359b --- /dev/null +++ b/src/openai/types/realtime/realtime_audio_config_param.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +from .realtime_audio_config_input_param import RealtimeAudioConfigInputParam +from .realtime_audio_config_output_param import RealtimeAudioConfigOutputParam + +__all__ = ["RealtimeAudioConfigParam"] + + +class RealtimeAudioConfigParam(TypedDict, total=False): + """Configuration for input and output audio.""" + + input: RealtimeAudioConfigInputParam + + output: RealtimeAudioConfigOutputParam diff --git a/src/openai/types/realtime/realtime_audio_formats.py b/src/openai/types/realtime/realtime_audio_formats.py new file mode 100644 index 0000000000..fa10c9a7a4 --- /dev/null +++ b/src/openai/types/realtime/realtime_audio_formats.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = ["RealtimeAudioFormats", "AudioPCM", "AudioPCMU", "AudioPCMA"] + + +class AudioPCM(BaseModel): + """The PCM audio format. Only a 24kHz sample rate is supported.""" + + rate: Optional[Literal[24000]] = None + """The sample rate of the audio. Always `24000`.""" + + type: Optional[Literal["audio/pcm"]] = None + """The audio format. Always `audio/pcm`.""" + + +class AudioPCMU(BaseModel): + """The G.711 μ-law format.""" + + type: Optional[Literal["audio/pcmu"]] = None + """The audio format. Always `audio/pcmu`.""" + + +class AudioPCMA(BaseModel): + """The G.711 A-law format.""" + + type: Optional[Literal["audio/pcma"]] = None + """The audio format. Always `audio/pcma`.""" + + +RealtimeAudioFormats: TypeAlias = Annotated[Union[AudioPCM, AudioPCMU, AudioPCMA], PropertyInfo(discriminator="type")] diff --git a/src/openai/types/realtime/realtime_audio_formats_param.py b/src/openai/types/realtime/realtime_audio_formats_param.py new file mode 100644 index 0000000000..6392f632c3 --- /dev/null +++ b/src/openai/types/realtime/realtime_audio_formats_param.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypeAlias, TypedDict + +__all__ = ["RealtimeAudioFormatsParam", "AudioPCM", "AudioPCMU", "AudioPCMA"] + + +class AudioPCM(TypedDict, total=False): + """The PCM audio format. Only a 24kHz sample rate is supported.""" + + rate: Literal[24000] + """The sample rate of the audio. Always `24000`.""" + + type: Literal["audio/pcm"] + """The audio format. Always `audio/pcm`.""" + + +class AudioPCMU(TypedDict, total=False): + """The G.711 μ-law format.""" + + type: Literal["audio/pcmu"] + """The audio format. Always `audio/pcmu`.""" + + +class AudioPCMA(TypedDict, total=False): + """The G.711 A-law format.""" + + type: Literal["audio/pcma"] + """The audio format. Always `audio/pcma`.""" + + +RealtimeAudioFormatsParam: TypeAlias = Union[AudioPCM, AudioPCMU, AudioPCMA] diff --git a/src/openai/types/realtime/realtime_audio_input_turn_detection.py b/src/openai/types/realtime/realtime_audio_input_turn_detection.py new file mode 100644 index 0000000000..8d9aff3563 --- /dev/null +++ b/src/openai/types/realtime/realtime_audio_input_turn_detection.py @@ -0,0 +1,115 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = ["RealtimeAudioInputTurnDetection", "ServerVad", "SemanticVad"] + + +class ServerVad(BaseModel): + """ + Server-side voice activity detection (VAD) which flips on when user speech is detected and off after a period of silence. + """ + + type: Literal["server_vad"] + """Type of turn detection, `server_vad` to turn on simple Server VAD.""" + + create_response: Optional[bool] = None + """Whether or not to automatically generate a response when a VAD stop event + occurs. + + If `interrupt_response` is set to `false` this may fail to create a response if + the model is already responding. + + If both `create_response` and `interrupt_response` are set to `false`, the model + will never respond automatically but VAD events will still be emitted. + """ + + idle_timeout_ms: Optional[int] = None + """Optional timeout after which a model response will be triggered automatically. + + This is useful for situations in which a long pause from the user is unexpected, + such as a phone call. The model will effectively prompt the user to continue the + conversation based on the current context. + + The timeout value will be applied after the last model response's audio has + finished playing, i.e. it's set to the `response.done` time plus audio playback + duration. + + An `input_audio_buffer.timeout_triggered` event (plus events associated with the + Response) will be emitted when the timeout is reached. Idle timeout is currently + only supported for `server_vad` mode. + """ + + interrupt_response: Optional[bool] = None + """ + Whether or not to automatically interrupt (cancel) any ongoing response with + output to the default conversation (i.e. `conversation` of `auto`) when a VAD + start event occurs. If `true` then the response will be cancelled, otherwise it + will continue until complete. + + If both `create_response` and `interrupt_response` are set to `false`, the model + will never respond automatically but VAD events will still be emitted. + """ + + prefix_padding_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: Optional[float] = None + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + +class SemanticVad(BaseModel): + """ + Server-side semantic turn detection which uses a model to determine when the user has finished speaking. + """ + + type: Literal["semantic_vad"] + """Type of turn detection, `semantic_vad` to turn on Semantic VAD.""" + + create_response: Optional[bool] = None + """ + Whether or not to automatically generate a response when a VAD stop event + occurs. + """ + + eagerness: Optional[Literal["low", "medium", "high", "auto"]] = None + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. `low`, `medium`, and `high` have max timeouts of 8s, + 4s, and 2s respectively. + """ + + interrupt_response: Optional[bool] = None + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. + """ + + +RealtimeAudioInputTurnDetection: TypeAlias = Annotated[ + Union[ServerVad, SemanticVad, None], PropertyInfo(discriminator="type") +] diff --git a/src/openai/types/realtime/realtime_audio_input_turn_detection_param.py b/src/openai/types/realtime/realtime_audio_input_turn_detection_param.py new file mode 100644 index 0000000000..30522d74e1 --- /dev/null +++ b/src/openai/types/realtime/realtime_audio_input_turn_detection_param.py @@ -0,0 +1,112 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = ["RealtimeAudioInputTurnDetectionParam", "ServerVad", "SemanticVad"] + + +class ServerVad(TypedDict, total=False): + """ + Server-side voice activity detection (VAD) which flips on when user speech is detected and off after a period of silence. + """ + + type: Required[Literal["server_vad"]] + """Type of turn detection, `server_vad` to turn on simple Server VAD.""" + + create_response: bool + """Whether or not to automatically generate a response when a VAD stop event + occurs. + + If `interrupt_response` is set to `false` this may fail to create a response if + the model is already responding. + + If both `create_response` and `interrupt_response` are set to `false`, the model + will never respond automatically but VAD events will still be emitted. + """ + + idle_timeout_ms: Optional[int] + """Optional timeout after which a model response will be triggered automatically. + + This is useful for situations in which a long pause from the user is unexpected, + such as a phone call. The model will effectively prompt the user to continue the + conversation based on the current context. + + The timeout value will be applied after the last model response's audio has + finished playing, i.e. it's set to the `response.done` time plus audio playback + duration. + + An `input_audio_buffer.timeout_triggered` event (plus events associated with the + Response) will be emitted when the timeout is reached. Idle timeout is currently + only supported for `server_vad` mode. + """ + + interrupt_response: bool + """ + Whether or not to automatically interrupt (cancel) any ongoing response with + output to the default conversation (i.e. `conversation` of `auto`) when a VAD + start event occurs. If `true` then the response will be cancelled, otherwise it + will continue until complete. + + If both `create_response` and `interrupt_response` are set to `false`, the model + will never respond automatically but VAD events will still be emitted. + """ + + prefix_padding_ms: int + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: int + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: float + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + +class SemanticVad(TypedDict, total=False): + """ + Server-side semantic turn detection which uses a model to determine when the user has finished speaking. + """ + + type: Required[Literal["semantic_vad"]] + """Type of turn detection, `semantic_vad` to turn on Semantic VAD.""" + + create_response: bool + """ + Whether or not to automatically generate a response when a VAD stop event + occurs. + """ + + eagerness: Literal["low", "medium", "high", "auto"] + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. `low`, `medium`, and `high` have max timeouts of 8s, + 4s, and 2s respectively. + """ + + interrupt_response: bool + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. + """ + + +RealtimeAudioInputTurnDetectionParam: TypeAlias = Union[ServerVad, SemanticVad] diff --git a/src/openai/types/realtime/realtime_client_event.py b/src/openai/types/realtime/realtime_client_event.py new file mode 100644 index 0000000000..3b1c348daa --- /dev/null +++ b/src/openai/types/realtime/realtime_client_event.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from .session_update_event import SessionUpdateEvent +from .response_cancel_event import ResponseCancelEvent +from .response_create_event import ResponseCreateEvent +from .conversation_item_create_event import ConversationItemCreateEvent +from .conversation_item_delete_event import ConversationItemDeleteEvent +from .input_audio_buffer_clear_event import InputAudioBufferClearEvent +from .input_audio_buffer_append_event import InputAudioBufferAppendEvent +from .input_audio_buffer_commit_event import InputAudioBufferCommitEvent +from .output_audio_buffer_clear_event import OutputAudioBufferClearEvent +from .conversation_item_retrieve_event import ConversationItemRetrieveEvent +from .conversation_item_truncate_event import ConversationItemTruncateEvent + +__all__ = ["RealtimeClientEvent"] + +RealtimeClientEvent: TypeAlias = Annotated[ + Union[ + ConversationItemCreateEvent, + ConversationItemDeleteEvent, + ConversationItemRetrieveEvent, + ConversationItemTruncateEvent, + InputAudioBufferAppendEvent, + InputAudioBufferClearEvent, + OutputAudioBufferClearEvent, + InputAudioBufferCommitEvent, + ResponseCancelEvent, + ResponseCreateEvent, + SessionUpdateEvent, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/realtime/realtime_client_event_param.py b/src/openai/types/realtime/realtime_client_event_param.py new file mode 100644 index 0000000000..cda5766e2a --- /dev/null +++ b/src/openai/types/realtime/realtime_client_event_param.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypeAlias + +from .session_update_event_param import SessionUpdateEventParam +from .response_cancel_event_param import ResponseCancelEventParam +from .response_create_event_param import ResponseCreateEventParam +from .conversation_item_create_event_param import ConversationItemCreateEventParam +from .conversation_item_delete_event_param import ConversationItemDeleteEventParam +from .input_audio_buffer_clear_event_param import InputAudioBufferClearEventParam +from .input_audio_buffer_append_event_param import InputAudioBufferAppendEventParam +from .input_audio_buffer_commit_event_param import InputAudioBufferCommitEventParam +from .output_audio_buffer_clear_event_param import OutputAudioBufferClearEventParam +from .conversation_item_retrieve_event_param import ConversationItemRetrieveEventParam +from .conversation_item_truncate_event_param import ConversationItemTruncateEventParam + +__all__ = ["RealtimeClientEventParam"] + +RealtimeClientEventParam: TypeAlias = Union[ + ConversationItemCreateEventParam, + ConversationItemDeleteEventParam, + ConversationItemRetrieveEventParam, + ConversationItemTruncateEventParam, + InputAudioBufferAppendEventParam, + InputAudioBufferClearEventParam, + OutputAudioBufferClearEventParam, + InputAudioBufferCommitEventParam, + ResponseCancelEventParam, + ResponseCreateEventParam, + SessionUpdateEventParam, +] diff --git a/src/openai/types/realtime/realtime_connect_params.py b/src/openai/types/realtime/realtime_connect_params.py new file mode 100644 index 0000000000..950f36212f --- /dev/null +++ b/src/openai/types/realtime/realtime_connect_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["RealtimeConnectParams"] + + +class RealtimeConnectParams(TypedDict, total=False): + call_id: str + + model: str diff --git a/src/openai/types/realtime/realtime_conversation_item_assistant_message.py b/src/openai/types/realtime/realtime_conversation_item_assistant_message.py new file mode 100644 index 0000000000..207831a3c8 --- /dev/null +++ b/src/openai/types/realtime/realtime_conversation_item_assistant_message.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeConversationItemAssistantMessage", "Content"] + + +class Content(BaseModel): + audio: Optional[str] = None + """ + Base64-encoded audio bytes, these will be parsed as the format specified in the + session output audio type configuration. This defaults to PCM 16-bit 24kHz mono + if not specified. + """ + + text: Optional[str] = None + """The text content.""" + + transcript: Optional[str] = None + """ + The transcript of the audio content, this will always be present if the output + type is `audio`. + """ + + type: Optional[Literal["output_text", "output_audio"]] = None + """ + The content type, `output_text` or `output_audio` depending on the session + `output_modalities` configuration. + """ + + +class RealtimeConversationItemAssistantMessage(BaseModel): + """An assistant message item in a Realtime conversation.""" + + content: List[Content] + """The content of the message.""" + + role: Literal["assistant"] + """The role of the message sender. Always `assistant`.""" + + type: Literal["message"] + """The type of the item. Always `message`.""" + + id: Optional[str] = None + """The unique ID of the item. + + This may be provided by the client or generated by the server. + """ + + object: Optional[Literal["realtime.item"]] = None + """Identifier for the API object being returned - always `realtime.item`. + + Optional when creating a new item. + """ + + status: Optional[Literal["completed", "incomplete", "in_progress"]] = None + """The status of the item. Has no effect on the conversation.""" diff --git a/src/openai/types/realtime/realtime_conversation_item_assistant_message_param.py b/src/openai/types/realtime/realtime_conversation_item_assistant_message_param.py new file mode 100644 index 0000000000..abc78e7d3f --- /dev/null +++ b/src/openai/types/realtime/realtime_conversation_item_assistant_message_param.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeConversationItemAssistantMessageParam", "Content"] + + +class Content(TypedDict, total=False): + audio: str + """ + Base64-encoded audio bytes, these will be parsed as the format specified in the + session output audio type configuration. This defaults to PCM 16-bit 24kHz mono + if not specified. + """ + + text: str + """The text content.""" + + transcript: str + """ + The transcript of the audio content, this will always be present if the output + type is `audio`. + """ + + type: Literal["output_text", "output_audio"] + """ + The content type, `output_text` or `output_audio` depending on the session + `output_modalities` configuration. + """ + + +class RealtimeConversationItemAssistantMessageParam(TypedDict, total=False): + """An assistant message item in a Realtime conversation.""" + + content: Required[Iterable[Content]] + """The content of the message.""" + + role: Required[Literal["assistant"]] + """The role of the message sender. Always `assistant`.""" + + type: Required[Literal["message"]] + """The type of the item. Always `message`.""" + + id: str + """The unique ID of the item. + + This may be provided by the client or generated by the server. + """ + + object: Literal["realtime.item"] + """Identifier for the API object being returned - always `realtime.item`. + + Optional when creating a new item. + """ + + status: Literal["completed", "incomplete", "in_progress"] + """The status of the item. Has no effect on the conversation.""" diff --git a/src/openai/types/realtime/realtime_conversation_item_function_call.py b/src/openai/types/realtime/realtime_conversation_item_function_call.py new file mode 100644 index 0000000000..4e40394883 --- /dev/null +++ b/src/openai/types/realtime/realtime_conversation_item_function_call.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeConversationItemFunctionCall"] + + +class RealtimeConversationItemFunctionCall(BaseModel): + """A function call item in a Realtime conversation.""" + + arguments: str + """The arguments of the function call. + + This is a JSON-encoded string representing the arguments passed to the function, + for example `{"arg1": "value1", "arg2": 42}`. + """ + + name: str + """The name of the function being called.""" + + type: Literal["function_call"] + """The type of the item. Always `function_call`.""" + + id: Optional[str] = None + """The unique ID of the item. + + This may be provided by the client or generated by the server. + """ + + call_id: Optional[str] = None + """The ID of the function call.""" + + object: Optional[Literal["realtime.item"]] = None + """Identifier for the API object being returned - always `realtime.item`. + + Optional when creating a new item. + """ + + status: Optional[Literal["completed", "incomplete", "in_progress"]] = None + """The status of the item. Has no effect on the conversation.""" diff --git a/src/openai/types/realtime/realtime_conversation_item_function_call_output.py b/src/openai/types/realtime/realtime_conversation_item_function_call_output.py new file mode 100644 index 0000000000..cdbc352d85 --- /dev/null +++ b/src/openai/types/realtime/realtime_conversation_item_function_call_output.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeConversationItemFunctionCallOutput"] + + +class RealtimeConversationItemFunctionCallOutput(BaseModel): + """A function call output item in a Realtime conversation.""" + + call_id: str + """The ID of the function call this output is for.""" + + output: str + """ + The output of the function call, this is free text and can contain any + information or simply be empty. + """ + + type: Literal["function_call_output"] + """The type of the item. Always `function_call_output`.""" + + id: Optional[str] = None + """The unique ID of the item. + + This may be provided by the client or generated by the server. + """ + + object: Optional[Literal["realtime.item"]] = None + """Identifier for the API object being returned - always `realtime.item`. + + Optional when creating a new item. + """ + + status: Optional[Literal["completed", "incomplete", "in_progress"]] = None + """The status of the item. Has no effect on the conversation.""" diff --git a/src/openai/types/realtime/realtime_conversation_item_function_call_output_param.py b/src/openai/types/realtime/realtime_conversation_item_function_call_output_param.py new file mode 100644 index 0000000000..2e56a81dc3 --- /dev/null +++ b/src/openai/types/realtime/realtime_conversation_item_function_call_output_param.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeConversationItemFunctionCallOutputParam"] + + +class RealtimeConversationItemFunctionCallOutputParam(TypedDict, total=False): + """A function call output item in a Realtime conversation.""" + + call_id: Required[str] + """The ID of the function call this output is for.""" + + output: Required[str] + """ + The output of the function call, this is free text and can contain any + information or simply be empty. + """ + + type: Required[Literal["function_call_output"]] + """The type of the item. Always `function_call_output`.""" + + id: str + """The unique ID of the item. + + This may be provided by the client or generated by the server. + """ + + object: Literal["realtime.item"] + """Identifier for the API object being returned - always `realtime.item`. + + Optional when creating a new item. + """ + + status: Literal["completed", "incomplete", "in_progress"] + """The status of the item. Has no effect on the conversation.""" diff --git a/src/openai/types/realtime/realtime_conversation_item_function_call_param.py b/src/openai/types/realtime/realtime_conversation_item_function_call_param.py new file mode 100644 index 0000000000..6467ce149e --- /dev/null +++ b/src/openai/types/realtime/realtime_conversation_item_function_call_param.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeConversationItemFunctionCallParam"] + + +class RealtimeConversationItemFunctionCallParam(TypedDict, total=False): + """A function call item in a Realtime conversation.""" + + arguments: Required[str] + """The arguments of the function call. + + This is a JSON-encoded string representing the arguments passed to the function, + for example `{"arg1": "value1", "arg2": 42}`. + """ + + name: Required[str] + """The name of the function being called.""" + + type: Required[Literal["function_call"]] + """The type of the item. Always `function_call`.""" + + id: str + """The unique ID of the item. + + This may be provided by the client or generated by the server. + """ + + call_id: str + """The ID of the function call.""" + + object: Literal["realtime.item"] + """Identifier for the API object being returned - always `realtime.item`. + + Optional when creating a new item. + """ + + status: Literal["completed", "incomplete", "in_progress"] + """The status of the item. Has no effect on the conversation.""" diff --git a/src/openai/types/realtime/realtime_conversation_item_system_message.py b/src/openai/types/realtime/realtime_conversation_item_system_message.py new file mode 100644 index 0000000000..f69bc03937 --- /dev/null +++ b/src/openai/types/realtime/realtime_conversation_item_system_message.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeConversationItemSystemMessage", "Content"] + + +class Content(BaseModel): + text: Optional[str] = None + """The text content.""" + + type: Optional[Literal["input_text"]] = None + """The content type. Always `input_text` for system messages.""" + + +class RealtimeConversationItemSystemMessage(BaseModel): + """ + A system message in a Realtime conversation can be used to provide additional context or instructions to the model. This is similar but distinct from the instruction prompt provided at the start of a conversation, as system messages can be added at any point in the conversation. For major changes to the conversation's behavior, use instructions, but for smaller updates (e.g. "the user is now asking about a different topic"), use system messages. + """ + + content: List[Content] + """The content of the message.""" + + role: Literal["system"] + """The role of the message sender. Always `system`.""" + + type: Literal["message"] + """The type of the item. Always `message`.""" + + id: Optional[str] = None + """The unique ID of the item. + + This may be provided by the client or generated by the server. + """ + + object: Optional[Literal["realtime.item"]] = None + """Identifier for the API object being returned - always `realtime.item`. + + Optional when creating a new item. + """ + + status: Optional[Literal["completed", "incomplete", "in_progress"]] = None + """The status of the item. Has no effect on the conversation.""" diff --git a/src/openai/types/realtime/realtime_conversation_item_system_message_param.py b/src/openai/types/realtime/realtime_conversation_item_system_message_param.py new file mode 100644 index 0000000000..93880198fa --- /dev/null +++ b/src/openai/types/realtime/realtime_conversation_item_system_message_param.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeConversationItemSystemMessageParam", "Content"] + + +class Content(TypedDict, total=False): + text: str + """The text content.""" + + type: Literal["input_text"] + """The content type. Always `input_text` for system messages.""" + + +class RealtimeConversationItemSystemMessageParam(TypedDict, total=False): + """ + A system message in a Realtime conversation can be used to provide additional context or instructions to the model. This is similar but distinct from the instruction prompt provided at the start of a conversation, as system messages can be added at any point in the conversation. For major changes to the conversation's behavior, use instructions, but for smaller updates (e.g. "the user is now asking about a different topic"), use system messages. + """ + + content: Required[Iterable[Content]] + """The content of the message.""" + + role: Required[Literal["system"]] + """The role of the message sender. Always `system`.""" + + type: Required[Literal["message"]] + """The type of the item. Always `message`.""" + + id: str + """The unique ID of the item. + + This may be provided by the client or generated by the server. + """ + + object: Literal["realtime.item"] + """Identifier for the API object being returned - always `realtime.item`. + + Optional when creating a new item. + """ + + status: Literal["completed", "incomplete", "in_progress"] + """The status of the item. Has no effect on the conversation.""" diff --git a/src/openai/types/realtime/realtime_conversation_item_user_message.py b/src/openai/types/realtime/realtime_conversation_item_user_message.py new file mode 100644 index 0000000000..20e9614eb6 --- /dev/null +++ b/src/openai/types/realtime/realtime_conversation_item_user_message.py @@ -0,0 +1,71 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeConversationItemUserMessage", "Content"] + + +class Content(BaseModel): + audio: Optional[str] = None + """ + Base64-encoded audio bytes (for `input_audio`), these will be parsed as the + format specified in the session input audio type configuration. This defaults to + PCM 16-bit 24kHz mono if not specified. + """ + + detail: Optional[Literal["auto", "low", "high"]] = None + """The detail level of the image (for `input_image`). + + `auto` will default to `high`. + """ + + image_url: Optional[str] = None + """Base64-encoded image bytes (for `input_image`) as a data URI. + + For example `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...`. Supported + formats are PNG and JPEG. + """ + + text: Optional[str] = None + """The text content (for `input_text`).""" + + transcript: Optional[str] = None + """Transcript of the audio (for `input_audio`). + + This is not sent to the model, but will be attached to the message item for + reference. + """ + + type: Optional[Literal["input_text", "input_audio", "input_image"]] = None + """The content type (`input_text`, `input_audio`, or `input_image`).""" + + +class RealtimeConversationItemUserMessage(BaseModel): + """A user message item in a Realtime conversation.""" + + content: List[Content] + """The content of the message.""" + + role: Literal["user"] + """The role of the message sender. Always `user`.""" + + type: Literal["message"] + """The type of the item. Always `message`.""" + + id: Optional[str] = None + """The unique ID of the item. + + This may be provided by the client or generated by the server. + """ + + object: Optional[Literal["realtime.item"]] = None + """Identifier for the API object being returned - always `realtime.item`. + + Optional when creating a new item. + """ + + status: Optional[Literal["completed", "incomplete", "in_progress"]] = None + """The status of the item. Has no effect on the conversation.""" diff --git a/src/openai/types/realtime/realtime_conversation_item_user_message_param.py b/src/openai/types/realtime/realtime_conversation_item_user_message_param.py new file mode 100644 index 0000000000..69a24692e8 --- /dev/null +++ b/src/openai/types/realtime/realtime_conversation_item_user_message_param.py @@ -0,0 +1,71 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeConversationItemUserMessageParam", "Content"] + + +class Content(TypedDict, total=False): + audio: str + """ + Base64-encoded audio bytes (for `input_audio`), these will be parsed as the + format specified in the session input audio type configuration. This defaults to + PCM 16-bit 24kHz mono if not specified. + """ + + detail: Literal["auto", "low", "high"] + """The detail level of the image (for `input_image`). + + `auto` will default to `high`. + """ + + image_url: str + """Base64-encoded image bytes (for `input_image`) as a data URI. + + For example `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...`. Supported + formats are PNG and JPEG. + """ + + text: str + """The text content (for `input_text`).""" + + transcript: str + """Transcript of the audio (for `input_audio`). + + This is not sent to the model, but will be attached to the message item for + reference. + """ + + type: Literal["input_text", "input_audio", "input_image"] + """The content type (`input_text`, `input_audio`, or `input_image`).""" + + +class RealtimeConversationItemUserMessageParam(TypedDict, total=False): + """A user message item in a Realtime conversation.""" + + content: Required[Iterable[Content]] + """The content of the message.""" + + role: Required[Literal["user"]] + """The role of the message sender. Always `user`.""" + + type: Required[Literal["message"]] + """The type of the item. Always `message`.""" + + id: str + """The unique ID of the item. + + This may be provided by the client or generated by the server. + """ + + object: Literal["realtime.item"] + """Identifier for the API object being returned - always `realtime.item`. + + Optional when creating a new item. + """ + + status: Literal["completed", "incomplete", "in_progress"] + """The status of the item. Has no effect on the conversation.""" diff --git a/src/openai/types/realtime/realtime_error.py b/src/openai/types/realtime/realtime_error.py new file mode 100644 index 0000000000..2aa5bc9425 --- /dev/null +++ b/src/openai/types/realtime/realtime_error.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["RealtimeError"] + + +class RealtimeError(BaseModel): + """Details of the error.""" + + message: str + """A human-readable error message.""" + + type: str + """The type of error (e.g., "invalid_request_error", "server_error").""" + + code: Optional[str] = None + """Error code, if any.""" + + event_id: Optional[str] = None + """The event_id of the client event that caused the error, if applicable.""" + + param: Optional[str] = None + """Parameter related to the error, if any.""" diff --git a/src/openai/types/realtime/realtime_error_event.py b/src/openai/types/realtime/realtime_error_event.py new file mode 100644 index 0000000000..574464b29e --- /dev/null +++ b/src/openai/types/realtime/realtime_error_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from .realtime_error import RealtimeError + +__all__ = ["RealtimeErrorEvent"] + + +class RealtimeErrorEvent(BaseModel): + """ + Returned when an error occurs, which could be a client problem or a server + problem. Most errors are recoverable and the session will stay open, we + recommend to implementors to monitor and log error messages by default. + """ + + error: RealtimeError + """Details of the error.""" + + event_id: str + """The unique ID of the server event.""" + + type: Literal["error"] + """The event type, must be `error`.""" diff --git a/src/openai/types/realtime/realtime_function_tool.py b/src/openai/types/realtime/realtime_function_tool.py new file mode 100644 index 0000000000..48dbf9929d --- /dev/null +++ b/src/openai/types/realtime/realtime_function_tool.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeFunctionTool"] + + +class RealtimeFunctionTool(BaseModel): + description: Optional[str] = None + """ + The description of the function, including guidance on when and how to call it, + and guidance about what to tell the user when calling (if anything). + """ + + name: Optional[str] = None + """The name of the function.""" + + parameters: Optional[object] = None + """Parameters of the function in JSON Schema.""" + + type: Optional[Literal["function"]] = None + """The type of the tool, i.e. `function`.""" diff --git a/src/openai/types/realtime/realtime_function_tool_param.py b/src/openai/types/realtime/realtime_function_tool_param.py new file mode 100644 index 0000000000..f42e3e497c --- /dev/null +++ b/src/openai/types/realtime/realtime_function_tool_param.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["RealtimeFunctionToolParam"] + + +class RealtimeFunctionToolParam(TypedDict, total=False): + description: str + """ + The description of the function, including guidance on when and how to call it, + and guidance about what to tell the user when calling (if anything). + """ + + name: str + """The name of the function.""" + + parameters: object + """Parameters of the function in JSON Schema.""" + + type: Literal["function"] + """The type of the tool, i.e. `function`.""" diff --git a/src/openai/types/realtime/realtime_mcp_approval_request.py b/src/openai/types/realtime/realtime_mcp_approval_request.py new file mode 100644 index 0000000000..1744c90070 --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_approval_request.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeMcpApprovalRequest"] + + +class RealtimeMcpApprovalRequest(BaseModel): + """A Realtime item requesting human approval of a tool invocation.""" + + id: str + """The unique ID of the approval request.""" + + arguments: str + """A JSON string of arguments for the tool.""" + + name: str + """The name of the tool to run.""" + + server_label: str + """The label of the MCP server making the request.""" + + type: Literal["mcp_approval_request"] + """The type of the item. Always `mcp_approval_request`.""" diff --git a/src/openai/types/realtime/realtime_mcp_approval_request_param.py b/src/openai/types/realtime/realtime_mcp_approval_request_param.py new file mode 100644 index 0000000000..f7cb68d67e --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_approval_request_param.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeMcpApprovalRequestParam"] + + +class RealtimeMcpApprovalRequestParam(TypedDict, total=False): + """A Realtime item requesting human approval of a tool invocation.""" + + id: Required[str] + """The unique ID of the approval request.""" + + arguments: Required[str] + """A JSON string of arguments for the tool.""" + + name: Required[str] + """The name of the tool to run.""" + + server_label: Required[str] + """The label of the MCP server making the request.""" + + type: Required[Literal["mcp_approval_request"]] + """The type of the item. Always `mcp_approval_request`.""" diff --git a/src/openai/types/realtime/realtime_mcp_approval_response.py b/src/openai/types/realtime/realtime_mcp_approval_response.py new file mode 100644 index 0000000000..f8525a12fc --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_approval_response.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeMcpApprovalResponse"] + + +class RealtimeMcpApprovalResponse(BaseModel): + """A Realtime item responding to an MCP approval request.""" + + id: str + """The unique ID of the approval response.""" + + approval_request_id: str + """The ID of the approval request being answered.""" + + approve: bool + """Whether the request was approved.""" + + type: Literal["mcp_approval_response"] + """The type of the item. Always `mcp_approval_response`.""" + + reason: Optional[str] = None + """Optional reason for the decision.""" diff --git a/src/openai/types/realtime/realtime_mcp_approval_response_param.py b/src/openai/types/realtime/realtime_mcp_approval_response_param.py new file mode 100644 index 0000000000..6a65f7ce38 --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_approval_response_param.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeMcpApprovalResponseParam"] + + +class RealtimeMcpApprovalResponseParam(TypedDict, total=False): + """A Realtime item responding to an MCP approval request.""" + + id: Required[str] + """The unique ID of the approval response.""" + + approval_request_id: Required[str] + """The ID of the approval request being answered.""" + + approve: Required[bool] + """Whether the request was approved.""" + + type: Required[Literal["mcp_approval_response"]] + """The type of the item. Always `mcp_approval_response`.""" + + reason: Optional[str] + """Optional reason for the decision.""" diff --git a/src/openai/types/realtime/realtime_mcp_list_tools.py b/src/openai/types/realtime/realtime_mcp_list_tools.py new file mode 100644 index 0000000000..669d1fb43b --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_list_tools.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeMcpListTools", "Tool"] + + +class Tool(BaseModel): + """A tool available on an MCP server.""" + + input_schema: object + """The JSON schema describing the tool's input.""" + + name: str + """The name of the tool.""" + + annotations: Optional[object] = None + """Additional annotations about the tool.""" + + description: Optional[str] = None + """The description of the tool.""" + + +class RealtimeMcpListTools(BaseModel): + """A Realtime item listing tools available on an MCP server.""" + + server_label: str + """The label of the MCP server.""" + + tools: List[Tool] + """The tools available on the server.""" + + type: Literal["mcp_list_tools"] + """The type of the item. Always `mcp_list_tools`.""" + + id: Optional[str] = None + """The unique ID of the list.""" diff --git a/src/openai/types/realtime/realtime_mcp_list_tools_param.py b/src/openai/types/realtime/realtime_mcp_list_tools_param.py new file mode 100644 index 0000000000..614fa53347 --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_list_tools_param.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeMcpListToolsParam", "Tool"] + + +class Tool(TypedDict, total=False): + """A tool available on an MCP server.""" + + input_schema: Required[object] + """The JSON schema describing the tool's input.""" + + name: Required[str] + """The name of the tool.""" + + annotations: Optional[object] + """Additional annotations about the tool.""" + + description: Optional[str] + """The description of the tool.""" + + +class RealtimeMcpListToolsParam(TypedDict, total=False): + """A Realtime item listing tools available on an MCP server.""" + + server_label: Required[str] + """The label of the MCP server.""" + + tools: Required[Iterable[Tool]] + """The tools available on the server.""" + + type: Required[Literal["mcp_list_tools"]] + """The type of the item. Always `mcp_list_tools`.""" + + id: str + """The unique ID of the list.""" diff --git a/src/openai/types/realtime/realtime_mcp_protocol_error.py b/src/openai/types/realtime/realtime_mcp_protocol_error.py new file mode 100644 index 0000000000..2e7cfdffa3 --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_protocol_error.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeMcpProtocolError"] + + +class RealtimeMcpProtocolError(BaseModel): + code: int + + message: str + + type: Literal["protocol_error"] diff --git a/src/openai/types/realtime/realtime_mcp_protocol_error_param.py b/src/openai/types/realtime/realtime_mcp_protocol_error_param.py new file mode 100644 index 0000000000..bebe3d379e --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_protocol_error_param.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeMcpProtocolErrorParam"] + + +class RealtimeMcpProtocolErrorParam(TypedDict, total=False): + code: Required[int] + + message: Required[str] + + type: Required[Literal["protocol_error"]] diff --git a/src/openai/types/realtime/realtime_mcp_tool_call.py b/src/openai/types/realtime/realtime_mcp_tool_call.py new file mode 100644 index 0000000000..f53ad0eaa9 --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_tool_call.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .realtime_mcphttp_error import RealtimeMcphttpError +from .realtime_mcp_protocol_error import RealtimeMcpProtocolError +from .realtime_mcp_tool_execution_error import RealtimeMcpToolExecutionError + +__all__ = ["RealtimeMcpToolCall", "Error"] + +Error: TypeAlias = Annotated[ + Union[RealtimeMcpProtocolError, RealtimeMcpToolExecutionError, RealtimeMcphttpError, None], + PropertyInfo(discriminator="type"), +] + + +class RealtimeMcpToolCall(BaseModel): + """A Realtime item representing an invocation of a tool on an MCP server.""" + + id: str + """The unique ID of the tool call.""" + + arguments: str + """A JSON string of the arguments passed to the tool.""" + + name: str + """The name of the tool that was run.""" + + server_label: str + """The label of the MCP server running the tool.""" + + type: Literal["mcp_call"] + """The type of the item. Always `mcp_call`.""" + + approval_request_id: Optional[str] = None + """The ID of an associated approval request, if any.""" + + error: Optional[Error] = None + """The error from the tool call, if any.""" + + output: Optional[str] = None + """The output from the tool call.""" diff --git a/src/openai/types/realtime/realtime_mcp_tool_call_param.py b/src/openai/types/realtime/realtime_mcp_tool_call_param.py new file mode 100644 index 0000000000..8ccb5efc8a --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_tool_call_param.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .realtime_mcphttp_error_param import RealtimeMcphttpErrorParam +from .realtime_mcp_protocol_error_param import RealtimeMcpProtocolErrorParam +from .realtime_mcp_tool_execution_error_param import RealtimeMcpToolExecutionErrorParam + +__all__ = ["RealtimeMcpToolCallParam", "Error"] + +Error: TypeAlias = Union[RealtimeMcpProtocolErrorParam, RealtimeMcpToolExecutionErrorParam, RealtimeMcphttpErrorParam] + + +class RealtimeMcpToolCallParam(TypedDict, total=False): + """A Realtime item representing an invocation of a tool on an MCP server.""" + + id: Required[str] + """The unique ID of the tool call.""" + + arguments: Required[str] + """A JSON string of the arguments passed to the tool.""" + + name: Required[str] + """The name of the tool that was run.""" + + server_label: Required[str] + """The label of the MCP server running the tool.""" + + type: Required[Literal["mcp_call"]] + """The type of the item. Always `mcp_call`.""" + + approval_request_id: Optional[str] + """The ID of an associated approval request, if any.""" + + error: Optional[Error] + """The error from the tool call, if any.""" + + output: Optional[str] + """The output from the tool call.""" diff --git a/src/openai/types/realtime/realtime_mcp_tool_execution_error.py b/src/openai/types/realtime/realtime_mcp_tool_execution_error.py new file mode 100644 index 0000000000..a2ed063129 --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_tool_execution_error.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeMcpToolExecutionError"] + + +class RealtimeMcpToolExecutionError(BaseModel): + message: str + + type: Literal["tool_execution_error"] diff --git a/src/openai/types/realtime/realtime_mcp_tool_execution_error_param.py b/src/openai/types/realtime/realtime_mcp_tool_execution_error_param.py new file mode 100644 index 0000000000..619e11c305 --- /dev/null +++ b/src/openai/types/realtime/realtime_mcp_tool_execution_error_param.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeMcpToolExecutionErrorParam"] + + +class RealtimeMcpToolExecutionErrorParam(TypedDict, total=False): + message: Required[str] + + type: Required[Literal["tool_execution_error"]] diff --git a/src/openai/types/realtime/realtime_mcphttp_error.py b/src/openai/types/realtime/realtime_mcphttp_error.py new file mode 100644 index 0000000000..53cff91e6e --- /dev/null +++ b/src/openai/types/realtime/realtime_mcphttp_error.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeMcphttpError"] + + +class RealtimeMcphttpError(BaseModel): + code: int + + message: str + + type: Literal["http_error"] diff --git a/src/openai/types/realtime/realtime_mcphttp_error_param.py b/src/openai/types/realtime/realtime_mcphttp_error_param.py new file mode 100644 index 0000000000..2b80a6f0a4 --- /dev/null +++ b/src/openai/types/realtime/realtime_mcphttp_error_param.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeMcphttpErrorParam"] + + +class RealtimeMcphttpErrorParam(TypedDict, total=False): + code: Required[int] + + message: Required[str] + + type: Required[Literal["http_error"]] diff --git a/src/openai/types/realtime/realtime_reasoning.py b/src/openai/types/realtime/realtime_reasoning.py new file mode 100644 index 0000000000..5d49c9bd0b --- /dev/null +++ b/src/openai/types/realtime/realtime_reasoning.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .realtime_reasoning_effort import RealtimeReasoningEffort + +__all__ = ["RealtimeReasoning"] + + +class RealtimeReasoning(BaseModel): + """Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`.""" + + effort: Optional[RealtimeReasoningEffort] = None + """ + Constrains effort on reasoning for reasoning-capable Realtime models such as + `gpt-realtime-2`. + """ diff --git a/src/openai/types/realtime/realtime_reasoning_effort.py b/src/openai/types/realtime/realtime_reasoning_effort.py new file mode 100644 index 0000000000..dcf9e303a7 --- /dev/null +++ b/src/openai/types/realtime/realtime_reasoning_effort.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["RealtimeReasoningEffort"] + +RealtimeReasoningEffort: TypeAlias = Literal["minimal", "low", "medium", "high", "xhigh"] diff --git a/src/openai/types/realtime/realtime_reasoning_param.py b/src/openai/types/realtime/realtime_reasoning_param.py new file mode 100644 index 0000000000..f6de89c99a --- /dev/null +++ b/src/openai/types/realtime/realtime_reasoning_param.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +from .realtime_reasoning_effort import RealtimeReasoningEffort + +__all__ = ["RealtimeReasoningParam"] + + +class RealtimeReasoningParam(TypedDict, total=False): + """Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`.""" + + effort: RealtimeReasoningEffort + """ + Constrains effort on reasoning for reasoning-capable Realtime models such as + `gpt-realtime-2`. + """ diff --git a/src/openai/types/realtime/realtime_response.py b/src/openai/types/realtime/realtime_response.py new file mode 100644 index 0000000000..a23edc48ab --- /dev/null +++ b/src/openai/types/realtime/realtime_response.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from ..shared.metadata import Metadata +from .conversation_item import ConversationItem +from .realtime_audio_formats import RealtimeAudioFormats +from .realtime_response_usage import RealtimeResponseUsage +from .realtime_response_status import RealtimeResponseStatus + +__all__ = ["RealtimeResponse", "Audio", "AudioOutput"] + + +class AudioOutput(BaseModel): + format: Optional[RealtimeAudioFormats] = None + """The format of the output audio.""" + + voice: Union[ + str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"], None + ] = None + """The voice the model uses to respond. + + Voice cannot be changed during the session once the model has responded with + audio at least once. Current voice options are `alloy`, `ash`, `ballad`, + `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. We recommend + `marin` and `cedar` for best quality. + """ + + +class Audio(BaseModel): + """Configuration for audio output.""" + + output: Optional[AudioOutput] = None + + +class RealtimeResponse(BaseModel): + """The response resource.""" + + id: Optional[str] = None + """The unique ID of the response, will look like `resp_1234`.""" + + audio: Optional[Audio] = None + """Configuration for audio output.""" + + conversation_id: Optional[str] = None + """ + Which conversation the response is added to, determined by the `conversation` + field in the `response.create` event. If `auto`, the response will be added to + the default conversation and the value of `conversation_id` will be an id like + `conv_1234`. If `none`, the response will not be added to any conversation and + the value of `conversation_id` will be `null`. If responses are being triggered + automatically by VAD the response will be added to the default conversation + """ + + max_output_tokens: Union[int, Literal["inf"], None] = None + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls, that was used in this response. + """ + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + object: Optional[Literal["realtime.response"]] = None + """The object type, must be `realtime.response`.""" + + output: Optional[List[ConversationItem]] = None + """The list of output items generated by the response.""" + + output_modalities: Optional[List[Literal["text", "audio"]]] = None + """ + The set of modalities the model used to respond, currently the only possible + values are `[\"audio\"]`, `[\"text\"]`. Audio output always include a text + transcript. Setting the output to mode `text` will disable audio output from the + model. + """ + + status: Optional[Literal["completed", "cancelled", "failed", "incomplete", "in_progress"]] = None + """ + The final status of the response (`completed`, `cancelled`, `failed`, or + `incomplete`, `in_progress`). + """ + + status_details: Optional[RealtimeResponseStatus] = None + """Additional details about the status.""" + + usage: Optional[RealtimeResponseUsage] = None + """Usage statistics for the Response, this will correspond to billing. + + A Realtime API session will maintain a conversation context and append new Items + to the Conversation, thus output from previous turns (text and audio tokens) + will become the input for later turns. + """ diff --git a/src/openai/types/realtime/realtime_response_create_audio_output.py b/src/openai/types/realtime/realtime_response_create_audio_output.py new file mode 100644 index 0000000000..7848357236 --- /dev/null +++ b/src/openai/types/realtime/realtime_response_create_audio_output.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .realtime_audio_formats import RealtimeAudioFormats + +__all__ = ["RealtimeResponseCreateAudioOutput", "Output", "OutputVoice", "OutputVoiceID"] + + +class OutputVoiceID(BaseModel): + """Custom voice reference.""" + + id: str + """The custom voice ID, e.g. `voice_1234`.""" + + +OutputVoice: TypeAlias = Union[ + str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"], OutputVoiceID +] + + +class Output(BaseModel): + format: Optional[RealtimeAudioFormats] = None + """The format of the output audio.""" + + voice: Optional[OutputVoice] = None + """The voice the model uses to respond. + + Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, + `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice + object with an `id`, for example `{ "id": "voice_1234" }`. Voice cannot be + changed during the session once the model has responded with audio at least + once. We recommend `marin` and `cedar` for best quality. + """ + + +class RealtimeResponseCreateAudioOutput(BaseModel): + """Configuration for audio input and output.""" + + output: Optional[Output] = None diff --git a/src/openai/types/realtime/realtime_response_create_audio_output_param.py b/src/openai/types/realtime/realtime_response_create_audio_output_param.py new file mode 100644 index 0000000000..bb930f5488 --- /dev/null +++ b/src/openai/types/realtime/realtime_response_create_audio_output_param.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .realtime_audio_formats_param import RealtimeAudioFormatsParam + +__all__ = ["RealtimeResponseCreateAudioOutputParam", "Output", "OutputVoice", "OutputVoiceID"] + + +class OutputVoiceID(TypedDict, total=False): + """Custom voice reference.""" + + id: Required[str] + """The custom voice ID, e.g. `voice_1234`.""" + + +OutputVoice: TypeAlias = Union[ + str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"], OutputVoiceID +] + + +class Output(TypedDict, total=False): + format: RealtimeAudioFormatsParam + """The format of the output audio.""" + + voice: OutputVoice + """The voice the model uses to respond. + + Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, + `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice + object with an `id`, for example `{ "id": "voice_1234" }`. Voice cannot be + changed during the session once the model has responded with audio at least + once. We recommend `marin` and `cedar` for best quality. + """ + + +class RealtimeResponseCreateAudioOutputParam(TypedDict, total=False): + """Configuration for audio input and output.""" + + output: Output diff --git a/src/openai/types/realtime/realtime_response_create_mcp_tool.py b/src/openai/types/realtime/realtime_response_create_mcp_tool.py new file mode 100644 index 0000000000..cb5eae42d4 --- /dev/null +++ b/src/openai/types/realtime/realtime_response_create_mcp_tool.py @@ -0,0 +1,156 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel + +__all__ = [ + "RealtimeResponseCreateMcpTool", + "AllowedTools", + "AllowedToolsMcpToolFilter", + "RequireApproval", + "RequireApprovalMcpToolApprovalFilter", + "RequireApprovalMcpToolApprovalFilterAlways", + "RequireApprovalMcpToolApprovalFilterNever", +] + + +class AllowedToolsMcpToolFilter(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +AllowedTools: TypeAlias = Union[List[str], AllowedToolsMcpToolFilter, None] + + +class RequireApprovalMcpToolApprovalFilterAlways(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +class RequireApprovalMcpToolApprovalFilterNever(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +class RequireApprovalMcpToolApprovalFilter(BaseModel): + """Specify which of the MCP server's tools require approval. + + Can be + `always`, `never`, or a filter object associated with tools + that require approval. + """ + + always: Optional[RequireApprovalMcpToolApprovalFilterAlways] = None + """A filter object to specify which tools are allowed.""" + + never: Optional[RequireApprovalMcpToolApprovalFilterNever] = None + """A filter object to specify which tools are allowed.""" + + +RequireApproval: TypeAlias = Union[RequireApprovalMcpToolApprovalFilter, Literal["always", "never"], None] + + +class RealtimeResponseCreateMcpTool(BaseModel): + """ + Give the model access to additional tools via remote Model Context Protocol + (MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp). + """ + + server_label: str + """A label for this MCP server, used to identify it in tool calls.""" + + type: Literal["mcp"] + """The type of the MCP tool. Always `mcp`.""" + + allowed_tools: Optional[AllowedTools] = None + """List of allowed tool names or a filter object.""" + + authorization: Optional[str] = None + """ + An OAuth access token that can be used with a remote MCP server, either with a + custom MCP server URL or a service connector. Your application must handle the + OAuth authorization flow and provide the token here. + """ + + connector_id: Optional[ + Literal[ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint", + ] + ] = None + """Identifier for service connectors, like those available in ChatGPT. + + One of `server_url` or `connector_id` must be provided. Learn more about service + connectors + [here](https://platform.openai.com/docs/guides/tools-remote-mcp#connectors). + + Currently supported `connector_id` values are: + + - Dropbox: `connector_dropbox` + - Gmail: `connector_gmail` + - Google Calendar: `connector_googlecalendar` + - Google Drive: `connector_googledrive` + - Microsoft Teams: `connector_microsoftteams` + - Outlook Calendar: `connector_outlookcalendar` + - Outlook Email: `connector_outlookemail` + - SharePoint: `connector_sharepoint` + """ + + defer_loading: Optional[bool] = None + """Whether this MCP tool is deferred and discovered via tool search.""" + + headers: Optional[Dict[str, str]] = None + """Optional HTTP headers to send to the MCP server. + + Use for authentication or other purposes. + """ + + require_approval: Optional[RequireApproval] = None + """Specify which of the MCP server's tools require approval.""" + + server_description: Optional[str] = None + """Optional description of the MCP server, used to provide more context.""" + + server_url: Optional[str] = None + """The URL for the MCP server. + + One of `server_url` or `connector_id` must be provided. + """ diff --git a/src/openai/types/realtime/realtime_response_create_mcp_tool_param.py b/src/openai/types/realtime/realtime_response_create_mcp_tool_param.py new file mode 100644 index 0000000000..dd8c2e018f --- /dev/null +++ b/src/openai/types/realtime/realtime_response_create_mcp_tool_param.py @@ -0,0 +1,156 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr + +__all__ = [ + "RealtimeResponseCreateMcpToolParam", + "AllowedTools", + "AllowedToolsMcpToolFilter", + "RequireApproval", + "RequireApprovalMcpToolApprovalFilter", + "RequireApprovalMcpToolApprovalFilterAlways", + "RequireApprovalMcpToolApprovalFilterNever", +] + + +class AllowedToolsMcpToolFilter(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +AllowedTools: TypeAlias = Union[SequenceNotStr[str], AllowedToolsMcpToolFilter] + + +class RequireApprovalMcpToolApprovalFilterAlways(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +class RequireApprovalMcpToolApprovalFilterNever(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +class RequireApprovalMcpToolApprovalFilter(TypedDict, total=False): + """Specify which of the MCP server's tools require approval. + + Can be + `always`, `never`, or a filter object associated with tools + that require approval. + """ + + always: RequireApprovalMcpToolApprovalFilterAlways + """A filter object to specify which tools are allowed.""" + + never: RequireApprovalMcpToolApprovalFilterNever + """A filter object to specify which tools are allowed.""" + + +RequireApproval: TypeAlias = Union[RequireApprovalMcpToolApprovalFilter, Literal["always", "never"]] + + +class RealtimeResponseCreateMcpToolParam(TypedDict, total=False): + """ + Give the model access to additional tools via remote Model Context Protocol + (MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp). + """ + + server_label: Required[str] + """A label for this MCP server, used to identify it in tool calls.""" + + type: Required[Literal["mcp"]] + """The type of the MCP tool. Always `mcp`.""" + + allowed_tools: Optional[AllowedTools] + """List of allowed tool names or a filter object.""" + + authorization: str + """ + An OAuth access token that can be used with a remote MCP server, either with a + custom MCP server URL or a service connector. Your application must handle the + OAuth authorization flow and provide the token here. + """ + + connector_id: Literal[ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint", + ] + """Identifier for service connectors, like those available in ChatGPT. + + One of `server_url` or `connector_id` must be provided. Learn more about service + connectors + [here](https://platform.openai.com/docs/guides/tools-remote-mcp#connectors). + + Currently supported `connector_id` values are: + + - Dropbox: `connector_dropbox` + - Gmail: `connector_gmail` + - Google Calendar: `connector_googlecalendar` + - Google Drive: `connector_googledrive` + - Microsoft Teams: `connector_microsoftteams` + - Outlook Calendar: `connector_outlookcalendar` + - Outlook Email: `connector_outlookemail` + - SharePoint: `connector_sharepoint` + """ + + defer_loading: bool + """Whether this MCP tool is deferred and discovered via tool search.""" + + headers: Optional[Dict[str, str]] + """Optional HTTP headers to send to the MCP server. + + Use for authentication or other purposes. + """ + + require_approval: Optional[RequireApproval] + """Specify which of the MCP server's tools require approval.""" + + server_description: str + """Optional description of the MCP server, used to provide more context.""" + + server_url: str + """The URL for the MCP server. + + One of `server_url` or `connector_id` must be provided. + """ diff --git a/src/openai/types/realtime/realtime_response_create_params.py b/src/openai/types/realtime/realtime_response_create_params.py new file mode 100644 index 0000000000..8e20168e34 --- /dev/null +++ b/src/openai/types/realtime/realtime_response_create_params.py @@ -0,0 +1,110 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from ..shared.metadata import Metadata +from .conversation_item import ConversationItem +from .realtime_reasoning import RealtimeReasoning +from .realtime_function_tool import RealtimeFunctionTool +from ..responses.response_prompt import ResponsePrompt +from ..responses.tool_choice_mcp import ToolChoiceMcp +from ..responses.tool_choice_options import ToolChoiceOptions +from ..responses.tool_choice_function import ToolChoiceFunction +from .realtime_response_create_mcp_tool import RealtimeResponseCreateMcpTool +from .realtime_response_create_audio_output import RealtimeResponseCreateAudioOutput + +__all__ = ["RealtimeResponseCreateParams", "ToolChoice", "Tool"] + +ToolChoice: TypeAlias = Union[ToolChoiceOptions, ToolChoiceFunction, ToolChoiceMcp] + +Tool: TypeAlias = Union[RealtimeFunctionTool, RealtimeResponseCreateMcpTool] + + +class RealtimeResponseCreateParams(BaseModel): + """Create a new Realtime response with these parameters""" + + audio: Optional[RealtimeResponseCreateAudioOutput] = None + """Configuration for audio input and output.""" + + conversation: Union[str, Literal["auto", "none"], None] = None + """Controls which conversation the response is added to. + + Currently supports `auto` and `none`, with `auto` as the default value. The + `auto` value means that the contents of the response will be added to the + default conversation. Set this to `none` to create an out-of-band response which + will not add items to default conversation. + """ + + input: Optional[List[ConversationItem]] = None + """Input items to include in the prompt for the model. + + Using this field creates a new context for this Response instead of using the + default conversation. An empty array `[]` will clear the context for this + Response. Note that this can include references to items that previously + appeared in the session using their id. + """ + + instructions: Optional[str] = None + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. Note that the server sets default instructions which will be used if + this field is not set and are visible in the `session.created` event at the + start of the session. + """ + + max_output_tokens: Union[int, Literal["inf"], None] = None + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + output_modalities: Optional[List[Literal["text", "audio"]]] = None + """ + The set of modalities the model used to respond, currently the only possible + values are `[\"audio\"]`, `[\"text\"]`. Audio output always include a text + transcript. Setting the output to mode `text` will disable audio output from the + model. + """ + + parallel_tool_calls: Optional[bool] = None + """Whether the model may call multiple tools in parallel. + + Only supported by reasoning Realtime models such as `gpt-realtime-2`. + """ + + prompt: Optional[ResponsePrompt] = None + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + reasoning: Optional[RealtimeReasoning] = None + """Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`.""" + + tool_choice: Optional[ToolChoice] = None + """How the model chooses tools. + + Provide one of the string modes or force a specific function/MCP tool. + """ + + tools: Optional[List[Tool]] = None + """Tools available to the model.""" diff --git a/src/openai/types/realtime/realtime_response_create_params_param.py b/src/openai/types/realtime/realtime_response_create_params_param.py new file mode 100644 index 0000000000..aafa3d4e25 --- /dev/null +++ b/src/openai/types/realtime/realtime_response_create_params_param.py @@ -0,0 +1,111 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable, Optional +from typing_extensions import Literal, TypeAlias, TypedDict + +from ..shared_params.metadata import Metadata +from .conversation_item_param import ConversationItemParam +from .realtime_reasoning_param import RealtimeReasoningParam +from .realtime_function_tool_param import RealtimeFunctionToolParam +from ..responses.tool_choice_options import ToolChoiceOptions +from ..responses.response_prompt_param import ResponsePromptParam +from ..responses.tool_choice_mcp_param import ToolChoiceMcpParam +from ..responses.tool_choice_function_param import ToolChoiceFunctionParam +from .realtime_response_create_mcp_tool_param import RealtimeResponseCreateMcpToolParam +from .realtime_response_create_audio_output_param import RealtimeResponseCreateAudioOutputParam + +__all__ = ["RealtimeResponseCreateParamsParam", "ToolChoice", "Tool"] + +ToolChoice: TypeAlias = Union[ToolChoiceOptions, ToolChoiceFunctionParam, ToolChoiceMcpParam] + +Tool: TypeAlias = Union[RealtimeFunctionToolParam, RealtimeResponseCreateMcpToolParam] + + +class RealtimeResponseCreateParamsParam(TypedDict, total=False): + """Create a new Realtime response with these parameters""" + + audio: RealtimeResponseCreateAudioOutputParam + """Configuration for audio input and output.""" + + conversation: Union[str, Literal["auto", "none"]] + """Controls which conversation the response is added to. + + Currently supports `auto` and `none`, with `auto` as the default value. The + `auto` value means that the contents of the response will be added to the + default conversation. Set this to `none` to create an out-of-band response which + will not add items to default conversation. + """ + + input: Iterable[ConversationItemParam] + """Input items to include in the prompt for the model. + + Using this field creates a new context for this Response instead of using the + default conversation. An empty array `[]` will clear the context for this + Response. Note that this can include references to items that previously + appeared in the session using their id. + """ + + instructions: str + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. Note that the server sets default instructions which will be used if + this field is not set and are visible in the `session.created` event at the + start of the session. + """ + + max_output_tokens: Union[int, Literal["inf"]] + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + output_modalities: List[Literal["text", "audio"]] + """ + The set of modalities the model used to respond, currently the only possible + values are `[\"audio\"]`, `[\"text\"]`. Audio output always include a text + transcript. Setting the output to mode `text` will disable audio output from the + model. + """ + + parallel_tool_calls: bool + """Whether the model may call multiple tools in parallel. + + Only supported by reasoning Realtime models such as `gpt-realtime-2`. + """ + + prompt: Optional[ResponsePromptParam] + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + reasoning: RealtimeReasoningParam + """Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`.""" + + tool_choice: ToolChoice + """How the model chooses tools. + + Provide one of the string modes or force a specific function/MCP tool. + """ + + tools: Iterable[Tool] + """Tools available to the model.""" diff --git a/src/openai/types/realtime/realtime_response_status.py b/src/openai/types/realtime/realtime_response_status.py new file mode 100644 index 0000000000..26b272ae5a --- /dev/null +++ b/src/openai/types/realtime/realtime_response_status.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeResponseStatus", "Error"] + + +class Error(BaseModel): + """ + A description of the error that caused the response to fail, + populated when the `status` is `failed`. + """ + + code: Optional[str] = None + """Error code, if any.""" + + type: Optional[str] = None + """The type of error.""" + + +class RealtimeResponseStatus(BaseModel): + """Additional details about the status.""" + + error: Optional[Error] = None + """ + A description of the error that caused the response to fail, populated when the + `status` is `failed`. + """ + + reason: Optional[Literal["turn_detected", "client_cancelled", "max_output_tokens", "content_filter"]] = None + """The reason the Response did not complete. + + For a `cancelled` Response, one of `turn_detected` (the server VAD detected a + new start of speech) or `client_cancelled` (the client sent a cancel event). For + an `incomplete` Response, one of `max_output_tokens` or `content_filter` (the + server-side safety filter activated and cut off the response). + """ + + type: Optional[Literal["completed", "cancelled", "incomplete", "failed"]] = None + """ + The type of error that caused the response to fail, corresponding with the + `status` field (`completed`, `cancelled`, `incomplete`, `failed`). + """ diff --git a/src/openai/types/realtime/realtime_response_usage.py b/src/openai/types/realtime/realtime_response_usage.py new file mode 100644 index 0000000000..a5985d8a7b --- /dev/null +++ b/src/openai/types/realtime/realtime_response_usage.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .realtime_response_usage_input_token_details import RealtimeResponseUsageInputTokenDetails +from .realtime_response_usage_output_token_details import RealtimeResponseUsageOutputTokenDetails + +__all__ = ["RealtimeResponseUsage"] + + +class RealtimeResponseUsage(BaseModel): + """Usage statistics for the Response, this will correspond to billing. + + A + Realtime API session will maintain a conversation context and append new + Items to the Conversation, thus output from previous turns (text and + audio tokens) will become the input for later turns. + """ + + input_token_details: Optional[RealtimeResponseUsageInputTokenDetails] = None + """Details about the input tokens used in the Response. + + Cached tokens are tokens from previous turns in the conversation that are + included as context for the current response. Cached tokens here are counted as + a subset of input tokens, meaning input tokens will include cached and uncached + tokens. + """ + + input_tokens: Optional[int] = None + """ + The number of input tokens used in the Response, including text and audio + tokens. + """ + + output_token_details: Optional[RealtimeResponseUsageOutputTokenDetails] = None + """Details about the output tokens used in the Response.""" + + output_tokens: Optional[int] = None + """ + The number of output tokens sent in the Response, including text and audio + tokens. + """ + + total_tokens: Optional[int] = None + """ + The total number of tokens in the Response including input and output text and + audio tokens. + """ diff --git a/src/openai/types/realtime/realtime_response_usage_input_token_details.py b/src/openai/types/realtime/realtime_response_usage_input_token_details.py new file mode 100644 index 0000000000..0fc71749e9 --- /dev/null +++ b/src/openai/types/realtime/realtime_response_usage_input_token_details.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["RealtimeResponseUsageInputTokenDetails", "CachedTokensDetails"] + + +class CachedTokensDetails(BaseModel): + """Details about the cached tokens used as input for the Response.""" + + audio_tokens: Optional[int] = None + """The number of cached audio tokens used as input for the Response.""" + + image_tokens: Optional[int] = None + """The number of cached image tokens used as input for the Response.""" + + text_tokens: Optional[int] = None + """The number of cached text tokens used as input for the Response.""" + + +class RealtimeResponseUsageInputTokenDetails(BaseModel): + """Details about the input tokens used in the Response. + + Cached tokens are tokens from previous turns in the conversation that are included as context for the current response. Cached tokens here are counted as a subset of input tokens, meaning input tokens will include cached and uncached tokens. + """ + + audio_tokens: Optional[int] = None + """The number of audio tokens used as input for the Response.""" + + cached_tokens: Optional[int] = None + """The number of cached tokens used as input for the Response.""" + + cached_tokens_details: Optional[CachedTokensDetails] = None + """Details about the cached tokens used as input for the Response.""" + + image_tokens: Optional[int] = None + """The number of image tokens used as input for the Response.""" + + text_tokens: Optional[int] = None + """The number of text tokens used as input for the Response.""" diff --git a/src/openai/types/realtime/realtime_response_usage_output_token_details.py b/src/openai/types/realtime/realtime_response_usage_output_token_details.py new file mode 100644 index 0000000000..2154c77d5d --- /dev/null +++ b/src/openai/types/realtime/realtime_response_usage_output_token_details.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["RealtimeResponseUsageOutputTokenDetails"] + + +class RealtimeResponseUsageOutputTokenDetails(BaseModel): + """Details about the output tokens used in the Response.""" + + audio_tokens: Optional[int] = None + """The number of audio tokens used in the Response.""" + + text_tokens: Optional[int] = None + """The number of text tokens used in the Response.""" diff --git a/src/openai/types/realtime/realtime_server_event.py b/src/openai/types/realtime/realtime_server_event.py new file mode 100644 index 0000000000..5de53d053e --- /dev/null +++ b/src/openai/types/realtime/realtime_server_event.py @@ -0,0 +1,185 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .conversation_item import ConversationItem +from .response_done_event import ResponseDoneEvent +from .realtime_error_event import RealtimeErrorEvent +from .mcp_list_tools_failed import McpListToolsFailed +from .session_created_event import SessionCreatedEvent +from .session_updated_event import SessionUpdatedEvent +from .conversation_item_done import ConversationItemDone +from .response_created_event import ResponseCreatedEvent +from .conversation_item_added import ConversationItemAdded +from .mcp_list_tools_completed import McpListToolsCompleted +from .response_mcp_call_failed import ResponseMcpCallFailed +from .response_text_done_event import ResponseTextDoneEvent +from .rate_limits_updated_event import RateLimitsUpdatedEvent +from .response_audio_done_event import ResponseAudioDoneEvent +from .response_text_delta_event import ResponseTextDeltaEvent +from .conversation_created_event import ConversationCreatedEvent +from .mcp_list_tools_in_progress import McpListToolsInProgress +from .response_audio_delta_event import ResponseAudioDeltaEvent +from .response_mcp_call_completed import ResponseMcpCallCompleted +from .response_mcp_call_in_progress import ResponseMcpCallInProgress +from .conversation_item_created_event import ConversationItemCreatedEvent +from .conversation_item_deleted_event import ConversationItemDeletedEvent +from .response_output_item_done_event import ResponseOutputItemDoneEvent +from .input_audio_buffer_cleared_event import InputAudioBufferClearedEvent +from .response_content_part_done_event import ResponseContentPartDoneEvent +from .response_mcp_call_arguments_done import ResponseMcpCallArgumentsDone +from .response_output_item_added_event import ResponseOutputItemAddedEvent +from .conversation_item_truncated_event import ConversationItemTruncatedEvent +from .response_content_part_added_event import ResponseContentPartAddedEvent +from .response_mcp_call_arguments_delta import ResponseMcpCallArgumentsDelta +from .input_audio_buffer_committed_event import InputAudioBufferCommittedEvent +from .input_audio_buffer_timeout_triggered import InputAudioBufferTimeoutTriggered +from .response_audio_transcript_done_event import ResponseAudioTranscriptDoneEvent +from .response_audio_transcript_delta_event import ResponseAudioTranscriptDeltaEvent +from .input_audio_buffer_speech_started_event import InputAudioBufferSpeechStartedEvent +from .input_audio_buffer_speech_stopped_event import InputAudioBufferSpeechStoppedEvent +from .response_function_call_arguments_done_event import ResponseFunctionCallArgumentsDoneEvent +from .input_audio_buffer_dtmf_event_received_event import InputAudioBufferDtmfEventReceivedEvent +from .response_function_call_arguments_delta_event import ResponseFunctionCallArgumentsDeltaEvent +from .conversation_item_input_audio_transcription_segment import ConversationItemInputAudioTranscriptionSegment +from .conversation_item_input_audio_transcription_delta_event import ConversationItemInputAudioTranscriptionDeltaEvent +from .conversation_item_input_audio_transcription_failed_event import ConversationItemInputAudioTranscriptionFailedEvent +from .conversation_item_input_audio_transcription_completed_event import ( + ConversationItemInputAudioTranscriptionCompletedEvent, +) + +__all__ = [ + "RealtimeServerEvent", + "ConversationItemRetrieved", + "OutputAudioBufferStarted", + "OutputAudioBufferStopped", + "OutputAudioBufferCleared", +] + + +class ConversationItemRetrieved(BaseModel): + """Returned when a conversation item is retrieved with `conversation.item.retrieve`. + + This is provided as a way to fetch the server's representation of an item, for example to get access to the post-processed audio data after noise cancellation and VAD. It includes the full content of the Item, including audio data. + """ + + event_id: str + """The unique ID of the server event.""" + + item: ConversationItem + """A single item within a Realtime conversation.""" + + type: Literal["conversation.item.retrieved"] + """The event type, must be `conversation.item.retrieved`.""" + + +class OutputAudioBufferStarted(BaseModel): + """ + **WebRTC/SIP Only:** Emitted when the server begins streaming audio to the client. This event is + emitted after an audio content part has been added (`response.content_part.added`) + to the response. + [Learn more](https://platform.openai.com/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc). + """ + + event_id: str + """The unique ID of the server event.""" + + response_id: str + """The unique ID of the response that produced the audio.""" + + type: Literal["output_audio_buffer.started"] + """The event type, must be `output_audio_buffer.started`.""" + + +class OutputAudioBufferStopped(BaseModel): + """ + **WebRTC/SIP Only:** Emitted when the output audio buffer has been completely drained on the server, + and no more audio is forthcoming. This event is emitted after the full response + data has been sent to the client (`response.done`). + [Learn more](https://platform.openai.com/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc). + """ + + event_id: str + """The unique ID of the server event.""" + + response_id: str + """The unique ID of the response that produced the audio.""" + + type: Literal["output_audio_buffer.stopped"] + """The event type, must be `output_audio_buffer.stopped`.""" + + +class OutputAudioBufferCleared(BaseModel): + """**WebRTC/SIP Only:** Emitted when the output audio buffer is cleared. + + This happens either in VAD + mode when the user has interrupted (`input_audio_buffer.speech_started`), + or when the client has emitted the `output_audio_buffer.clear` event to manually + cut off the current audio response. + [Learn more](https://platform.openai.com/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc). + """ + + event_id: str + """The unique ID of the server event.""" + + response_id: str + """The unique ID of the response that produced the audio.""" + + type: Literal["output_audio_buffer.cleared"] + """The event type, must be `output_audio_buffer.cleared`.""" + + +RealtimeServerEvent: TypeAlias = Annotated[ + Union[ + ConversationCreatedEvent, + ConversationItemCreatedEvent, + ConversationItemDeletedEvent, + ConversationItemInputAudioTranscriptionCompletedEvent, + ConversationItemInputAudioTranscriptionDeltaEvent, + ConversationItemInputAudioTranscriptionFailedEvent, + ConversationItemRetrieved, + ConversationItemTruncatedEvent, + RealtimeErrorEvent, + InputAudioBufferClearedEvent, + InputAudioBufferCommittedEvent, + InputAudioBufferDtmfEventReceivedEvent, + InputAudioBufferSpeechStartedEvent, + InputAudioBufferSpeechStoppedEvent, + RateLimitsUpdatedEvent, + ResponseAudioDeltaEvent, + ResponseAudioDoneEvent, + ResponseAudioTranscriptDeltaEvent, + ResponseAudioTranscriptDoneEvent, + ResponseContentPartAddedEvent, + ResponseContentPartDoneEvent, + ResponseCreatedEvent, + ResponseDoneEvent, + ResponseFunctionCallArgumentsDeltaEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseOutputItemAddedEvent, + ResponseOutputItemDoneEvent, + ResponseTextDeltaEvent, + ResponseTextDoneEvent, + SessionCreatedEvent, + SessionUpdatedEvent, + OutputAudioBufferStarted, + OutputAudioBufferStopped, + OutputAudioBufferCleared, + ConversationItemAdded, + ConversationItemDone, + InputAudioBufferTimeoutTriggered, + ConversationItemInputAudioTranscriptionSegment, + McpListToolsInProgress, + McpListToolsCompleted, + McpListToolsFailed, + ResponseMcpCallArgumentsDelta, + ResponseMcpCallArgumentsDone, + ResponseMcpCallInProgress, + ResponseMcpCallCompleted, + ResponseMcpCallFailed, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/realtime/realtime_session_create_request.py b/src/openai/types/realtime/realtime_session_create_request.py new file mode 100644 index 0000000000..cf681e99a1 --- /dev/null +++ b/src/openai/types/realtime/realtime_session_create_request.py @@ -0,0 +1,145 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .realtime_reasoning import RealtimeReasoning +from .realtime_truncation import RealtimeTruncation +from .realtime_audio_config import RealtimeAudioConfig +from .realtime_tools_config import RealtimeToolsConfig +from .realtime_tracing_config import RealtimeTracingConfig +from ..responses.response_prompt import ResponsePrompt +from .realtime_tool_choice_config import RealtimeToolChoiceConfig + +__all__ = ["RealtimeSessionCreateRequest"] + + +class RealtimeSessionCreateRequest(BaseModel): + """Realtime session object configuration.""" + + type: Literal["realtime"] + """The type of session to create. Always `realtime` for the Realtime API.""" + + audio: Optional[RealtimeAudioConfig] = None + """Configuration for input and output audio.""" + + include: Optional[List[Literal["item.input_audio_transcription.logprobs"]]] = None + """Additional fields to include in server outputs. + + `item.input_audio_transcription.logprobs`: Include logprobs for input audio + transcription. + """ + + instructions: Optional[str] = None + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_output_tokens: Union[int, Literal["inf"], None] = None + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + model: Union[ + str, + Literal[ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15", + ], + None, + ] = None + """The Realtime model used for this session.""" + + output_modalities: Optional[List[Literal["text", "audio"]]] = None + """The set of modalities the model can respond with. + + It defaults to `["audio"]`, indicating that the model will respond with audio + plus a transcript. `["text"]` can be used to make the model respond with text + only. It is not possible to request both `text` and `audio` at the same time. + """ + + parallel_tool_calls: Optional[bool] = None + """Whether the model may call multiple tools in parallel. + + Only supported by reasoning Realtime models such as `gpt-realtime-2`. + """ + + prompt: Optional[ResponsePrompt] = None + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + reasoning: Optional[RealtimeReasoning] = None + """Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`.""" + + tool_choice: Optional[RealtimeToolChoiceConfig] = None + """How the model chooses tools. + + Provide one of the string modes or force a specific function/MCP tool. + """ + + tools: Optional[RealtimeToolsConfig] = None + """Tools available to the model.""" + + tracing: Optional[RealtimeTracingConfig] = None + """ + Realtime API can write session traces to the + [Traces Dashboard](https://platform.openai.com/logs?api=traces). Set to null to + disable tracing. Once tracing is enabled for a session, the configuration cannot + be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + """ + + truncation: Optional[RealtimeTruncation] = None + """ + When the number of tokens in a conversation exceeds the model's input token + limit, the conversation be truncated, meaning messages (starting from the + oldest) will not be included in the model's context. A 32k context model with + 4,096 max output tokens can only include 28,224 tokens in the context before + truncation occurs. + + Clients can configure truncation behavior to truncate with a lower max token + limit, which is an effective way to control token usage and cost. + + Truncation will reduce the number of cached tokens on the next turn (busting the + cache), since messages are dropped from the beginning of the context. However, + clients can also configure truncation to retain messages up to a fraction of the + maximum context size, which will reduce the need for future truncations and thus + improve the cache rate. + + Truncation can be disabled entirely, which means the server will never truncate + but would instead return an error if the conversation exceeds the model's input + token limit. + """ diff --git a/src/openai/types/realtime/realtime_session_create_request_param.py b/src/openai/types/realtime/realtime_session_create_request_param.py new file mode 100644 index 0000000000..ab7de47c3c --- /dev/null +++ b/src/openai/types/realtime/realtime_session_create_request_param.py @@ -0,0 +1,145 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from typing_extensions import Literal, Required, TypedDict + +from .realtime_reasoning_param import RealtimeReasoningParam +from .realtime_truncation_param import RealtimeTruncationParam +from .realtime_audio_config_param import RealtimeAudioConfigParam +from .realtime_tools_config_param import RealtimeToolsConfigParam +from .realtime_tracing_config_param import RealtimeTracingConfigParam +from ..responses.response_prompt_param import ResponsePromptParam +from .realtime_tool_choice_config_param import RealtimeToolChoiceConfigParam + +__all__ = ["RealtimeSessionCreateRequestParam"] + + +class RealtimeSessionCreateRequestParam(TypedDict, total=False): + """Realtime session object configuration.""" + + type: Required[Literal["realtime"]] + """The type of session to create. Always `realtime` for the Realtime API.""" + + audio: RealtimeAudioConfigParam + """Configuration for input and output audio.""" + + include: List[Literal["item.input_audio_transcription.logprobs"]] + """Additional fields to include in server outputs. + + `item.input_audio_transcription.logprobs`: Include logprobs for input audio + transcription. + """ + + instructions: str + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_output_tokens: Union[int, Literal["inf"]] + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + model: Union[ + str, + Literal[ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15", + ], + ] + """The Realtime model used for this session.""" + + output_modalities: List[Literal["text", "audio"]] + """The set of modalities the model can respond with. + + It defaults to `["audio"]`, indicating that the model will respond with audio + plus a transcript. `["text"]` can be used to make the model respond with text + only. It is not possible to request both `text` and `audio` at the same time. + """ + + parallel_tool_calls: bool + """Whether the model may call multiple tools in parallel. + + Only supported by reasoning Realtime models such as `gpt-realtime-2`. + """ + + prompt: Optional[ResponsePromptParam] + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + reasoning: RealtimeReasoningParam + """Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`.""" + + tool_choice: RealtimeToolChoiceConfigParam + """How the model chooses tools. + + Provide one of the string modes or force a specific function/MCP tool. + """ + + tools: RealtimeToolsConfigParam + """Tools available to the model.""" + + tracing: Optional[RealtimeTracingConfigParam] + """ + Realtime API can write session traces to the + [Traces Dashboard](https://platform.openai.com/logs?api=traces). Set to null to + disable tracing. Once tracing is enabled for a session, the configuration cannot + be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + """ + + truncation: RealtimeTruncationParam + """ + When the number of tokens in a conversation exceeds the model's input token + limit, the conversation be truncated, meaning messages (starting from the + oldest) will not be included in the model's context. A 32k context model with + 4,096 max output tokens can only include 28,224 tokens in the context before + truncation occurs. + + Clients can configure truncation behavior to truncate with a lower max token + limit, which is an effective way to control token usage and cost. + + Truncation will reduce the number of cached tokens on the next turn (busting the + cache), since messages are dropped from the beginning of the context. However, + clients can also configure truncation to retain messages up to a fraction of the + maximum context size, which will reduce the need for future truncations and thus + improve the cache rate. + + Truncation can be disabled entirely, which means the server will never truncate + but would instead return an error if the conversation exceeds the model's input + token limit. + """ diff --git a/src/openai/types/realtime/realtime_session_create_response.py b/src/openai/types/realtime/realtime_session_create_response.py new file mode 100644 index 0000000000..7193eafcc1 --- /dev/null +++ b/src/openai/types/realtime/realtime_session_create_response.py @@ -0,0 +1,539 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .realtime_reasoning import RealtimeReasoning +from .audio_transcription import AudioTranscription +from .realtime_truncation import RealtimeTruncation +from .noise_reduction_type import NoiseReductionType +from .realtime_audio_formats import RealtimeAudioFormats +from .realtime_function_tool import RealtimeFunctionTool +from ..responses.response_prompt import ResponsePrompt +from ..responses.tool_choice_mcp import ToolChoiceMcp +from ..responses.tool_choice_options import ToolChoiceOptions +from ..responses.tool_choice_function import ToolChoiceFunction + +__all__ = [ + "RealtimeSessionCreateResponse", + "Audio", + "AudioInput", + "AudioInputNoiseReduction", + "AudioInputTurnDetection", + "AudioInputTurnDetectionServerVad", + "AudioInputTurnDetectionSemanticVad", + "AudioOutput", + "ToolChoice", + "Tool", + "ToolMcpTool", + "ToolMcpToolAllowedTools", + "ToolMcpToolAllowedToolsMcpToolFilter", + "ToolMcpToolRequireApproval", + "ToolMcpToolRequireApprovalMcpToolApprovalFilter", + "ToolMcpToolRequireApprovalMcpToolApprovalFilterAlways", + "ToolMcpToolRequireApprovalMcpToolApprovalFilterNever", + "Tracing", + "TracingTracingConfiguration", +] + + +class AudioInputNoiseReduction(BaseModel): + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. + Noise reduction filters audio added to the input audio buffer before it is sent to VAD and the model. + Filtering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio. + """ + + type: Optional[NoiseReductionType] = None + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class AudioInputTurnDetectionServerVad(BaseModel): + """ + Server-side voice activity detection (VAD) which flips on when user speech is detected and off after a period of silence. + """ + + type: Literal["server_vad"] + """Type of turn detection, `server_vad` to turn on simple Server VAD.""" + + create_response: Optional[bool] = None + """Whether or not to automatically generate a response when a VAD stop event + occurs. + + If `interrupt_response` is set to `false` this may fail to create a response if + the model is already responding. + + If both `create_response` and `interrupt_response` are set to `false`, the model + will never respond automatically but VAD events will still be emitted. + """ + + idle_timeout_ms: Optional[int] = None + """Optional timeout after which a model response will be triggered automatically. + + This is useful for situations in which a long pause from the user is unexpected, + such as a phone call. The model will effectively prompt the user to continue the + conversation based on the current context. + + The timeout value will be applied after the last model response's audio has + finished playing, i.e. it's set to the `response.done` time plus audio playback + duration. + + An `input_audio_buffer.timeout_triggered` event (plus events associated with the + Response) will be emitted when the timeout is reached. Idle timeout is currently + only supported for `server_vad` mode. + """ + + interrupt_response: Optional[bool] = None + """ + Whether or not to automatically interrupt (cancel) any ongoing response with + output to the default conversation (i.e. `conversation` of `auto`) when a VAD + start event occurs. If `true` then the response will be cancelled, otherwise it + will continue until complete. + + If both `create_response` and `interrupt_response` are set to `false`, the model + will never respond automatically but VAD events will still be emitted. + """ + + prefix_padding_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: Optional[float] = None + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + +class AudioInputTurnDetectionSemanticVad(BaseModel): + """ + Server-side semantic turn detection which uses a model to determine when the user has finished speaking. + """ + + type: Literal["semantic_vad"] + """Type of turn detection, `semantic_vad` to turn on Semantic VAD.""" + + create_response: Optional[bool] = None + """ + Whether or not to automatically generate a response when a VAD stop event + occurs. + """ + + eagerness: Optional[Literal["low", "medium", "high", "auto"]] = None + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. `low`, `medium`, and `high` have max timeouts of 8s, + 4s, and 2s respectively. + """ + + interrupt_response: Optional[bool] = None + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. + """ + + +AudioInputTurnDetection: TypeAlias = Annotated[ + Union[AudioInputTurnDetectionServerVad, AudioInputTurnDetectionSemanticVad, None], + PropertyInfo(discriminator="type"), +] + + +class AudioInput(BaseModel): + format: Optional[RealtimeAudioFormats] = None + """The format of the input audio.""" + + noise_reduction: Optional[AudioInputNoiseReduction] = None + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + transcription: Optional[AudioTranscription] = None + + turn_detection: Optional[AudioInputTurnDetection] = None + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. + + Server VAD means that the model will detect the start and end of speech based on + audio volume and respond at the end of user speech. + + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + + For `gpt-realtime-whisper` transcription sessions, turn detection must be set to + `null`; VAD is not supported. + """ + + +class AudioOutput(BaseModel): + format: Optional[RealtimeAudioFormats] = None + """The format of the output audio.""" + + speed: Optional[float] = None + """ + The speed of the model's spoken response as a multiple of the original speed. + 1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. + This value can only be changed in between model turns, not while a response is + in progress. + + This parameter is a post-processing adjustment to the audio after it is + generated, it's also possible to prompt the model to speak faster or slower. + """ + + voice: Union[ + str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"], None + ] = None + """The voice the model uses to respond. + + Voice cannot be changed during the session once the model has responded with + audio at least once. Current voice options are `alloy`, `ash`, `ballad`, + `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. We recommend + `marin` and `cedar` for best quality. + """ + + +class Audio(BaseModel): + """Configuration for input and output audio.""" + + input: Optional[AudioInput] = None + + output: Optional[AudioOutput] = None + + +ToolChoice: TypeAlias = Union[ToolChoiceOptions, ToolChoiceFunction, ToolChoiceMcp] + + +class ToolMcpToolAllowedToolsMcpToolFilter(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +ToolMcpToolAllowedTools: TypeAlias = Union[List[str], ToolMcpToolAllowedToolsMcpToolFilter, None] + + +class ToolMcpToolRequireApprovalMcpToolApprovalFilterAlways(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +class ToolMcpToolRequireApprovalMcpToolApprovalFilterNever(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +class ToolMcpToolRequireApprovalMcpToolApprovalFilter(BaseModel): + """Specify which of the MCP server's tools require approval. + + Can be + `always`, `never`, or a filter object associated with tools + that require approval. + """ + + always: Optional[ToolMcpToolRequireApprovalMcpToolApprovalFilterAlways] = None + """A filter object to specify which tools are allowed.""" + + never: Optional[ToolMcpToolRequireApprovalMcpToolApprovalFilterNever] = None + """A filter object to specify which tools are allowed.""" + + +ToolMcpToolRequireApproval: TypeAlias = Union[ + ToolMcpToolRequireApprovalMcpToolApprovalFilter, Literal["always", "never"], None +] + + +class ToolMcpTool(BaseModel): + """ + Give the model access to additional tools via remote Model Context Protocol + (MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp). + """ + + server_label: str + """A label for this MCP server, used to identify it in tool calls.""" + + type: Literal["mcp"] + """The type of the MCP tool. Always `mcp`.""" + + allowed_tools: Optional[ToolMcpToolAllowedTools] = None + """List of allowed tool names or a filter object.""" + + authorization: Optional[str] = None + """ + An OAuth access token that can be used with a remote MCP server, either with a + custom MCP server URL or a service connector. Your application must handle the + OAuth authorization flow and provide the token here. + """ + + connector_id: Optional[ + Literal[ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint", + ] + ] = None + """Identifier for service connectors, like those available in ChatGPT. + + One of `server_url` or `connector_id` must be provided. Learn more about service + connectors + [here](https://platform.openai.com/docs/guides/tools-remote-mcp#connectors). + + Currently supported `connector_id` values are: + + - Dropbox: `connector_dropbox` + - Gmail: `connector_gmail` + - Google Calendar: `connector_googlecalendar` + - Google Drive: `connector_googledrive` + - Microsoft Teams: `connector_microsoftteams` + - Outlook Calendar: `connector_outlookcalendar` + - Outlook Email: `connector_outlookemail` + - SharePoint: `connector_sharepoint` + """ + + defer_loading: Optional[bool] = None + """Whether this MCP tool is deferred and discovered via tool search.""" + + headers: Optional[Dict[str, str]] = None + """Optional HTTP headers to send to the MCP server. + + Use for authentication or other purposes. + """ + + require_approval: Optional[ToolMcpToolRequireApproval] = None + """Specify which of the MCP server's tools require approval.""" + + server_description: Optional[str] = None + """Optional description of the MCP server, used to provide more context.""" + + server_url: Optional[str] = None + """The URL for the MCP server. + + One of `server_url` or `connector_id` must be provided. + """ + + +Tool: TypeAlias = Union[RealtimeFunctionTool, ToolMcpTool] + + +class TracingTracingConfiguration(BaseModel): + """Granular configuration for tracing.""" + + group_id: Optional[str] = None + """ + The group id to attach to this trace to enable filtering and grouping in the + Traces Dashboard. + """ + + metadata: Optional[object] = None + """ + The arbitrary metadata to attach to this trace to enable filtering in the Traces + Dashboard. + """ + + workflow_name: Optional[str] = None + """The name of the workflow to attach to this trace. + + This is used to name the trace in the Traces Dashboard. + """ + + +Tracing: TypeAlias = Union[Literal["auto"], TracingTracingConfiguration, None] + + +class RealtimeSessionCreateResponse(BaseModel): + """A Realtime session configuration object.""" + + id: str + """Unique identifier for the session that looks like `sess_1234567890abcdef`.""" + + object: Literal["realtime.session"] + """The object type. Always `realtime.session`.""" + + type: Literal["realtime"] + """The type of session to create. Always `realtime` for the Realtime API.""" + + audio: Optional[Audio] = None + """Configuration for input and output audio.""" + + expires_at: Optional[int] = None + """Expiration timestamp for the session, in seconds since epoch.""" + + include: Optional[List[Literal["item.input_audio_transcription.logprobs"]]] = None + """Additional fields to include in server outputs. + + `item.input_audio_transcription.logprobs`: Include logprobs for input audio + transcription. + """ + + instructions: Optional[str] = None + """The default system instructions (i.e. + + system message) prepended to model calls. This field allows the client to guide + the model on desired responses. The model can be instructed on response content + and format, (e.g. "be extremely succinct", "act friendly", "here are examples of + good responses") and on audio behavior (e.g. "talk quickly", "inject emotion + into your voice", "laugh frequently"). The instructions are not guaranteed to be + followed by the model, but they provide guidance to the model on the desired + behavior. + + Note that the server sets default instructions which will be used if this field + is not set and are visible in the `session.created` event at the start of the + session. + """ + + max_output_tokens: Union[int, Literal["inf"], None] = None + """ + Maximum number of output tokens for a single assistant response, inclusive of + tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + `inf` for the maximum available tokens for a given model. Defaults to `inf`. + """ + + model: Union[ + str, + Literal[ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15", + ], + None, + ] = None + """The Realtime model used for this session.""" + + output_modalities: Optional[List[Literal["text", "audio"]]] = None + """The set of modalities the model can respond with. + + It defaults to `["audio"]`, indicating that the model will respond with audio + plus a transcript. `["text"]` can be used to make the model respond with text + only. It is not possible to request both `text` and `audio` at the same time. + """ + + prompt: Optional[ResponsePrompt] = None + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + reasoning: Optional[RealtimeReasoning] = None + """Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`.""" + + tool_choice: Optional[ToolChoice] = None + """How the model chooses tools. + + Provide one of the string modes or force a specific function/MCP tool. + """ + + tools: Optional[List[Tool]] = None + """Tools available to the model.""" + + tracing: Optional[Tracing] = None + """ + Realtime API can write session traces to the + [Traces Dashboard](https://platform.openai.com/logs?api=traces). Set to null to + disable tracing. Once tracing is enabled for a session, the configuration cannot + be modified. + + `auto` will create a trace for the session with default values for the workflow + name, group id, and metadata. + """ + + truncation: Optional[RealtimeTruncation] = None + """ + When the number of tokens in a conversation exceeds the model's input token + limit, the conversation be truncated, meaning messages (starting from the + oldest) will not be included in the model's context. A 32k context model with + 4,096 max output tokens can only include 28,224 tokens in the context before + truncation occurs. + + Clients can configure truncation behavior to truncate with a lower max token + limit, which is an effective way to control token usage and cost. + + Truncation will reduce the number of cached tokens on the next turn (busting the + cache), since messages are dropped from the beginning of the context. However, + clients can also configure truncation to retain messages up to a fraction of the + maximum context size, which will reduce the need for future truncations and thus + improve the cache rate. + + Truncation can be disabled entirely, which means the server will never truncate + but would instead return an error if the conversation exceeds the model's input + token limit. + """ diff --git a/src/openai/types/realtime/realtime_tool_choice_config.py b/src/openai/types/realtime/realtime_tool_choice_config.py new file mode 100644 index 0000000000..f93c490004 --- /dev/null +++ b/src/openai/types/realtime/realtime_tool_choice_config.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import TypeAlias + +from ..responses.tool_choice_mcp import ToolChoiceMcp +from ..responses.tool_choice_options import ToolChoiceOptions +from ..responses.tool_choice_function import ToolChoiceFunction + +__all__ = ["RealtimeToolChoiceConfig"] + +RealtimeToolChoiceConfig: TypeAlias = Union[ToolChoiceOptions, ToolChoiceFunction, ToolChoiceMcp] diff --git a/src/openai/types/realtime/realtime_tool_choice_config_param.py b/src/openai/types/realtime/realtime_tool_choice_config_param.py new file mode 100644 index 0000000000..af92f243b0 --- /dev/null +++ b/src/openai/types/realtime/realtime_tool_choice_config_param.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypeAlias + +from ..responses.tool_choice_options import ToolChoiceOptions +from ..responses.tool_choice_mcp_param import ToolChoiceMcpParam +from ..responses.tool_choice_function_param import ToolChoiceFunctionParam + +__all__ = ["RealtimeToolChoiceConfigParam"] + +RealtimeToolChoiceConfigParam: TypeAlias = Union[ToolChoiceOptions, ToolChoiceFunctionParam, ToolChoiceMcpParam] diff --git a/src/openai/types/realtime/realtime_tools_config.py b/src/openai/types/realtime/realtime_tools_config.py new file mode 100644 index 0000000000..b97599ab42 --- /dev/null +++ b/src/openai/types/realtime/realtime_tools_config.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .realtime_tools_config_union import RealtimeToolsConfigUnion + +__all__ = ["RealtimeToolsConfig"] + +RealtimeToolsConfig: TypeAlias = List[RealtimeToolsConfigUnion] diff --git a/src/openai/types/realtime/realtime_tools_config_param.py b/src/openai/types/realtime/realtime_tools_config_param.py new file mode 100644 index 0000000000..217130922a --- /dev/null +++ b/src/openai/types/realtime/realtime_tools_config_param.py @@ -0,0 +1,164 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from .realtime_function_tool_param import RealtimeFunctionToolParam + +__all__ = [ + "RealtimeToolsConfigParam", + "RealtimeToolsConfigUnionParam", + "Mcp", + "McpAllowedTools", + "McpAllowedToolsMcpToolFilter", + "McpRequireApproval", + "McpRequireApprovalMcpToolApprovalFilter", + "McpRequireApprovalMcpToolApprovalFilterAlways", + "McpRequireApprovalMcpToolApprovalFilterNever", +] + + +class McpAllowedToolsMcpToolFilter(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +McpAllowedTools: TypeAlias = Union[SequenceNotStr[str], McpAllowedToolsMcpToolFilter] + + +class McpRequireApprovalMcpToolApprovalFilterAlways(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +class McpRequireApprovalMcpToolApprovalFilterNever(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +class McpRequireApprovalMcpToolApprovalFilter(TypedDict, total=False): + """Specify which of the MCP server's tools require approval. + + Can be + `always`, `never`, or a filter object associated with tools + that require approval. + """ + + always: McpRequireApprovalMcpToolApprovalFilterAlways + """A filter object to specify which tools are allowed.""" + + never: McpRequireApprovalMcpToolApprovalFilterNever + """A filter object to specify which tools are allowed.""" + + +McpRequireApproval: TypeAlias = Union[McpRequireApprovalMcpToolApprovalFilter, Literal["always", "never"]] + + +class Mcp(TypedDict, total=False): + """ + Give the model access to additional tools via remote Model Context Protocol + (MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp). + """ + + server_label: Required[str] + """A label for this MCP server, used to identify it in tool calls.""" + + type: Required[Literal["mcp"]] + """The type of the MCP tool. Always `mcp`.""" + + allowed_tools: Optional[McpAllowedTools] + """List of allowed tool names or a filter object.""" + + authorization: str + """ + An OAuth access token that can be used with a remote MCP server, either with a + custom MCP server URL or a service connector. Your application must handle the + OAuth authorization flow and provide the token here. + """ + + connector_id: Literal[ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint", + ] + """Identifier for service connectors, like those available in ChatGPT. + + One of `server_url` or `connector_id` must be provided. Learn more about service + connectors + [here](https://platform.openai.com/docs/guides/tools-remote-mcp#connectors). + + Currently supported `connector_id` values are: + + - Dropbox: `connector_dropbox` + - Gmail: `connector_gmail` + - Google Calendar: `connector_googlecalendar` + - Google Drive: `connector_googledrive` + - Microsoft Teams: `connector_microsoftteams` + - Outlook Calendar: `connector_outlookcalendar` + - Outlook Email: `connector_outlookemail` + - SharePoint: `connector_sharepoint` + """ + + defer_loading: bool + """Whether this MCP tool is deferred and discovered via tool search.""" + + headers: Optional[Dict[str, str]] + """Optional HTTP headers to send to the MCP server. + + Use for authentication or other purposes. + """ + + require_approval: Optional[McpRequireApproval] + """Specify which of the MCP server's tools require approval.""" + + server_description: str + """Optional description of the MCP server, used to provide more context.""" + + server_url: str + """The URL for the MCP server. + + One of `server_url` or `connector_id` must be provided. + """ + + +RealtimeToolsConfigUnionParam: TypeAlias = Union[RealtimeFunctionToolParam, Mcp] + +RealtimeToolsConfigParam: TypeAlias = List[RealtimeToolsConfigUnionParam] diff --git a/src/openai/types/realtime/realtime_tools_config_union.py b/src/openai/types/realtime/realtime_tools_config_union.py new file mode 100644 index 0000000000..55da58269c --- /dev/null +++ b/src/openai/types/realtime/realtime_tools_config_union.py @@ -0,0 +1,162 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .realtime_function_tool import RealtimeFunctionTool + +__all__ = [ + "RealtimeToolsConfigUnion", + "Mcp", + "McpAllowedTools", + "McpAllowedToolsMcpToolFilter", + "McpRequireApproval", + "McpRequireApprovalMcpToolApprovalFilter", + "McpRequireApprovalMcpToolApprovalFilterAlways", + "McpRequireApprovalMcpToolApprovalFilterNever", +] + + +class McpAllowedToolsMcpToolFilter(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +McpAllowedTools: TypeAlias = Union[List[str], McpAllowedToolsMcpToolFilter, None] + + +class McpRequireApprovalMcpToolApprovalFilterAlways(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +class McpRequireApprovalMcpToolApprovalFilterNever(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +class McpRequireApprovalMcpToolApprovalFilter(BaseModel): + """Specify which of the MCP server's tools require approval. + + Can be + `always`, `never`, or a filter object associated with tools + that require approval. + """ + + always: Optional[McpRequireApprovalMcpToolApprovalFilterAlways] = None + """A filter object to specify which tools are allowed.""" + + never: Optional[McpRequireApprovalMcpToolApprovalFilterNever] = None + """A filter object to specify which tools are allowed.""" + + +McpRequireApproval: TypeAlias = Union[McpRequireApprovalMcpToolApprovalFilter, Literal["always", "never"], None] + + +class Mcp(BaseModel): + """ + Give the model access to additional tools via remote Model Context Protocol + (MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp). + """ + + server_label: str + """A label for this MCP server, used to identify it in tool calls.""" + + type: Literal["mcp"] + """The type of the MCP tool. Always `mcp`.""" + + allowed_tools: Optional[McpAllowedTools] = None + """List of allowed tool names or a filter object.""" + + authorization: Optional[str] = None + """ + An OAuth access token that can be used with a remote MCP server, either with a + custom MCP server URL or a service connector. Your application must handle the + OAuth authorization flow and provide the token here. + """ + + connector_id: Optional[ + Literal[ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint", + ] + ] = None + """Identifier for service connectors, like those available in ChatGPT. + + One of `server_url` or `connector_id` must be provided. Learn more about service + connectors + [here](https://platform.openai.com/docs/guides/tools-remote-mcp#connectors). + + Currently supported `connector_id` values are: + + - Dropbox: `connector_dropbox` + - Gmail: `connector_gmail` + - Google Calendar: `connector_googlecalendar` + - Google Drive: `connector_googledrive` + - Microsoft Teams: `connector_microsoftteams` + - Outlook Calendar: `connector_outlookcalendar` + - Outlook Email: `connector_outlookemail` + - SharePoint: `connector_sharepoint` + """ + + defer_loading: Optional[bool] = None + """Whether this MCP tool is deferred and discovered via tool search.""" + + headers: Optional[Dict[str, str]] = None + """Optional HTTP headers to send to the MCP server. + + Use for authentication or other purposes. + """ + + require_approval: Optional[McpRequireApproval] = None + """Specify which of the MCP server's tools require approval.""" + + server_description: Optional[str] = None + """Optional description of the MCP server, used to provide more context.""" + + server_url: Optional[str] = None + """The URL for the MCP server. + + One of `server_url` or `connector_id` must be provided. + """ + + +RealtimeToolsConfigUnion: TypeAlias = Annotated[Union[RealtimeFunctionTool, Mcp], PropertyInfo(discriminator="type")] diff --git a/src/openai/types/realtime/realtime_tools_config_union_param.py b/src/openai/types/realtime/realtime_tools_config_union_param.py new file mode 100644 index 0000000000..15118f3388 --- /dev/null +++ b/src/openai/types/realtime/realtime_tools_config_union_param.py @@ -0,0 +1,161 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from .realtime_function_tool_param import RealtimeFunctionToolParam + +__all__ = [ + "RealtimeToolsConfigUnionParam", + "Mcp", + "McpAllowedTools", + "McpAllowedToolsMcpToolFilter", + "McpRequireApproval", + "McpRequireApprovalMcpToolApprovalFilter", + "McpRequireApprovalMcpToolApprovalFilterAlways", + "McpRequireApprovalMcpToolApprovalFilterNever", +] + + +class McpAllowedToolsMcpToolFilter(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +McpAllowedTools: TypeAlias = Union[SequenceNotStr[str], McpAllowedToolsMcpToolFilter] + + +class McpRequireApprovalMcpToolApprovalFilterAlways(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +class McpRequireApprovalMcpToolApprovalFilterNever(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +class McpRequireApprovalMcpToolApprovalFilter(TypedDict, total=False): + """Specify which of the MCP server's tools require approval. + + Can be + `always`, `never`, or a filter object associated with tools + that require approval. + """ + + always: McpRequireApprovalMcpToolApprovalFilterAlways + """A filter object to specify which tools are allowed.""" + + never: McpRequireApprovalMcpToolApprovalFilterNever + """A filter object to specify which tools are allowed.""" + + +McpRequireApproval: TypeAlias = Union[McpRequireApprovalMcpToolApprovalFilter, Literal["always", "never"]] + + +class Mcp(TypedDict, total=False): + """ + Give the model access to additional tools via remote Model Context Protocol + (MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp). + """ + + server_label: Required[str] + """A label for this MCP server, used to identify it in tool calls.""" + + type: Required[Literal["mcp"]] + """The type of the MCP tool. Always `mcp`.""" + + allowed_tools: Optional[McpAllowedTools] + """List of allowed tool names or a filter object.""" + + authorization: str + """ + An OAuth access token that can be used with a remote MCP server, either with a + custom MCP server URL or a service connector. Your application must handle the + OAuth authorization flow and provide the token here. + """ + + connector_id: Literal[ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint", + ] + """Identifier for service connectors, like those available in ChatGPT. + + One of `server_url` or `connector_id` must be provided. Learn more about service + connectors + [here](https://platform.openai.com/docs/guides/tools-remote-mcp#connectors). + + Currently supported `connector_id` values are: + + - Dropbox: `connector_dropbox` + - Gmail: `connector_gmail` + - Google Calendar: `connector_googlecalendar` + - Google Drive: `connector_googledrive` + - Microsoft Teams: `connector_microsoftteams` + - Outlook Calendar: `connector_outlookcalendar` + - Outlook Email: `connector_outlookemail` + - SharePoint: `connector_sharepoint` + """ + + defer_loading: bool + """Whether this MCP tool is deferred and discovered via tool search.""" + + headers: Optional[Dict[str, str]] + """Optional HTTP headers to send to the MCP server. + + Use for authentication or other purposes. + """ + + require_approval: Optional[McpRequireApproval] + """Specify which of the MCP server's tools require approval.""" + + server_description: str + """Optional description of the MCP server, used to provide more context.""" + + server_url: str + """The URL for the MCP server. + + One of `server_url` or `connector_id` must be provided. + """ + + +RealtimeToolsConfigUnionParam: TypeAlias = Union[RealtimeFunctionToolParam, Mcp] diff --git a/src/openai/types/realtime/realtime_tracing_config.py b/src/openai/types/realtime/realtime_tracing_config.py new file mode 100644 index 0000000000..37e3ce8945 --- /dev/null +++ b/src/openai/types/realtime/realtime_tracing_config.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel + +__all__ = ["RealtimeTracingConfig", "TracingConfiguration"] + + +class TracingConfiguration(BaseModel): + """Granular configuration for tracing.""" + + group_id: Optional[str] = None + """ + The group id to attach to this trace to enable filtering and grouping in the + Traces Dashboard. + """ + + metadata: Optional[object] = None + """ + The arbitrary metadata to attach to this trace to enable filtering in the Traces + Dashboard. + """ + + workflow_name: Optional[str] = None + """The name of the workflow to attach to this trace. + + This is used to name the trace in the Traces Dashboard. + """ + + +RealtimeTracingConfig: TypeAlias = Union[Literal["auto"], TracingConfiguration, None] diff --git a/src/openai/types/realtime/realtime_tracing_config_param.py b/src/openai/types/realtime/realtime_tracing_config_param.py new file mode 100644 index 0000000000..742412897f --- /dev/null +++ b/src/openai/types/realtime/realtime_tracing_config_param.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypeAlias, TypedDict + +__all__ = ["RealtimeTracingConfigParam", "TracingConfiguration"] + + +class TracingConfiguration(TypedDict, total=False): + """Granular configuration for tracing.""" + + group_id: str + """ + The group id to attach to this trace to enable filtering and grouping in the + Traces Dashboard. + """ + + metadata: object + """ + The arbitrary metadata to attach to this trace to enable filtering in the Traces + Dashboard. + """ + + workflow_name: str + """The name of the workflow to attach to this trace. + + This is used to name the trace in the Traces Dashboard. + """ + + +RealtimeTracingConfigParam: TypeAlias = Union[Literal["auto"], TracingConfiguration] diff --git a/src/openai/types/realtime/realtime_transcription_session_audio.py b/src/openai/types/realtime/realtime_transcription_session_audio.py new file mode 100644 index 0000000000..7ec29afb79 --- /dev/null +++ b/src/openai/types/realtime/realtime_transcription_session_audio.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .realtime_transcription_session_audio_input import RealtimeTranscriptionSessionAudioInput + +__all__ = ["RealtimeTranscriptionSessionAudio"] + + +class RealtimeTranscriptionSessionAudio(BaseModel): + """Configuration for input and output audio.""" + + input: Optional[RealtimeTranscriptionSessionAudioInput] = None diff --git a/src/openai/types/realtime/realtime_transcription_session_audio_input.py b/src/openai/types/realtime/realtime_transcription_session_audio_input.py new file mode 100644 index 0000000000..e6f044bc22 --- /dev/null +++ b/src/openai/types/realtime/realtime_transcription_session_audio_input.py @@ -0,0 +1,75 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .audio_transcription import AudioTranscription +from .noise_reduction_type import NoiseReductionType +from .realtime_audio_formats import RealtimeAudioFormats +from .realtime_transcription_session_audio_input_turn_detection import ( + RealtimeTranscriptionSessionAudioInputTurnDetection, +) + +__all__ = ["RealtimeTranscriptionSessionAudioInput", "NoiseReduction"] + + +class NoiseReduction(BaseModel): + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. + Noise reduction filters audio added to the input audio buffer before it is sent to VAD and the model. + Filtering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio. + """ + + type: Optional[NoiseReductionType] = None + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class RealtimeTranscriptionSessionAudioInput(BaseModel): + format: Optional[RealtimeAudioFormats] = None + """The PCM audio format. Only a 24kHz sample rate is supported.""" + + noise_reduction: Optional[NoiseReduction] = None + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + transcription: Optional[AudioTranscription] = None + """ + Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously through + [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) + and should be treated as guidance of input audio content rather than precisely + what the model heard. The client can optionally set the language and prompt for + transcription, these offer additional guidance to the transcription service. + """ + + turn_detection: Optional[RealtimeTranscriptionSessionAudioInputTurnDetection] = None + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. + + Server VAD means that the model will detect the start and end of speech based on + audio volume and respond at the end of user speech. + + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + + For `gpt-realtime-whisper` transcription sessions, turn detection must be set to + `null`; VAD is not supported. + """ diff --git a/src/openai/types/realtime/realtime_transcription_session_audio_input_param.py b/src/openai/types/realtime/realtime_transcription_session_audio_input_param.py new file mode 100644 index 0000000000..cacb38dc22 --- /dev/null +++ b/src/openai/types/realtime/realtime_transcription_session_audio_input_param.py @@ -0,0 +1,77 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +from .noise_reduction_type import NoiseReductionType +from .audio_transcription_param import AudioTranscriptionParam +from .realtime_audio_formats_param import RealtimeAudioFormatsParam +from .realtime_transcription_session_audio_input_turn_detection_param import ( + RealtimeTranscriptionSessionAudioInputTurnDetectionParam, +) + +__all__ = ["RealtimeTranscriptionSessionAudioInputParam", "NoiseReduction"] + + +class NoiseReduction(TypedDict, total=False): + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. + Noise reduction filters audio added to the input audio buffer before it is sent to VAD and the model. + Filtering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio. + """ + + type: NoiseReductionType + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class RealtimeTranscriptionSessionAudioInputParam(TypedDict, total=False): + format: RealtimeAudioFormatsParam + """The PCM audio format. Only a 24kHz sample rate is supported.""" + + noise_reduction: NoiseReduction + """Configuration for input audio noise reduction. + + This can be set to `null` to turn off. Noise reduction filters audio added to + the input audio buffer before it is sent to VAD and the model. Filtering the + audio can improve VAD and turn detection accuracy (reducing false positives) and + model performance by improving perception of the input audio. + """ + + transcription: AudioTranscriptionParam + """ + Configuration for input audio transcription, defaults to off and can be set to + `null` to turn off once on. Input audio transcription is not native to the + model, since the model consumes audio directly. Transcription runs + asynchronously through + [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) + and should be treated as guidance of input audio content rather than precisely + what the model heard. The client can optionally set the language and prompt for + transcription, these offer additional guidance to the transcription service. + """ + + turn_detection: Optional[RealtimeTranscriptionSessionAudioInputTurnDetectionParam] + """Configuration for turn detection, ether Server VAD or Semantic VAD. + + This can be set to `null` to turn off, in which case the client must manually + trigger model response. + + Server VAD means that the model will detect the start and end of speech based on + audio volume and respond at the end of user speech. + + Semantic VAD is more advanced and uses a turn detection model (in conjunction + with VAD) to semantically estimate whether the user has finished speaking, then + dynamically sets a timeout based on this probability. For example, if user audio + trails off with "uhhm", the model will score a low probability of turn end and + wait longer for the user to continue speaking. This can be useful for more + natural conversations, but may have a higher latency. + + For `gpt-realtime-whisper` transcription sessions, turn detection must be set to + `null`; VAD is not supported. + """ diff --git a/src/openai/types/realtime/realtime_transcription_session_audio_input_turn_detection.py b/src/openai/types/realtime/realtime_transcription_session_audio_input_turn_detection.py new file mode 100644 index 0000000000..3d4ee779f4 --- /dev/null +++ b/src/openai/types/realtime/realtime_transcription_session_audio_input_turn_detection.py @@ -0,0 +1,115 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = ["RealtimeTranscriptionSessionAudioInputTurnDetection", "ServerVad", "SemanticVad"] + + +class ServerVad(BaseModel): + """ + Server-side voice activity detection (VAD) which flips on when user speech is detected and off after a period of silence. + """ + + type: Literal["server_vad"] + """Type of turn detection, `server_vad` to turn on simple Server VAD.""" + + create_response: Optional[bool] = None + """Whether or not to automatically generate a response when a VAD stop event + occurs. + + If `interrupt_response` is set to `false` this may fail to create a response if + the model is already responding. + + If both `create_response` and `interrupt_response` are set to `false`, the model + will never respond automatically but VAD events will still be emitted. + """ + + idle_timeout_ms: Optional[int] = None + """Optional timeout after which a model response will be triggered automatically. + + This is useful for situations in which a long pause from the user is unexpected, + such as a phone call. The model will effectively prompt the user to continue the + conversation based on the current context. + + The timeout value will be applied after the last model response's audio has + finished playing, i.e. it's set to the `response.done` time plus audio playback + duration. + + An `input_audio_buffer.timeout_triggered` event (plus events associated with the + Response) will be emitted when the timeout is reached. Idle timeout is currently + only supported for `server_vad` mode. + """ + + interrupt_response: Optional[bool] = None + """ + Whether or not to automatically interrupt (cancel) any ongoing response with + output to the default conversation (i.e. `conversation` of `auto`) when a VAD + start event occurs. If `true` then the response will be cancelled, otherwise it + will continue until complete. + + If both `create_response` and `interrupt_response` are set to `false`, the model + will never respond automatically but VAD events will still be emitted. + """ + + prefix_padding_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: Optional[int] = None + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: Optional[float] = None + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + +class SemanticVad(BaseModel): + """ + Server-side semantic turn detection which uses a model to determine when the user has finished speaking. + """ + + type: Literal["semantic_vad"] + """Type of turn detection, `semantic_vad` to turn on Semantic VAD.""" + + create_response: Optional[bool] = None + """ + Whether or not to automatically generate a response when a VAD stop event + occurs. + """ + + eagerness: Optional[Literal["low", "medium", "high", "auto"]] = None + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. `low`, `medium`, and `high` have max timeouts of 8s, + 4s, and 2s respectively. + """ + + interrupt_response: Optional[bool] = None + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. + """ + + +RealtimeTranscriptionSessionAudioInputTurnDetection: TypeAlias = Annotated[ + Union[ServerVad, SemanticVad, None], PropertyInfo(discriminator="type") +] diff --git a/src/openai/types/realtime/realtime_transcription_session_audio_input_turn_detection_param.py b/src/openai/types/realtime/realtime_transcription_session_audio_input_turn_detection_param.py new file mode 100644 index 0000000000..0aca59ce11 --- /dev/null +++ b/src/openai/types/realtime/realtime_transcription_session_audio_input_turn_detection_param.py @@ -0,0 +1,112 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = ["RealtimeTranscriptionSessionAudioInputTurnDetectionParam", "ServerVad", "SemanticVad"] + + +class ServerVad(TypedDict, total=False): + """ + Server-side voice activity detection (VAD) which flips on when user speech is detected and off after a period of silence. + """ + + type: Required[Literal["server_vad"]] + """Type of turn detection, `server_vad` to turn on simple Server VAD.""" + + create_response: bool + """Whether or not to automatically generate a response when a VAD stop event + occurs. + + If `interrupt_response` is set to `false` this may fail to create a response if + the model is already responding. + + If both `create_response` and `interrupt_response` are set to `false`, the model + will never respond automatically but VAD events will still be emitted. + """ + + idle_timeout_ms: Optional[int] + """Optional timeout after which a model response will be triggered automatically. + + This is useful for situations in which a long pause from the user is unexpected, + such as a phone call. The model will effectively prompt the user to continue the + conversation based on the current context. + + The timeout value will be applied after the last model response's audio has + finished playing, i.e. it's set to the `response.done` time plus audio playback + duration. + + An `input_audio_buffer.timeout_triggered` event (plus events associated with the + Response) will be emitted when the timeout is reached. Idle timeout is currently + only supported for `server_vad` mode. + """ + + interrupt_response: bool + """ + Whether or not to automatically interrupt (cancel) any ongoing response with + output to the default conversation (i.e. `conversation` of `auto`) when a VAD + start event occurs. If `true` then the response will be cancelled, otherwise it + will continue until complete. + + If both `create_response` and `interrupt_response` are set to `false`, the model + will never respond automatically but VAD events will still be emitted. + """ + + prefix_padding_ms: int + """Used only for `server_vad` mode. + + Amount of audio to include before the VAD detected speech (in milliseconds). + Defaults to 300ms. + """ + + silence_duration_ms: int + """Used only for `server_vad` mode. + + Duration of silence to detect speech stop (in milliseconds). Defaults to 500ms. + With shorter values the model will respond more quickly, but may jump in on + short pauses from the user. + """ + + threshold: float + """Used only for `server_vad` mode. + + Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A higher + threshold will require louder audio to activate the model, and thus might + perform better in noisy environments. + """ + + +class SemanticVad(TypedDict, total=False): + """ + Server-side semantic turn detection which uses a model to determine when the user has finished speaking. + """ + + type: Required[Literal["semantic_vad"]] + """Type of turn detection, `semantic_vad` to turn on Semantic VAD.""" + + create_response: bool + """ + Whether or not to automatically generate a response when a VAD stop event + occurs. + """ + + eagerness: Literal["low", "medium", "high", "auto"] + """Used only for `semantic_vad` mode. + + The eagerness of the model to respond. `low` will wait longer for the user to + continue speaking, `high` will respond more quickly. `auto` is the default and + is equivalent to `medium`. `low`, `medium`, and `high` have max timeouts of 8s, + 4s, and 2s respectively. + """ + + interrupt_response: bool + """ + Whether or not to automatically interrupt any ongoing response with output to + the default conversation (i.e. `conversation` of `auto`) when a VAD start event + occurs. + """ + + +RealtimeTranscriptionSessionAudioInputTurnDetectionParam: TypeAlias = Union[ServerVad, SemanticVad] diff --git a/src/openai/types/realtime/realtime_transcription_session_audio_param.py b/src/openai/types/realtime/realtime_transcription_session_audio_param.py new file mode 100644 index 0000000000..6bf1117917 --- /dev/null +++ b/src/openai/types/realtime/realtime_transcription_session_audio_param.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +from .realtime_transcription_session_audio_input_param import RealtimeTranscriptionSessionAudioInputParam + +__all__ = ["RealtimeTranscriptionSessionAudioParam"] + + +class RealtimeTranscriptionSessionAudioParam(TypedDict, total=False): + """Configuration for input and output audio.""" + + input: RealtimeTranscriptionSessionAudioInputParam diff --git a/src/openai/types/realtime/realtime_transcription_session_create_request.py b/src/openai/types/realtime/realtime_transcription_session_create_request.py new file mode 100644 index 0000000000..f72a4ad93f --- /dev/null +++ b/src/openai/types/realtime/realtime_transcription_session_create_request.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .realtime_transcription_session_audio import RealtimeTranscriptionSessionAudio + +__all__ = ["RealtimeTranscriptionSessionCreateRequest"] + + +class RealtimeTranscriptionSessionCreateRequest(BaseModel): + """Realtime transcription session object configuration.""" + + type: Literal["transcription"] + """The type of session to create. + + Always `transcription` for transcription sessions. + """ + + audio: Optional[RealtimeTranscriptionSessionAudio] = None + """Configuration for input and output audio.""" + + include: Optional[List[Literal["item.input_audio_transcription.logprobs"]]] = None + """Additional fields to include in server outputs. + + `item.input_audio_transcription.logprobs`: Include logprobs for input audio + transcription. + """ diff --git a/src/openai/types/realtime/realtime_transcription_session_create_request_param.py b/src/openai/types/realtime/realtime_transcription_session_create_request_param.py new file mode 100644 index 0000000000..9b4d8ead79 --- /dev/null +++ b/src/openai/types/realtime/realtime_transcription_session_create_request_param.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +from .realtime_transcription_session_audio_param import RealtimeTranscriptionSessionAudioParam + +__all__ = ["RealtimeTranscriptionSessionCreateRequestParam"] + + +class RealtimeTranscriptionSessionCreateRequestParam(TypedDict, total=False): + """Realtime transcription session object configuration.""" + + type: Required[Literal["transcription"]] + """The type of session to create. + + Always `transcription` for transcription sessions. + """ + + audio: RealtimeTranscriptionSessionAudioParam + """Configuration for input and output audio.""" + + include: List[Literal["item.input_audio_transcription.logprobs"]] + """Additional fields to include in server outputs. + + `item.input_audio_transcription.logprobs`: Include logprobs for input audio + transcription. + """ diff --git a/src/openai/types/realtime/realtime_transcription_session_create_response.py b/src/openai/types/realtime/realtime_transcription_session_create_response.py new file mode 100644 index 0000000000..68f7e71a54 --- /dev/null +++ b/src/openai/types/realtime/realtime_transcription_session_create_response.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .audio_transcription import AudioTranscription +from .noise_reduction_type import NoiseReductionType +from .realtime_audio_formats import RealtimeAudioFormats +from .realtime_transcription_session_turn_detection import RealtimeTranscriptionSessionTurnDetection + +__all__ = ["RealtimeTranscriptionSessionCreateResponse", "Audio", "AudioInput", "AudioInputNoiseReduction"] + + +class AudioInputNoiseReduction(BaseModel): + """Configuration for input audio noise reduction.""" + + type: Optional[NoiseReductionType] = None + """Type of noise reduction. + + `near_field` is for close-talking microphones such as headphones, `far_field` is + for far-field microphones such as laptop or conference room microphones. + """ + + +class AudioInput(BaseModel): + format: Optional[RealtimeAudioFormats] = None + """The PCM audio format. Only a 24kHz sample rate is supported.""" + + noise_reduction: Optional[AudioInputNoiseReduction] = None + """Configuration for input audio noise reduction.""" + + transcription: Optional[AudioTranscription] = None + + turn_detection: Optional[RealtimeTranscriptionSessionTurnDetection] = None + """Configuration for turn detection. + + Can be set to `null` to turn off. Server VAD means that the model will detect + the start and end of speech based on audio volume and respond at the end of user + speech. For `gpt-realtime-whisper`, this must be `null`; VAD is not supported. + """ + + +class Audio(BaseModel): + """Configuration for input audio for the session.""" + + input: Optional[AudioInput] = None + + +class RealtimeTranscriptionSessionCreateResponse(BaseModel): + """A Realtime transcription session configuration object.""" + + id: str + """Unique identifier for the session that looks like `sess_1234567890abcdef`.""" + + object: str + """The object type. Always `realtime.transcription_session`.""" + + type: Literal["transcription"] + """The type of session. Always `transcription` for transcription sessions.""" + + audio: Optional[Audio] = None + """Configuration for input audio for the session.""" + + expires_at: Optional[int] = None + """Expiration timestamp for the session, in seconds since epoch.""" + + include: Optional[List[Literal["item.input_audio_transcription.logprobs"]]] = None + """Additional fields to include in server outputs. + + - `item.input_audio_transcription.logprobs`: Include logprobs for input audio + transcription. + """ diff --git a/src/openai/types/realtime/realtime_transcription_session_turn_detection.py b/src/openai/types/realtime/realtime_transcription_session_turn_detection.py new file mode 100644 index 0000000000..0f3d5b1c00 --- /dev/null +++ b/src/openai/types/realtime/realtime_transcription_session_turn_detection.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["RealtimeTranscriptionSessionTurnDetection"] + + +class RealtimeTranscriptionSessionTurnDetection(BaseModel): + """Configuration for turn detection. + + Can be set to `null` to turn off. Server + VAD means that the model will detect the start and end of speech based on + audio volume and respond at the end of user speech. For `gpt-realtime-whisper`, this must be `null`; VAD is not supported. + """ + + prefix_padding_ms: Optional[int] = None + """Amount of audio to include before the VAD detected speech (in milliseconds). + + Defaults to 300ms. + """ + + silence_duration_ms: Optional[int] = None + """Duration of silence to detect speech stop (in milliseconds). + + Defaults to 500ms. With shorter values the model will respond more quickly, but + may jump in on short pauses from the user. + """ + + threshold: Optional[float] = None + """Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. + + A higher threshold will require louder audio to activate the model, and thus + might perform better in noisy environments. + """ + + type: Optional[str] = None + """Type of turn detection, only `server_vad` is currently supported.""" diff --git a/src/openai/types/realtime/realtime_truncation.py b/src/openai/types/realtime/realtime_truncation.py new file mode 100644 index 0000000000..515f869071 --- /dev/null +++ b/src/openai/types/realtime/realtime_truncation.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, TypeAlias + +from .realtime_truncation_retention_ratio import RealtimeTruncationRetentionRatio + +__all__ = ["RealtimeTruncation"] + +RealtimeTruncation: TypeAlias = Union[Literal["auto", "disabled"], RealtimeTruncationRetentionRatio] diff --git a/src/openai/types/realtime/realtime_truncation_param.py b/src/openai/types/realtime/realtime_truncation_param.py new file mode 100644 index 0000000000..5e42b27418 --- /dev/null +++ b/src/openai/types/realtime/realtime_truncation_param.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypeAlias + +from .realtime_truncation_retention_ratio_param import RealtimeTruncationRetentionRatioParam + +__all__ = ["RealtimeTruncationParam"] + +RealtimeTruncationParam: TypeAlias = Union[Literal["auto", "disabled"], RealtimeTruncationRetentionRatioParam] diff --git a/src/openai/types/realtime/realtime_truncation_retention_ratio.py b/src/openai/types/realtime/realtime_truncation_retention_ratio.py new file mode 100644 index 0000000000..72a93a5654 --- /dev/null +++ b/src/openai/types/realtime/realtime_truncation_retention_ratio.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeTruncationRetentionRatio", "TokenLimits"] + + +class TokenLimits(BaseModel): + """Optional custom token limits for this truncation strategy. + + If not provided, the model's default token limits will be used. + """ + + post_instructions: Optional[int] = None + """ + Maximum tokens allowed in the conversation after instructions (which including + tool definitions). For example, setting this to 5,000 would mean that truncation + would occur when the conversation exceeds 5,000 tokens after instructions. This + cannot be higher than the model's context window size minus the maximum output + tokens. + """ + + +class RealtimeTruncationRetentionRatio(BaseModel): + """ + Retain a fraction of the conversation tokens when the conversation exceeds the input token limit. This allows you to amortize truncations across multiple turns, which can help improve cached token usage. + """ + + retention_ratio: float + """ + Fraction of post-instruction conversation tokens to retain (`0.0` - `1.0`) when + the conversation exceeds the input token limit. Setting this to `0.8` means that + messages will be dropped until 80% of the maximum allowed tokens are used. This + helps reduce the frequency of truncations and improve cache rates. + """ + + type: Literal["retention_ratio"] + """Use retention ratio truncation.""" + + token_limits: Optional[TokenLimits] = None + """Optional custom token limits for this truncation strategy. + + If not provided, the model's default token limits will be used. + """ diff --git a/src/openai/types/realtime/realtime_truncation_retention_ratio_param.py b/src/openai/types/realtime/realtime_truncation_retention_ratio_param.py new file mode 100644 index 0000000000..4648fa66b0 --- /dev/null +++ b/src/openai/types/realtime/realtime_truncation_retention_ratio_param.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RealtimeTruncationRetentionRatioParam", "TokenLimits"] + + +class TokenLimits(TypedDict, total=False): + """Optional custom token limits for this truncation strategy. + + If not provided, the model's default token limits will be used. + """ + + post_instructions: int + """ + Maximum tokens allowed in the conversation after instructions (which including + tool definitions). For example, setting this to 5,000 would mean that truncation + would occur when the conversation exceeds 5,000 tokens after instructions. This + cannot be higher than the model's context window size minus the maximum output + tokens. + """ + + +class RealtimeTruncationRetentionRatioParam(TypedDict, total=False): + """ + Retain a fraction of the conversation tokens when the conversation exceeds the input token limit. This allows you to amortize truncations across multiple turns, which can help improve cached token usage. + """ + + retention_ratio: Required[float] + """ + Fraction of post-instruction conversation tokens to retain (`0.0` - `1.0`) when + the conversation exceeds the input token limit. Setting this to `0.8` means that + messages will be dropped until 80% of the maximum allowed tokens are used. This + helps reduce the frequency of truncations and improve cache rates. + """ + + type: Required[Literal["retention_ratio"]] + """Use retention ratio truncation.""" + + token_limits: TokenLimits + """Optional custom token limits for this truncation strategy. + + If not provided, the model's default token limits will be used. + """ diff --git a/src/openai/types/realtime/response_audio_delta_event.py b/src/openai/types/realtime/response_audio_delta_event.py new file mode 100644 index 0000000000..ae87014053 --- /dev/null +++ b/src/openai/types/realtime/response_audio_delta_event.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseAudioDeltaEvent"] + + +class ResponseAudioDeltaEvent(BaseModel): + """Returned when the model-generated audio is updated.""" + + content_index: int + """The index of the content part in the item's content array.""" + + delta: str + """Base64-encoded audio data delta.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.output_audio.delta"] + """The event type, must be `response.output_audio.delta`.""" diff --git a/src/openai/types/realtime/response_audio_done_event.py b/src/openai/types/realtime/response_audio_done_event.py new file mode 100644 index 0000000000..98715aba13 --- /dev/null +++ b/src/openai/types/realtime/response_audio_done_event.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseAudioDoneEvent"] + + +class ResponseAudioDoneEvent(BaseModel): + """Returned when the model-generated audio is done. + + Also emitted when a Response + is interrupted, incomplete, or cancelled. + """ + + content_index: int + """The index of the content part in the item's content array.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.output_audio.done"] + """The event type, must be `response.output_audio.done`.""" diff --git a/src/openai/types/realtime/response_audio_transcript_delta_event.py b/src/openai/types/realtime/response_audio_transcript_delta_event.py new file mode 100644 index 0000000000..4ec1a820ba --- /dev/null +++ b/src/openai/types/realtime/response_audio_transcript_delta_event.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseAudioTranscriptDeltaEvent"] + + +class ResponseAudioTranscriptDeltaEvent(BaseModel): + """Returned when the model-generated transcription of audio output is updated.""" + + content_index: int + """The index of the content part in the item's content array.""" + + delta: str + """The transcript delta.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.output_audio_transcript.delta"] + """The event type, must be `response.output_audio_transcript.delta`.""" diff --git a/src/openai/types/realtime/response_audio_transcript_done_event.py b/src/openai/types/realtime/response_audio_transcript_done_event.py new file mode 100644 index 0000000000..c2a2416355 --- /dev/null +++ b/src/openai/types/realtime/response_audio_transcript_done_event.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseAudioTranscriptDoneEvent"] + + +class ResponseAudioTranscriptDoneEvent(BaseModel): + """ + Returned when the model-generated transcription of audio output is done + streaming. Also emitted when a Response is interrupted, incomplete, or + cancelled. + """ + + content_index: int + """The index of the content part in the item's content array.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + transcript: str + """The final transcript of the audio.""" + + type: Literal["response.output_audio_transcript.done"] + """The event type, must be `response.output_audio_transcript.done`.""" diff --git a/src/openai/types/realtime/response_cancel_event.py b/src/openai/types/realtime/response_cancel_event.py new file mode 100644 index 0000000000..9c6113998f --- /dev/null +++ b/src/openai/types/realtime/response_cancel_event.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCancelEvent"] + + +class ResponseCancelEvent(BaseModel): + """Send this event to cancel an in-progress response. + + The server will respond + with a `response.done` event with a status of `response.status=cancelled`. If + there is no response to cancel, the server will respond with an error. It's safe + to call `response.cancel` even if no response is in progress, an error will be + returned the session will remain unaffected. + """ + + type: Literal["response.cancel"] + """The event type, must be `response.cancel`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" + + response_id: Optional[str] = None + """ + A specific response ID to cancel - if not provided, will cancel an in-progress + response in the default conversation. + """ diff --git a/src/openai/types/realtime/response_cancel_event_param.py b/src/openai/types/realtime/response_cancel_event_param.py new file mode 100644 index 0000000000..b233b407f9 --- /dev/null +++ b/src/openai/types/realtime/response_cancel_event_param.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseCancelEventParam"] + + +class ResponseCancelEventParam(TypedDict, total=False): + """Send this event to cancel an in-progress response. + + The server will respond + with a `response.done` event with a status of `response.status=cancelled`. If + there is no response to cancel, the server will respond with an error. It's safe + to call `response.cancel` even if no response is in progress, an error will be + returned the session will remain unaffected. + """ + + type: Required[Literal["response.cancel"]] + """The event type, must be `response.cancel`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" + + response_id: str + """ + A specific response ID to cancel - if not provided, will cancel an in-progress + response in the default conversation. + """ diff --git a/src/openai/types/realtime/response_content_part_added_event.py b/src/openai/types/realtime/response_content_part_added_event.py new file mode 100644 index 0000000000..e47c84af20 --- /dev/null +++ b/src/openai/types/realtime/response_content_part_added_event.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseContentPartAddedEvent", "Part"] + + +class Part(BaseModel): + """The content part that was added.""" + + audio: Optional[str] = None + """Base64-encoded audio data (if type is "audio").""" + + text: Optional[str] = None + """The text content (if type is "text").""" + + transcript: Optional[str] = None + """The transcript of the audio (if type is "audio").""" + + type: Optional[Literal["text", "audio"]] = None + """The content type ("text", "audio").""" + + +class ResponseContentPartAddedEvent(BaseModel): + """ + Returned when a new content part is added to an assistant message item during + response generation. + """ + + content_index: int + """The index of the content part in the item's content array.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item to which the content part was added.""" + + output_index: int + """The index of the output item in the response.""" + + part: Part + """The content part that was added.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.content_part.added"] + """The event type, must be `response.content_part.added`.""" diff --git a/src/openai/types/realtime/response_content_part_done_event.py b/src/openai/types/realtime/response_content_part_done_event.py new file mode 100644 index 0000000000..a6cb8559b9 --- /dev/null +++ b/src/openai/types/realtime/response_content_part_done_event.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseContentPartDoneEvent", "Part"] + + +class Part(BaseModel): + """The content part that is done.""" + + audio: Optional[str] = None + """Base64-encoded audio data (if type is "audio").""" + + text: Optional[str] = None + """The text content (if type is "text").""" + + transcript: Optional[str] = None + """The transcript of the audio (if type is "audio").""" + + type: Optional[Literal["text", "audio"]] = None + """The content type ("text", "audio").""" + + +class ResponseContentPartDoneEvent(BaseModel): + """ + Returned when a content part is done streaming in an assistant message item. + Also emitted when a Response is interrupted, incomplete, or cancelled. + """ + + content_index: int + """The index of the content part in the item's content array.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + part: Part + """The content part that is done.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.content_part.done"] + """The event type, must be `response.content_part.done`.""" diff --git a/src/openai/types/realtime/response_create_event.py b/src/openai/types/realtime/response_create_event.py new file mode 100644 index 0000000000..3e98a8d858 --- /dev/null +++ b/src/openai/types/realtime/response_create_event.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .realtime_response_create_params import RealtimeResponseCreateParams + +__all__ = ["ResponseCreateEvent"] + + +class ResponseCreateEvent(BaseModel): + """ + This event instructs the server to create a Response, which means triggering + model inference. When in Server VAD mode, the server will create Responses + automatically. + + A Response will include at least one Item, and may have two, in which case + the second will be a function call. These Items will be appended to the + conversation history by default. + + The server will respond with a `response.created` event, events for Items + and content created, and finally a `response.done` event to indicate the + Response is complete. + + The `response.create` event includes inference configuration like + `instructions` and `tools`. If these are set, they will override the Session's + configuration for this Response only. + + Responses can be created out-of-band of the default Conversation, meaning that they can + have arbitrary input, and it's possible to disable writing the output to the Conversation. + Only one Response can write to the default Conversation at a time, but otherwise multiple + Responses can be created in parallel. The `metadata` field is a good way to disambiguate + multiple simultaneous Responses. + + Clients can set `conversation` to `none` to create a Response that does not write to the default + Conversation. Arbitrary input can be provided with the `input` field, which is an array accepting + raw Items and references to existing Items. + """ + + type: Literal["response.create"] + """The event type, must be `response.create`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event.""" + + response: Optional[RealtimeResponseCreateParams] = None + """Create a new Realtime response with these parameters""" diff --git a/src/openai/types/realtime/response_create_event_param.py b/src/openai/types/realtime/response_create_event_param.py new file mode 100644 index 0000000000..9da89e14ee --- /dev/null +++ b/src/openai/types/realtime/response_create_event_param.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .realtime_response_create_params_param import RealtimeResponseCreateParamsParam + +__all__ = ["ResponseCreateEventParam"] + + +class ResponseCreateEventParam(TypedDict, total=False): + """ + This event instructs the server to create a Response, which means triggering + model inference. When in Server VAD mode, the server will create Responses + automatically. + + A Response will include at least one Item, and may have two, in which case + the second will be a function call. These Items will be appended to the + conversation history by default. + + The server will respond with a `response.created` event, events for Items + and content created, and finally a `response.done` event to indicate the + Response is complete. + + The `response.create` event includes inference configuration like + `instructions` and `tools`. If these are set, they will override the Session's + configuration for this Response only. + + Responses can be created out-of-band of the default Conversation, meaning that they can + have arbitrary input, and it's possible to disable writing the output to the Conversation. + Only one Response can write to the default Conversation at a time, but otherwise multiple + Responses can be created in parallel. The `metadata` field is a good way to disambiguate + multiple simultaneous Responses. + + Clients can set `conversation` to `none` to create a Response that does not write to the default + Conversation. Arbitrary input can be provided with the `input` field, which is an array accepting + raw Items and references to existing Items. + """ + + type: Required[Literal["response.create"]] + """The event type, must be `response.create`.""" + + event_id: str + """Optional client-generated ID used to identify this event.""" + + response: RealtimeResponseCreateParamsParam + """Create a new Realtime response with these parameters""" diff --git a/src/openai/types/realtime/response_created_event.py b/src/openai/types/realtime/response_created_event.py new file mode 100644 index 0000000000..dc5941262d --- /dev/null +++ b/src/openai/types/realtime/response_created_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from .realtime_response import RealtimeResponse + +__all__ = ["ResponseCreatedEvent"] + + +class ResponseCreatedEvent(BaseModel): + """Returned when a new Response is created. + + The first event of response creation, + where the response is in an initial state of `in_progress`. + """ + + event_id: str + """The unique ID of the server event.""" + + response: RealtimeResponse + """The response resource.""" + + type: Literal["response.created"] + """The event type, must be `response.created`.""" diff --git a/src/openai/types/realtime/response_done_event.py b/src/openai/types/realtime/response_done_event.py new file mode 100644 index 0000000000..9c31a2aa03 --- /dev/null +++ b/src/openai/types/realtime/response_done_event.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from .realtime_response import RealtimeResponse + +__all__ = ["ResponseDoneEvent"] + + +class ResponseDoneEvent(BaseModel): + """Returned when a Response is done streaming. + + Always emitted, no matter the + final state. The Response object included in the `response.done` event will + include all output Items in the Response but will omit the raw audio data. + + Clients should check the `status` field of the Response to determine if it was successful + (`completed`) or if there was another outcome: `cancelled`, `failed`, or `incomplete`. + + A response will contain all output items that were generated during the response, excluding + any audio content. + """ + + event_id: str + """The unique ID of the server event.""" + + response: RealtimeResponse + """The response resource.""" + + type: Literal["response.done"] + """The event type, must be `response.done`.""" diff --git a/src/openai/types/realtime/response_function_call_arguments_delta_event.py b/src/openai/types/realtime/response_function_call_arguments_delta_event.py new file mode 100644 index 0000000000..a426c3f211 --- /dev/null +++ b/src/openai/types/realtime/response_function_call_arguments_delta_event.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFunctionCallArgumentsDeltaEvent"] + + +class ResponseFunctionCallArgumentsDeltaEvent(BaseModel): + """Returned when the model-generated function call arguments are updated.""" + + call_id: str + """The ID of the function call.""" + + delta: str + """The arguments delta as a JSON string.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the function call item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.function_call_arguments.delta"] + """The event type, must be `response.function_call_arguments.delta`.""" diff --git a/src/openai/types/realtime/response_function_call_arguments_done_event.py b/src/openai/types/realtime/response_function_call_arguments_done_event.py new file mode 100644 index 0000000000..01bae80562 --- /dev/null +++ b/src/openai/types/realtime/response_function_call_arguments_done_event.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFunctionCallArgumentsDoneEvent"] + + +class ResponseFunctionCallArgumentsDoneEvent(BaseModel): + """ + Returned when the model-generated function call arguments are done streaming. + Also emitted when a Response is interrupted, incomplete, or cancelled. + """ + + arguments: str + """The final arguments as a JSON string.""" + + call_id: str + """The ID of the function call.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the function call item.""" + + name: str + """The name of the function that was called.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.function_call_arguments.done"] + """The event type, must be `response.function_call_arguments.done`.""" diff --git a/src/openai/types/realtime/response_mcp_call_arguments_delta.py b/src/openai/types/realtime/response_mcp_call_arguments_delta.py new file mode 100644 index 0000000000..d890de0575 --- /dev/null +++ b/src/openai/types/realtime/response_mcp_call_arguments_delta.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpCallArgumentsDelta"] + + +class ResponseMcpCallArgumentsDelta(BaseModel): + """Returned when MCP tool call arguments are updated during response generation.""" + + delta: str + """The JSON-encoded arguments delta.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the MCP tool call item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.mcp_call_arguments.delta"] + """The event type, must be `response.mcp_call_arguments.delta`.""" + + obfuscation: Optional[str] = None + """If present, indicates the delta text was obfuscated.""" diff --git a/src/openai/types/realtime/response_mcp_call_arguments_done.py b/src/openai/types/realtime/response_mcp_call_arguments_done.py new file mode 100644 index 0000000000..a7cb2d1958 --- /dev/null +++ b/src/openai/types/realtime/response_mcp_call_arguments_done.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpCallArgumentsDone"] + + +class ResponseMcpCallArgumentsDone(BaseModel): + """Returned when MCP tool call arguments are finalized during response generation.""" + + arguments: str + """The final JSON-encoded arguments string.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the MCP tool call item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.mcp_call_arguments.done"] + """The event type, must be `response.mcp_call_arguments.done`.""" diff --git a/src/openai/types/realtime/response_mcp_call_completed.py b/src/openai/types/realtime/response_mcp_call_completed.py new file mode 100644 index 0000000000..130260539a --- /dev/null +++ b/src/openai/types/realtime/response_mcp_call_completed.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpCallCompleted"] + + +class ResponseMcpCallCompleted(BaseModel): + """Returned when an MCP tool call has completed successfully.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the MCP tool call item.""" + + output_index: int + """The index of the output item in the response.""" + + type: Literal["response.mcp_call.completed"] + """The event type, must be `response.mcp_call.completed`.""" diff --git a/src/openai/types/realtime/response_mcp_call_failed.py b/src/openai/types/realtime/response_mcp_call_failed.py new file mode 100644 index 0000000000..1c08d1d4b7 --- /dev/null +++ b/src/openai/types/realtime/response_mcp_call_failed.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpCallFailed"] + + +class ResponseMcpCallFailed(BaseModel): + """Returned when an MCP tool call has failed.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the MCP tool call item.""" + + output_index: int + """The index of the output item in the response.""" + + type: Literal["response.mcp_call.failed"] + """The event type, must be `response.mcp_call.failed`.""" diff --git a/src/openai/types/realtime/response_mcp_call_in_progress.py b/src/openai/types/realtime/response_mcp_call_in_progress.py new file mode 100644 index 0000000000..4c0ad149e5 --- /dev/null +++ b/src/openai/types/realtime/response_mcp_call_in_progress.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpCallInProgress"] + + +class ResponseMcpCallInProgress(BaseModel): + """Returned when an MCP tool call has started and is in progress.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the MCP tool call item.""" + + output_index: int + """The index of the output item in the response.""" + + type: Literal["response.mcp_call.in_progress"] + """The event type, must be `response.mcp_call.in_progress`.""" diff --git a/src/openai/types/realtime/response_output_item_added_event.py b/src/openai/types/realtime/response_output_item_added_event.py new file mode 100644 index 0000000000..abec0d18f1 --- /dev/null +++ b/src/openai/types/realtime/response_output_item_added_event.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ResponseOutputItemAddedEvent"] + + +class ResponseOutputItemAddedEvent(BaseModel): + """Returned when a new Item is created during Response generation.""" + + event_id: str + """The unique ID of the server event.""" + + item: ConversationItem + """A single item within a Realtime conversation.""" + + output_index: int + """The index of the output item in the Response.""" + + response_id: str + """The ID of the Response to which the item belongs.""" + + type: Literal["response.output_item.added"] + """The event type, must be `response.output_item.added`.""" diff --git a/src/openai/types/realtime/response_output_item_done_event.py b/src/openai/types/realtime/response_output_item_done_event.py new file mode 100644 index 0000000000..63936b97d5 --- /dev/null +++ b/src/openai/types/realtime/response_output_item_done_event.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from .conversation_item import ConversationItem + +__all__ = ["ResponseOutputItemDoneEvent"] + + +class ResponseOutputItemDoneEvent(BaseModel): + """Returned when an Item is done streaming. + + Also emitted when a Response is + interrupted, incomplete, or cancelled. + """ + + event_id: str + """The unique ID of the server event.""" + + item: ConversationItem + """A single item within a Realtime conversation.""" + + output_index: int + """The index of the output item in the Response.""" + + response_id: str + """The ID of the Response to which the item belongs.""" + + type: Literal["response.output_item.done"] + """The event type, must be `response.output_item.done`.""" diff --git a/src/openai/types/realtime/response_text_delta_event.py b/src/openai/types/realtime/response_text_delta_event.py new file mode 100644 index 0000000000..b251b7639c --- /dev/null +++ b/src/openai/types/realtime/response_text_delta_event.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseTextDeltaEvent"] + + +class ResponseTextDeltaEvent(BaseModel): + """Returned when the text value of an "output_text" content part is updated.""" + + content_index: int + """The index of the content part in the item's content array.""" + + delta: str + """The text delta.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + type: Literal["response.output_text.delta"] + """The event type, must be `response.output_text.delta`.""" diff --git a/src/openai/types/realtime/response_text_done_event.py b/src/openai/types/realtime/response_text_done_event.py new file mode 100644 index 0000000000..046e520222 --- /dev/null +++ b/src/openai/types/realtime/response_text_done_event.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseTextDoneEvent"] + + +class ResponseTextDoneEvent(BaseModel): + """Returned when the text value of an "output_text" content part is done streaming. + + Also + emitted when a Response is interrupted, incomplete, or cancelled. + """ + + content_index: int + """The index of the content part in the item's content array.""" + + event_id: str + """The unique ID of the server event.""" + + item_id: str + """The ID of the item.""" + + output_index: int + """The index of the output item in the response.""" + + response_id: str + """The ID of the response.""" + + text: str + """The final text content.""" + + type: Literal["response.output_text.done"] + """The event type, must be `response.output_text.done`.""" diff --git a/src/openai/types/realtime/session_created_event.py b/src/openai/types/realtime/session_created_event.py new file mode 100644 index 0000000000..1b8d4a4d81 --- /dev/null +++ b/src/openai/types/realtime/session_created_event.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .realtime_session_create_request import RealtimeSessionCreateRequest +from .realtime_transcription_session_create_request import RealtimeTranscriptionSessionCreateRequest + +__all__ = ["SessionCreatedEvent", "Session"] + +Session: TypeAlias = Union[RealtimeSessionCreateRequest, RealtimeTranscriptionSessionCreateRequest] + + +class SessionCreatedEvent(BaseModel): + """Returned when a Session is created. + + Emitted automatically when a new + connection is established as the first server event. This event will contain + the default Session configuration. + """ + + event_id: str + """The unique ID of the server event.""" + + session: Session + """The session configuration.""" + + type: Literal["session.created"] + """The event type, must be `session.created`.""" diff --git a/src/openai/types/realtime/session_update_event.py b/src/openai/types/realtime/session_update_event.py new file mode 100644 index 0000000000..a8422e4e89 --- /dev/null +++ b/src/openai/types/realtime/session_update_event.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .realtime_session_create_request import RealtimeSessionCreateRequest +from .realtime_transcription_session_create_request import RealtimeTranscriptionSessionCreateRequest + +__all__ = ["SessionUpdateEvent", "Session"] + +Session: TypeAlias = Union[RealtimeSessionCreateRequest, RealtimeTranscriptionSessionCreateRequest] + + +class SessionUpdateEvent(BaseModel): + """ + Send this event to update the session’s configuration. + The client may send this event at any time to update any field + except for `voice` and `model`. `voice` can be updated only if there have been no other audio outputs yet. + + When the server receives a `session.update`, it will respond + with a `session.updated` event showing the full, effective configuration. + Only the fields that are present in the `session.update` are updated. To clear a field like + `instructions`, pass an empty string. To clear a field like `tools`, pass an empty array. + To clear a field like `turn_detection`, pass `null`. + """ + + session: Session + """Update the Realtime session. + + Choose either a realtime session or a transcription session. + """ + + type: Literal["session.update"] + """The event type, must be `session.update`.""" + + event_id: Optional[str] = None + """Optional client-generated ID used to identify this event. + + This is an arbitrary string that a client may assign. It will be passed back if + there is an error with the event, but the corresponding `session.updated` event + will not include it. + """ diff --git a/src/openai/types/realtime/session_update_event_param.py b/src/openai/types/realtime/session_update_event_param.py new file mode 100644 index 0000000000..910e89ca34 --- /dev/null +++ b/src/openai/types/realtime/session_update_event_param.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .realtime_session_create_request_param import RealtimeSessionCreateRequestParam +from .realtime_transcription_session_create_request_param import RealtimeTranscriptionSessionCreateRequestParam + +__all__ = ["SessionUpdateEventParam", "Session"] + +Session: TypeAlias = Union[RealtimeSessionCreateRequestParam, RealtimeTranscriptionSessionCreateRequestParam] + + +class SessionUpdateEventParam(TypedDict, total=False): + """ + Send this event to update the session’s configuration. + The client may send this event at any time to update any field + except for `voice` and `model`. `voice` can be updated only if there have been no other audio outputs yet. + + When the server receives a `session.update`, it will respond + with a `session.updated` event showing the full, effective configuration. + Only the fields that are present in the `session.update` are updated. To clear a field like + `instructions`, pass an empty string. To clear a field like `tools`, pass an empty array. + To clear a field like `turn_detection`, pass `null`. + """ + + session: Required[Session] + """Update the Realtime session. + + Choose either a realtime session or a transcription session. + """ + + type: Required[Literal["session.update"]] + """The event type, must be `session.update`.""" + + event_id: str + """Optional client-generated ID used to identify this event. + + This is an arbitrary string that a client may assign. It will be passed back if + there is an error with the event, but the corresponding `session.updated` event + will not include it. + """ diff --git a/src/openai/types/realtime/session_updated_event.py b/src/openai/types/realtime/session_updated_event.py new file mode 100644 index 0000000000..e68a08d6cc --- /dev/null +++ b/src/openai/types/realtime/session_updated_event.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .realtime_session_create_request import RealtimeSessionCreateRequest +from .realtime_transcription_session_create_request import RealtimeTranscriptionSessionCreateRequest + +__all__ = ["SessionUpdatedEvent", "Session"] + +Session: TypeAlias = Union[RealtimeSessionCreateRequest, RealtimeTranscriptionSessionCreateRequest] + + +class SessionUpdatedEvent(BaseModel): + """ + Returned when a session is updated with a `session.update` event, unless + there is an error. + """ + + event_id: str + """The unique ID of the server event.""" + + session: Session + """The session configuration.""" + + type: Literal["session.updated"] + """The event type, must be `session.updated`.""" diff --git a/src/openai/types/responses/__init__.py b/src/openai/types/responses/__init__.py new file mode 100644 index 0000000000..5e6f45e9b1 --- /dev/null +++ b/src/openai/types/responses/__init__.py @@ -0,0 +1,330 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .tool import Tool as Tool +from .response import Response as Response +from .tool_param import ToolParam as ToolParam +from .custom_tool import CustomTool as CustomTool +from .local_skill import LocalSkill as LocalSkill +from .inline_skill import InlineSkill as InlineSkill +from .computer_tool import ComputerTool as ComputerTool +from .function_tool import FunctionTool as FunctionTool +from .response_item import ResponseItem as ResponseItem +from .container_auto import ContainerAuto as ContainerAuto +from .namespace_tool import NamespaceTool as NamespaceTool +from .response_error import ResponseError as ResponseError +from .response_input import ResponseInput as ResponseInput +from .response_usage import ResponseUsage as ResponseUsage +from .computer_action import ComputerAction as ComputerAction +from .parsed_response import ( + ParsedContent as ParsedContent, + ParsedResponse as ParsedResponse, + ParsedResponseOutputItem as ParsedResponseOutputItem, + ParsedResponseOutputText as ParsedResponseOutputText, + ParsedResponseOutputMessage as ParsedResponseOutputMessage, + ParsedResponseFunctionToolCall as ParsedResponseFunctionToolCall, +) +from .response_prompt import ResponsePrompt as ResponsePrompt +from .response_status import ResponseStatus as ResponseStatus +from .skill_reference import SkillReference as SkillReference +from .tool_choice_mcp import ToolChoiceMcp as ToolChoiceMcp +from .web_search_tool import WebSearchTool as WebSearchTool +from .apply_patch_tool import ApplyPatchTool as ApplyPatchTool +from .file_search_tool import FileSearchTool as FileSearchTool +from .tool_search_tool import ToolSearchTool as ToolSearchTool +from .custom_tool_param import CustomToolParam as CustomToolParam +from .local_environment import LocalEnvironment as LocalEnvironment +from .local_skill_param import LocalSkillParam as LocalSkillParam +from .tool_choice_shell import ToolChoiceShell as ToolChoiceShell +from .tool_choice_types import ToolChoiceTypes as ToolChoiceTypes +from .compacted_response import CompactedResponse as CompactedResponse +from .easy_input_message import EasyInputMessage as EasyInputMessage +from .inline_skill_param import InlineSkillParam as InlineSkillParam +from .response_item_list import ResponseItemList as ResponseItemList +from .tool_choice_custom import ToolChoiceCustom as ToolChoiceCustom +from .computer_tool_param import ComputerToolParam as ComputerToolParam +from .container_reference import ContainerReference as ContainerReference +from .function_shell_tool import FunctionShellTool as FunctionShellTool +from .function_tool_param import FunctionToolParam as FunctionToolParam +from .inline_skill_source import InlineSkillSource as InlineSkillSource +from .response_includable import ResponseIncludable as ResponseIncludable +from .response_input_file import ResponseInputFile as ResponseInputFile +from .response_input_item import ResponseInputItem as ResponseInputItem +from .response_input_text import ResponseInputText as ResponseInputText +from .tool_choice_allowed import ToolChoiceAllowed as ToolChoiceAllowed +from .tool_choice_options import ToolChoiceOptions as ToolChoiceOptions +from .computer_action_list import ComputerActionList as ComputerActionList +from .container_auto_param import ContainerAutoParam as ContainerAutoParam +from .namespace_tool_param import NamespaceToolParam as NamespaceToolParam +from .response_error_event import ResponseErrorEvent as ResponseErrorEvent +from .response_input_audio import ResponseInputAudio as ResponseInputAudio +from .response_input_image import ResponseInputImage as ResponseInputImage +from .response_input_param import ResponseInputParam as ResponseInputParam +from .response_output_item import ResponseOutputItem as ResponseOutputItem +from .response_output_text import ResponseOutputText as ResponseOutputText +from .response_text_config import ResponseTextConfig as ResponseTextConfig +from .tool_choice_function import ToolChoiceFunction as ToolChoiceFunction +from .computer_action_param import ComputerActionParam as ComputerActionParam +from .response_failed_event import ResponseFailedEvent as ResponseFailedEvent +from .response_prompt_param import ResponsePromptParam as ResponsePromptParam +from .response_queued_event import ResponseQueuedEvent as ResponseQueuedEvent +from .response_stream_event import ResponseStreamEvent as ResponseStreamEvent +from .skill_reference_param import SkillReferenceParam as SkillReferenceParam +from .tool_choice_mcp_param import ToolChoiceMcpParam as ToolChoiceMcpParam +from .web_search_tool_param import WebSearchToolParam as WebSearchToolParam +from .apply_patch_tool_param import ApplyPatchToolParam as ApplyPatchToolParam +from .file_search_tool_param import FileSearchToolParam as FileSearchToolParam +from .input_item_list_params import InputItemListParams as InputItemListParams +from .response_create_params import ResponseCreateParams as ResponseCreateParams +from .response_created_event import ResponseCreatedEvent as ResponseCreatedEvent +from .response_input_content import ResponseInputContent as ResponseInputContent +from .responses_client_event import ResponsesClientEvent as ResponsesClientEvent +from .responses_server_event import ResponsesServerEvent as ResponsesServerEvent +from .tool_search_tool_param import ToolSearchToolParam as ToolSearchToolParam +from .local_environment_param import LocalEnvironmentParam as LocalEnvironmentParam +from .response_compact_params import ResponseCompactParams as ResponseCompactParams +from .response_output_message import ResponseOutputMessage as ResponseOutputMessage +from .response_output_refusal import ResponseOutputRefusal as ResponseOutputRefusal +from .response_reasoning_item import ResponseReasoningItem as ResponseReasoningItem +from .tool_choice_apply_patch import ToolChoiceApplyPatch as ToolChoiceApplyPatch +from .tool_choice_shell_param import ToolChoiceShellParam as ToolChoiceShellParam +from .tool_choice_types_param import ToolChoiceTypesParam as ToolChoiceTypesParam +from .web_search_preview_tool import WebSearchPreviewTool as WebSearchPreviewTool +from .easy_input_message_param import EasyInputMessageParam as EasyInputMessageParam +from .input_token_count_params import InputTokenCountParams as InputTokenCountParams +from .response_compaction_item import ResponseCompactionItem as ResponseCompactionItem +from .response_completed_event import ResponseCompletedEvent as ResponseCompletedEvent +from .response_retrieve_params import ResponseRetrieveParams as ResponseRetrieveParams +from .response_text_done_event import ResponseTextDoneEvent as ResponseTextDoneEvent +from .tool_choice_custom_param import ToolChoiceCustomParam as ToolChoiceCustomParam +from .computer_use_preview_tool import ComputerUsePreviewTool as ComputerUsePreviewTool +from .container_reference_param import ContainerReferenceParam as ContainerReferenceParam +from .function_shell_tool_param import FunctionShellToolParam as FunctionShellToolParam +from .inline_skill_source_param import InlineSkillSourceParam as InlineSkillSourceParam +from .response_audio_done_event import ResponseAudioDoneEvent as ResponseAudioDoneEvent +from .response_custom_tool_call import ResponseCustomToolCall as ResponseCustomToolCall +from .response_incomplete_event import ResponseIncompleteEvent as ResponseIncompleteEvent +from .response_input_file_param import ResponseInputFileParam as ResponseInputFileParam +from .response_input_item_param import ResponseInputItemParam as ResponseInputItemParam +from .response_input_text_param import ResponseInputTextParam as ResponseInputTextParam +from .response_text_delta_event import ResponseTextDeltaEvent as ResponseTextDeltaEvent +from .response_tool_search_call import ResponseToolSearchCall as ResponseToolSearchCall +from .tool_choice_allowed_param import ToolChoiceAllowedParam as ToolChoiceAllowedParam +from .computer_action_list_param import ComputerActionListParam as ComputerActionListParam +from .input_token_count_response import InputTokenCountResponse as InputTokenCountResponse +from .response_audio_delta_event import ResponseAudioDeltaEvent as ResponseAudioDeltaEvent +from .response_in_progress_event import ResponseInProgressEvent as ResponseInProgressEvent +from .response_input_audio_param import ResponseInputAudioParam as ResponseInputAudioParam +from .response_input_image_param import ResponseInputImageParam as ResponseInputImageParam +from .response_local_environment import ResponseLocalEnvironment as ResponseLocalEnvironment +from .response_output_text_param import ResponseOutputTextParam as ResponseOutputTextParam +from .response_text_config_param import ResponseTextConfigParam as ResponseTextConfigParam +from .tool_choice_function_param import ToolChoiceFunctionParam as ToolChoiceFunctionParam +from .response_computer_tool_call import ResponseComputerToolCall as ResponseComputerToolCall +from .response_conversation_param import ResponseConversationParam as ResponseConversationParam +from .response_format_text_config import ResponseFormatTextConfig as ResponseFormatTextConfig +from .response_function_tool_call import ResponseFunctionToolCall as ResponseFunctionToolCall +from .response_input_file_content import ResponseInputFileContent as ResponseInputFileContent +from .response_input_message_item import ResponseInputMessageItem as ResponseInputMessageItem +from .response_input_text_content import ResponseInputTextContent as ResponseInputTextContent +from .response_refusal_done_event import ResponseRefusalDoneEvent as ResponseRefusalDoneEvent +from .response_container_reference import ResponseContainerReference as ResponseContainerReference +from .response_function_web_search import ResponseFunctionWebSearch as ResponseFunctionWebSearch +from .response_input_content_param import ResponseInputContentParam as ResponseInputContentParam +from .response_input_image_content import ResponseInputImageContent as ResponseInputImageContent +from .response_refusal_delta_event import ResponseRefusalDeltaEvent as ResponseRefusalDeltaEvent +from .responses_client_event_param import ResponsesClientEventParam as ResponsesClientEventParam +from .response_output_message_param import ResponseOutputMessageParam as ResponseOutputMessageParam +from .response_output_refusal_param import ResponseOutputRefusalParam as ResponseOutputRefusalParam +from .response_reasoning_item_param import ResponseReasoningItemParam as ResponseReasoningItemParam +from .tool_choice_apply_patch_param import ToolChoiceApplyPatchParam as ToolChoiceApplyPatchParam +from .web_search_preview_tool_param import WebSearchPreviewToolParam as WebSearchPreviewToolParam +from .response_apply_patch_tool_call import ResponseApplyPatchToolCall as ResponseApplyPatchToolCall +from .response_compaction_item_param import ResponseCompactionItemParam as ResponseCompactionItemParam +from .response_custom_tool_call_item import ResponseCustomToolCallItem as ResponseCustomToolCallItem +from .response_file_search_tool_call import ResponseFileSearchToolCall as ResponseFileSearchToolCall +from .response_mcp_call_failed_event import ResponseMcpCallFailedEvent as ResponseMcpCallFailedEvent +from .computer_use_preview_tool_param import ComputerUsePreviewToolParam as ComputerUsePreviewToolParam +from .response_custom_tool_call_param import ResponseCustomToolCallParam as ResponseCustomToolCallParam +from .response_output_item_done_event import ResponseOutputItemDoneEvent as ResponseOutputItemDoneEvent +from .response_content_part_done_event import ResponseContentPartDoneEvent as ResponseContentPartDoneEvent +from .response_custom_tool_call_output import ResponseCustomToolCallOutput as ResponseCustomToolCallOutput +from .response_function_tool_call_item import ResponseFunctionToolCallItem as ResponseFunctionToolCallItem +from .response_output_item_added_event import ResponseOutputItemAddedEvent as ResponseOutputItemAddedEvent +from .response_tool_search_output_item import ResponseToolSearchOutputItem as ResponseToolSearchOutputItem +from .container_network_policy_disabled import ContainerNetworkPolicyDisabled as ContainerNetworkPolicyDisabled +from .response_computer_tool_call_param import ResponseComputerToolCallParam as ResponseComputerToolCallParam +from .response_content_part_added_event import ResponseContentPartAddedEvent as ResponseContentPartAddedEvent +from .response_conversation_param_param import ResponseConversationParamParam as ResponseConversationParamParam +from .response_format_text_config_param import ResponseFormatTextConfigParam as ResponseFormatTextConfigParam +from .response_function_shell_tool_call import ResponseFunctionShellToolCall as ResponseFunctionShellToolCall +from .response_function_tool_call_param import ResponseFunctionToolCallParam as ResponseFunctionToolCallParam +from .response_input_file_content_param import ResponseInputFileContentParam as ResponseInputFileContentParam +from .response_input_text_content_param import ResponseInputTextContentParam as ResponseInputTextContentParam +from .response_mcp_call_completed_event import ResponseMcpCallCompletedEvent as ResponseMcpCallCompletedEvent +from .container_network_policy_allowlist import ContainerNetworkPolicyAllowlist as ContainerNetworkPolicyAllowlist +from .response_function_call_output_item import ResponseFunctionCallOutputItem as ResponseFunctionCallOutputItem +from .response_function_web_search_param import ResponseFunctionWebSearchParam as ResponseFunctionWebSearchParam +from .response_input_image_content_param import ResponseInputImageContentParam as ResponseInputImageContentParam +from .response_reasoning_text_done_event import ResponseReasoningTextDoneEvent as ResponseReasoningTextDoneEvent +from .response_code_interpreter_tool_call import ResponseCodeInterpreterToolCall as ResponseCodeInterpreterToolCall +from .response_input_message_content_list import ResponseInputMessageContentList as ResponseInputMessageContentList +from .response_mcp_call_in_progress_event import ResponseMcpCallInProgressEvent as ResponseMcpCallInProgressEvent +from .response_reasoning_text_delta_event import ResponseReasoningTextDeltaEvent as ResponseReasoningTextDeltaEvent +from .response_audio_transcript_done_event import ResponseAudioTranscriptDoneEvent as ResponseAudioTranscriptDoneEvent +from .response_compaction_item_param_param import ResponseCompactionItemParamParam as ResponseCompactionItemParamParam +from .response_file_search_tool_call_param import ResponseFileSearchToolCallParam as ResponseFileSearchToolCallParam +from .response_mcp_list_tools_failed_event import ResponseMcpListToolsFailedEvent as ResponseMcpListToolsFailedEvent +from .response_apply_patch_tool_call_output import ResponseApplyPatchToolCallOutput as ResponseApplyPatchToolCallOutput +from .response_audio_transcript_delta_event import ( + ResponseAudioTranscriptDeltaEvent as ResponseAudioTranscriptDeltaEvent, +) +from .response_custom_tool_call_output_item import ResponseCustomToolCallOutputItem as ResponseCustomToolCallOutputItem +from .container_network_policy_domain_secret import ( + ContainerNetworkPolicyDomainSecret as ContainerNetworkPolicyDomainSecret, +) +from .response_custom_tool_call_output_param import ( + ResponseCustomToolCallOutputParam as ResponseCustomToolCallOutputParam, +) +from .response_mcp_call_arguments_done_event import ( + ResponseMcpCallArgumentsDoneEvent as ResponseMcpCallArgumentsDoneEvent, +) +from .response_tool_search_output_item_param import ( + ResponseToolSearchOutputItemParam as ResponseToolSearchOutputItemParam, +) +from .container_network_policy_disabled_param import ( + ContainerNetworkPolicyDisabledParam as ContainerNetworkPolicyDisabledParam, +) +from .response_computer_tool_call_output_item import ( + ResponseComputerToolCallOutputItem as ResponseComputerToolCallOutputItem, +) +from .response_format_text_json_schema_config import ( + ResponseFormatTextJSONSchemaConfig as ResponseFormatTextJSONSchemaConfig, +) +from .response_function_call_output_item_list import ( + ResponseFunctionCallOutputItemList as ResponseFunctionCallOutputItemList, +) +from .response_function_tool_call_output_item import ( + ResponseFunctionToolCallOutputItem as ResponseFunctionToolCallOutputItem, +) +from .response_image_gen_call_completed_event import ( + ResponseImageGenCallCompletedEvent as ResponseImageGenCallCompletedEvent, +) +from .response_mcp_call_arguments_delta_event import ( + ResponseMcpCallArgumentsDeltaEvent as ResponseMcpCallArgumentsDeltaEvent, +) +from .response_mcp_list_tools_completed_event import ( + ResponseMcpListToolsCompletedEvent as ResponseMcpListToolsCompletedEvent, +) +from .container_network_policy_allowlist_param import ( + ContainerNetworkPolicyAllowlistParam as ContainerNetworkPolicyAllowlistParam, +) +from .response_function_call_output_item_param import ( + ResponseFunctionCallOutputItemParam as ResponseFunctionCallOutputItemParam, +) +from .response_function_shell_tool_call_output import ( + ResponseFunctionShellToolCallOutput as ResponseFunctionShellToolCallOutput, +) +from .response_image_gen_call_generating_event import ( + ResponseImageGenCallGeneratingEvent as ResponseImageGenCallGeneratingEvent, +) +from .response_web_search_call_completed_event import ( + ResponseWebSearchCallCompletedEvent as ResponseWebSearchCallCompletedEvent, +) +from .response_web_search_call_searching_event import ( + ResponseWebSearchCallSearchingEvent as ResponseWebSearchCallSearchingEvent, +) +from .response_code_interpreter_tool_call_param import ( + ResponseCodeInterpreterToolCallParam as ResponseCodeInterpreterToolCallParam, +) +from .response_file_search_call_completed_event import ( + ResponseFileSearchCallCompletedEvent as ResponseFileSearchCallCompletedEvent, +) +from .response_file_search_call_searching_event import ( + ResponseFileSearchCallSearchingEvent as ResponseFileSearchCallSearchingEvent, +) +from .response_image_gen_call_in_progress_event import ( + ResponseImageGenCallInProgressEvent as ResponseImageGenCallInProgressEvent, +) +from .response_input_message_content_list_param import ( + ResponseInputMessageContentListParam as ResponseInputMessageContentListParam, +) +from .response_mcp_list_tools_in_progress_event import ( + ResponseMcpListToolsInProgressEvent as ResponseMcpListToolsInProgressEvent, +) +from .response_custom_tool_call_input_done_event import ( + ResponseCustomToolCallInputDoneEvent as ResponseCustomToolCallInputDoneEvent, +) +from .response_reasoning_summary_part_done_event import ( + ResponseReasoningSummaryPartDoneEvent as ResponseReasoningSummaryPartDoneEvent, +) +from .response_reasoning_summary_text_done_event import ( + ResponseReasoningSummaryTextDoneEvent as ResponseReasoningSummaryTextDoneEvent, +) +from .response_web_search_call_in_progress_event import ( + ResponseWebSearchCallInProgressEvent as ResponseWebSearchCallInProgressEvent, +) +from .response_custom_tool_call_input_delta_event import ( + ResponseCustomToolCallInputDeltaEvent as ResponseCustomToolCallInputDeltaEvent, +) +from .response_file_search_call_in_progress_event import ( + ResponseFileSearchCallInProgressEvent as ResponseFileSearchCallInProgressEvent, +) +from .response_function_call_arguments_done_event import ( + ResponseFunctionCallArgumentsDoneEvent as ResponseFunctionCallArgumentsDoneEvent, +) +from .response_function_shell_call_output_content import ( + ResponseFunctionShellCallOutputContent as ResponseFunctionShellCallOutputContent, +) +from .response_image_gen_call_partial_image_event import ( + ResponseImageGenCallPartialImageEvent as ResponseImageGenCallPartialImageEvent, +) +from .response_output_text_annotation_added_event import ( + ResponseOutputTextAnnotationAddedEvent as ResponseOutputTextAnnotationAddedEvent, +) +from .response_reasoning_summary_part_added_event import ( + ResponseReasoningSummaryPartAddedEvent as ResponseReasoningSummaryPartAddedEvent, +) +from .response_reasoning_summary_text_delta_event import ( + ResponseReasoningSummaryTextDeltaEvent as ResponseReasoningSummaryTextDeltaEvent, +) +from .container_network_policy_domain_secret_param import ( + ContainerNetworkPolicyDomainSecretParam as ContainerNetworkPolicyDomainSecretParam, +) +from .response_function_call_arguments_delta_event import ( + ResponseFunctionCallArgumentsDeltaEvent as ResponseFunctionCallArgumentsDeltaEvent, +) +from .response_tool_search_output_item_param_param import ( + ResponseToolSearchOutputItemParamParam as ResponseToolSearchOutputItemParamParam, +) +from .response_computer_tool_call_output_screenshot import ( + ResponseComputerToolCallOutputScreenshot as ResponseComputerToolCallOutputScreenshot, +) +from .response_format_text_json_schema_config_param import ( + ResponseFormatTextJSONSchemaConfigParam as ResponseFormatTextJSONSchemaConfigParam, +) +from .response_function_call_output_item_list_param import ( + ResponseFunctionCallOutputItemListParam as ResponseFunctionCallOutputItemListParam, +) +from .response_code_interpreter_call_code_done_event import ( + ResponseCodeInterpreterCallCodeDoneEvent as ResponseCodeInterpreterCallCodeDoneEvent, +) +from .response_code_interpreter_call_completed_event import ( + ResponseCodeInterpreterCallCompletedEvent as ResponseCodeInterpreterCallCompletedEvent, +) +from .response_code_interpreter_call_code_delta_event import ( + ResponseCodeInterpreterCallCodeDeltaEvent as ResponseCodeInterpreterCallCodeDeltaEvent, +) +from .response_code_interpreter_call_in_progress_event import ( + ResponseCodeInterpreterCallInProgressEvent as ResponseCodeInterpreterCallInProgressEvent, +) +from .response_code_interpreter_call_interpreting_event import ( + ResponseCodeInterpreterCallInterpretingEvent as ResponseCodeInterpreterCallInterpretingEvent, +) +from .response_function_shell_call_output_content_param import ( + ResponseFunctionShellCallOutputContentParam as ResponseFunctionShellCallOutputContentParam, +) +from .response_computer_tool_call_output_screenshot_param import ( + ResponseComputerToolCallOutputScreenshotParam as ResponseComputerToolCallOutputScreenshotParam, +) diff --git a/src/openai/types/responses/apply_patch_tool.py b/src/openai/types/responses/apply_patch_tool.py new file mode 100644 index 0000000000..f2ed245d10 --- /dev/null +++ b/src/openai/types/responses/apply_patch_tool.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ApplyPatchTool"] + + +class ApplyPatchTool(BaseModel): + """Allows the assistant to create, delete, or update files using unified diffs.""" + + type: Literal["apply_patch"] + """The type of the tool. Always `apply_patch`.""" diff --git a/src/openai/types/responses/apply_patch_tool_param.py b/src/openai/types/responses/apply_patch_tool_param.py new file mode 100644 index 0000000000..2e0a809099 --- /dev/null +++ b/src/openai/types/responses/apply_patch_tool_param.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ApplyPatchToolParam"] + + +class ApplyPatchToolParam(TypedDict, total=False): + """Allows the assistant to create, delete, or update files using unified diffs.""" + + type: Required[Literal["apply_patch"]] + """The type of the tool. Always `apply_patch`.""" diff --git a/src/openai/types/responses/compacted_response.py b/src/openai/types/responses/compacted_response.py new file mode 100644 index 0000000000..5b333b83c0 --- /dev/null +++ b/src/openai/types/responses/compacted_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import Literal + +from ..._models import BaseModel +from .response_usage import ResponseUsage +from .response_output_item import ResponseOutputItem + +__all__ = ["CompactedResponse"] + + +class CompactedResponse(BaseModel): + id: str + """The unique identifier for the compacted response.""" + + created_at: int + """Unix timestamp (in seconds) when the compacted conversation was created.""" + + object: Literal["response.compaction"] + """The object type. Always `response.compaction`.""" + + output: List[ResponseOutputItem] + """The compacted list of output items. + + This is a list of all user messages, followed by a single compaction item. + """ + + usage: ResponseUsage + """ + Token accounting for the compaction pass, including cached, reasoning, and total + tokens. + """ diff --git a/src/openai/types/responses/computer_action.py b/src/openai/types/responses/computer_action.py new file mode 100644 index 0000000000..f7a21d2ac4 --- /dev/null +++ b/src/openai/types/responses/computer_action.py @@ -0,0 +1,196 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = [ + "ComputerAction", + "Click", + "DoubleClick", + "Drag", + "DragPath", + "Keypress", + "Move", + "Screenshot", + "Scroll", + "Type", + "Wait", +] + + +class Click(BaseModel): + """A click action.""" + + button: Literal["left", "right", "wheel", "back", "forward"] + """Indicates which mouse button was pressed during the click. + + One of `left`, `right`, `wheel`, `back`, or `forward`. + """ + + type: Literal["click"] + """Specifies the event type. For a click action, this property is always `click`.""" + + x: int + """The x-coordinate where the click occurred.""" + + y: int + """The y-coordinate where the click occurred.""" + + keys: Optional[List[str]] = None + """The keys being held while clicking.""" + + +class DoubleClick(BaseModel): + """A double click action.""" + + keys: Optional[List[str]] = None + """The keys being held while double-clicking.""" + + type: Literal["double_click"] + """Specifies the event type. + + For a double click action, this property is always set to `double_click`. + """ + + x: int + """The x-coordinate where the double click occurred.""" + + y: int + """The y-coordinate where the double click occurred.""" + + +class DragPath(BaseModel): + """An x/y coordinate pair, e.g. `{ x: 100, y: 200 }`.""" + + x: int + """The x-coordinate.""" + + y: int + """The y-coordinate.""" + + +class Drag(BaseModel): + """A drag action.""" + + path: List[DragPath] + """An array of coordinates representing the path of the drag action. + + Coordinates will appear as an array of objects, eg + + ``` + [ + { x: 100, y: 200 }, + { x: 200, y: 300 } + ] + ``` + """ + + type: Literal["drag"] + """Specifies the event type. + + For a drag action, this property is always set to `drag`. + """ + + keys: Optional[List[str]] = None + """The keys being held while dragging the mouse.""" + + +class Keypress(BaseModel): + """A collection of keypresses the model would like to perform.""" + + keys: List[str] + """The combination of keys the model is requesting to be pressed. + + This is an array of strings, each representing a key. + """ + + type: Literal["keypress"] + """Specifies the event type. + + For a keypress action, this property is always set to `keypress`. + """ + + +class Move(BaseModel): + """A mouse move action.""" + + type: Literal["move"] + """Specifies the event type. + + For a move action, this property is always set to `move`. + """ + + x: int + """The x-coordinate to move to.""" + + y: int + """The y-coordinate to move to.""" + + keys: Optional[List[str]] = None + """The keys being held while moving the mouse.""" + + +class Screenshot(BaseModel): + """A screenshot action.""" + + type: Literal["screenshot"] + """Specifies the event type. + + For a screenshot action, this property is always set to `screenshot`. + """ + + +class Scroll(BaseModel): + """A scroll action.""" + + scroll_x: int + """The horizontal scroll distance.""" + + scroll_y: int + """The vertical scroll distance.""" + + type: Literal["scroll"] + """Specifies the event type. + + For a scroll action, this property is always set to `scroll`. + """ + + x: int + """The x-coordinate where the scroll occurred.""" + + y: int + """The y-coordinate where the scroll occurred.""" + + keys: Optional[List[str]] = None + """The keys being held while scrolling.""" + + +class Type(BaseModel): + """An action to type in text.""" + + text: str + """The text to type.""" + + type: Literal["type"] + """Specifies the event type. + + For a type action, this property is always set to `type`. + """ + + +class Wait(BaseModel): + """A wait action.""" + + type: Literal["wait"] + """Specifies the event type. + + For a wait action, this property is always set to `wait`. + """ + + +ComputerAction: TypeAlias = Annotated[ + Union[Click, DoubleClick, Drag, Keypress, Move, Screenshot, Scroll, Type, Wait], PropertyInfo(discriminator="type") +] diff --git a/src/openai/types/responses/computer_action_list.py b/src/openai/types/responses/computer_action_list.py new file mode 100644 index 0000000000..0198c6e866 --- /dev/null +++ b/src/openai/types/responses/computer_action_list.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .computer_action import ComputerAction + +__all__ = ["ComputerActionList"] + +ComputerActionList: TypeAlias = List[ComputerAction] diff --git a/src/openai/types/responses/computer_action_list_param.py b/src/openai/types/responses/computer_action_list_param.py new file mode 100644 index 0000000000..66a03520f7 --- /dev/null +++ b/src/openai/types/responses/computer_action_list_param.py @@ -0,0 +1,198 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr + +__all__ = [ + "ComputerActionListParam", + "ComputerActionParam", + "Click", + "DoubleClick", + "Drag", + "DragPath", + "Keypress", + "Move", + "Screenshot", + "Scroll", + "Type", + "Wait", +] + + +class Click(TypedDict, total=False): + """A click action.""" + + button: Required[Literal["left", "right", "wheel", "back", "forward"]] + """Indicates which mouse button was pressed during the click. + + One of `left`, `right`, `wheel`, `back`, or `forward`. + """ + + type: Required[Literal["click"]] + """Specifies the event type. For a click action, this property is always `click`.""" + + x: Required[int] + """The x-coordinate where the click occurred.""" + + y: Required[int] + """The y-coordinate where the click occurred.""" + + keys: Optional[SequenceNotStr[str]] + """The keys being held while clicking.""" + + +class DoubleClick(TypedDict, total=False): + """A double click action.""" + + keys: Required[Optional[SequenceNotStr[str]]] + """The keys being held while double-clicking.""" + + type: Required[Literal["double_click"]] + """Specifies the event type. + + For a double click action, this property is always set to `double_click`. + """ + + x: Required[int] + """The x-coordinate where the double click occurred.""" + + y: Required[int] + """The y-coordinate where the double click occurred.""" + + +class DragPath(TypedDict, total=False): + """An x/y coordinate pair, e.g. `{ x: 100, y: 200 }`.""" + + x: Required[int] + """The x-coordinate.""" + + y: Required[int] + """The y-coordinate.""" + + +class Drag(TypedDict, total=False): + """A drag action.""" + + path: Required[Iterable[DragPath]] + """An array of coordinates representing the path of the drag action. + + Coordinates will appear as an array of objects, eg + + ``` + [ + { x: 100, y: 200 }, + { x: 200, y: 300 } + ] + ``` + """ + + type: Required[Literal["drag"]] + """Specifies the event type. + + For a drag action, this property is always set to `drag`. + """ + + keys: Optional[SequenceNotStr[str]] + """The keys being held while dragging the mouse.""" + + +class Keypress(TypedDict, total=False): + """A collection of keypresses the model would like to perform.""" + + keys: Required[SequenceNotStr[str]] + """The combination of keys the model is requesting to be pressed. + + This is an array of strings, each representing a key. + """ + + type: Required[Literal["keypress"]] + """Specifies the event type. + + For a keypress action, this property is always set to `keypress`. + """ + + +class Move(TypedDict, total=False): + """A mouse move action.""" + + type: Required[Literal["move"]] + """Specifies the event type. + + For a move action, this property is always set to `move`. + """ + + x: Required[int] + """The x-coordinate to move to.""" + + y: Required[int] + """The y-coordinate to move to.""" + + keys: Optional[SequenceNotStr[str]] + """The keys being held while moving the mouse.""" + + +class Screenshot(TypedDict, total=False): + """A screenshot action.""" + + type: Required[Literal["screenshot"]] + """Specifies the event type. + + For a screenshot action, this property is always set to `screenshot`. + """ + + +class Scroll(TypedDict, total=False): + """A scroll action.""" + + scroll_x: Required[int] + """The horizontal scroll distance.""" + + scroll_y: Required[int] + """The vertical scroll distance.""" + + type: Required[Literal["scroll"]] + """Specifies the event type. + + For a scroll action, this property is always set to `scroll`. + """ + + x: Required[int] + """The x-coordinate where the scroll occurred.""" + + y: Required[int] + """The y-coordinate where the scroll occurred.""" + + keys: Optional[SequenceNotStr[str]] + """The keys being held while scrolling.""" + + +class Type(TypedDict, total=False): + """An action to type in text.""" + + text: Required[str] + """The text to type.""" + + type: Required[Literal["type"]] + """Specifies the event type. + + For a type action, this property is always set to `type`. + """ + + +class Wait(TypedDict, total=False): + """A wait action.""" + + type: Required[Literal["wait"]] + """Specifies the event type. + + For a wait action, this property is always set to `wait`. + """ + + +ComputerActionParam: TypeAlias = Union[Click, DoubleClick, Drag, Keypress, Move, Screenshot, Scroll, Type, Wait] + +ComputerActionListParam: TypeAlias = List[ComputerActionParam] diff --git a/src/openai/types/responses/computer_action_param.py b/src/openai/types/responses/computer_action_param.py new file mode 100644 index 0000000000..f60c72b1b4 --- /dev/null +++ b/src/openai/types/responses/computer_action_param.py @@ -0,0 +1,195 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr + +__all__ = [ + "ComputerActionParam", + "Click", + "DoubleClick", + "Drag", + "DragPath", + "Keypress", + "Move", + "Screenshot", + "Scroll", + "Type", + "Wait", +] + + +class Click(TypedDict, total=False): + """A click action.""" + + button: Required[Literal["left", "right", "wheel", "back", "forward"]] + """Indicates which mouse button was pressed during the click. + + One of `left`, `right`, `wheel`, `back`, or `forward`. + """ + + type: Required[Literal["click"]] + """Specifies the event type. For a click action, this property is always `click`.""" + + x: Required[int] + """The x-coordinate where the click occurred.""" + + y: Required[int] + """The y-coordinate where the click occurred.""" + + keys: Optional[SequenceNotStr[str]] + """The keys being held while clicking.""" + + +class DoubleClick(TypedDict, total=False): + """A double click action.""" + + keys: Required[Optional[SequenceNotStr[str]]] + """The keys being held while double-clicking.""" + + type: Required[Literal["double_click"]] + """Specifies the event type. + + For a double click action, this property is always set to `double_click`. + """ + + x: Required[int] + """The x-coordinate where the double click occurred.""" + + y: Required[int] + """The y-coordinate where the double click occurred.""" + + +class DragPath(TypedDict, total=False): + """An x/y coordinate pair, e.g. `{ x: 100, y: 200 }`.""" + + x: Required[int] + """The x-coordinate.""" + + y: Required[int] + """The y-coordinate.""" + + +class Drag(TypedDict, total=False): + """A drag action.""" + + path: Required[Iterable[DragPath]] + """An array of coordinates representing the path of the drag action. + + Coordinates will appear as an array of objects, eg + + ``` + [ + { x: 100, y: 200 }, + { x: 200, y: 300 } + ] + ``` + """ + + type: Required[Literal["drag"]] + """Specifies the event type. + + For a drag action, this property is always set to `drag`. + """ + + keys: Optional[SequenceNotStr[str]] + """The keys being held while dragging the mouse.""" + + +class Keypress(TypedDict, total=False): + """A collection of keypresses the model would like to perform.""" + + keys: Required[SequenceNotStr[str]] + """The combination of keys the model is requesting to be pressed. + + This is an array of strings, each representing a key. + """ + + type: Required[Literal["keypress"]] + """Specifies the event type. + + For a keypress action, this property is always set to `keypress`. + """ + + +class Move(TypedDict, total=False): + """A mouse move action.""" + + type: Required[Literal["move"]] + """Specifies the event type. + + For a move action, this property is always set to `move`. + """ + + x: Required[int] + """The x-coordinate to move to.""" + + y: Required[int] + """The y-coordinate to move to.""" + + keys: Optional[SequenceNotStr[str]] + """The keys being held while moving the mouse.""" + + +class Screenshot(TypedDict, total=False): + """A screenshot action.""" + + type: Required[Literal["screenshot"]] + """Specifies the event type. + + For a screenshot action, this property is always set to `screenshot`. + """ + + +class Scroll(TypedDict, total=False): + """A scroll action.""" + + scroll_x: Required[int] + """The horizontal scroll distance.""" + + scroll_y: Required[int] + """The vertical scroll distance.""" + + type: Required[Literal["scroll"]] + """Specifies the event type. + + For a scroll action, this property is always set to `scroll`. + """ + + x: Required[int] + """The x-coordinate where the scroll occurred.""" + + y: Required[int] + """The y-coordinate where the scroll occurred.""" + + keys: Optional[SequenceNotStr[str]] + """The keys being held while scrolling.""" + + +class Type(TypedDict, total=False): + """An action to type in text.""" + + text: Required[str] + """The text to type.""" + + type: Required[Literal["type"]] + """Specifies the event type. + + For a type action, this property is always set to `type`. + """ + + +class Wait(TypedDict, total=False): + """A wait action.""" + + type: Required[Literal["wait"]] + """Specifies the event type. + + For a wait action, this property is always set to `wait`. + """ + + +ComputerActionParam: TypeAlias = Union[Click, DoubleClick, Drag, Keypress, Move, Screenshot, Scroll, Type, Wait] diff --git a/src/openai/types/responses/computer_tool.py b/src/openai/types/responses/computer_tool.py new file mode 100644 index 0000000000..392faa9e79 --- /dev/null +++ b/src/openai/types/responses/computer_tool.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ComputerTool"] + + +class ComputerTool(BaseModel): + """A tool that controls a virtual computer. + + Learn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use). + """ + + type: Literal["computer"] + """The type of the computer tool. Always `computer`.""" diff --git a/src/openai/types/responses/computer_tool_param.py b/src/openai/types/responses/computer_tool_param.py new file mode 100644 index 0000000000..b5931dea4f --- /dev/null +++ b/src/openai/types/responses/computer_tool_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ComputerToolParam"] + + +class ComputerToolParam(TypedDict, total=False): + """A tool that controls a virtual computer. + + Learn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use). + """ + + type: Required[Literal["computer"]] + """The type of the computer tool. Always `computer`.""" diff --git a/src/openai/types/responses/computer_use_preview_tool.py b/src/openai/types/responses/computer_use_preview_tool.py new file mode 100644 index 0000000000..686860e2cf --- /dev/null +++ b/src/openai/types/responses/computer_use_preview_tool.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ComputerUsePreviewTool"] + + +class ComputerUsePreviewTool(BaseModel): + """A tool that controls a virtual computer. + + Learn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use). + """ + + display_height: int + """The height of the computer display.""" + + display_width: int + """The width of the computer display.""" + + environment: Literal["windows", "mac", "linux", "ubuntu", "browser"] + """The type of computer environment to control.""" + + type: Literal["computer_use_preview"] + """The type of the computer use tool. Always `computer_use_preview`.""" diff --git a/src/openai/types/responses/computer_use_preview_tool_param.py b/src/openai/types/responses/computer_use_preview_tool_param.py new file mode 100644 index 0000000000..2611c7297f --- /dev/null +++ b/src/openai/types/responses/computer_use_preview_tool_param.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ComputerUsePreviewToolParam"] + + +class ComputerUsePreviewToolParam(TypedDict, total=False): + """A tool that controls a virtual computer. + + Learn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use). + """ + + display_height: Required[int] + """The height of the computer display.""" + + display_width: Required[int] + """The width of the computer display.""" + + environment: Required[Literal["windows", "mac", "linux", "ubuntu", "browser"]] + """The type of computer environment to control.""" + + type: Required[Literal["computer_use_preview"]] + """The type of the computer use tool. Always `computer_use_preview`.""" diff --git a/src/openai/types/responses/container_auto.py b/src/openai/types/responses/container_auto.py new file mode 100644 index 0000000000..06b8a285bf --- /dev/null +++ b/src/openai/types/responses/container_auto.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .inline_skill import InlineSkill +from .skill_reference import SkillReference +from .container_network_policy_disabled import ContainerNetworkPolicyDisabled +from .container_network_policy_allowlist import ContainerNetworkPolicyAllowlist + +__all__ = ["ContainerAuto", "NetworkPolicy", "Skill"] + +NetworkPolicy: TypeAlias = Annotated[ + Union[ContainerNetworkPolicyDisabled, ContainerNetworkPolicyAllowlist], PropertyInfo(discriminator="type") +] + +Skill: TypeAlias = Annotated[Union[SkillReference, InlineSkill], PropertyInfo(discriminator="type")] + + +class ContainerAuto(BaseModel): + type: Literal["container_auto"] + """Automatically creates a container for this request""" + + file_ids: Optional[List[str]] = None + """An optional list of uploaded files to make available to your code.""" + + memory_limit: Optional[Literal["1g", "4g", "16g", "64g"]] = None + """The memory limit for the container.""" + + network_policy: Optional[NetworkPolicy] = None + """Network access policy for the container.""" + + skills: Optional[List[Skill]] = None + """An optional list of skills referenced by id or inline data.""" diff --git a/src/openai/types/responses/container_auto_param.py b/src/openai/types/responses/container_auto_param.py new file mode 100644 index 0000000000..b9e8bb5223 --- /dev/null +++ b/src/openai/types/responses/container_auto_param.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from .inline_skill_param import InlineSkillParam +from .skill_reference_param import SkillReferenceParam +from .container_network_policy_disabled_param import ContainerNetworkPolicyDisabledParam +from .container_network_policy_allowlist_param import ContainerNetworkPolicyAllowlistParam + +__all__ = ["ContainerAutoParam", "NetworkPolicy", "Skill"] + +NetworkPolicy: TypeAlias = Union[ContainerNetworkPolicyDisabledParam, ContainerNetworkPolicyAllowlistParam] + +Skill: TypeAlias = Union[SkillReferenceParam, InlineSkillParam] + + +class ContainerAutoParam(TypedDict, total=False): + type: Required[Literal["container_auto"]] + """Automatically creates a container for this request""" + + file_ids: SequenceNotStr[str] + """An optional list of uploaded files to make available to your code.""" + + memory_limit: Optional[Literal["1g", "4g", "16g", "64g"]] + """The memory limit for the container.""" + + network_policy: NetworkPolicy + """Network access policy for the container.""" + + skills: Iterable[Skill] + """An optional list of skills referenced by id or inline data.""" diff --git a/src/openai/types/responses/container_network_policy_allowlist.py b/src/openai/types/responses/container_network_policy_allowlist.py new file mode 100644 index 0000000000..caa2565ea2 --- /dev/null +++ b/src/openai/types/responses/container_network_policy_allowlist.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .container_network_policy_domain_secret import ContainerNetworkPolicyDomainSecret + +__all__ = ["ContainerNetworkPolicyAllowlist"] + + +class ContainerNetworkPolicyAllowlist(BaseModel): + allowed_domains: List[str] + """A list of allowed domains when type is `allowlist`.""" + + type: Literal["allowlist"] + """Allow outbound network access only to specified domains. Always `allowlist`.""" + + domain_secrets: Optional[List[ContainerNetworkPolicyDomainSecret]] = None + """Optional domain-scoped secrets for allowlisted domains.""" diff --git a/src/openai/types/responses/container_network_policy_allowlist_param.py b/src/openai/types/responses/container_network_policy_allowlist_param.py new file mode 100644 index 0000000000..583b761e69 --- /dev/null +++ b/src/openai/types/responses/container_network_policy_allowlist_param.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, TypedDict + +from ..._types import SequenceNotStr +from .container_network_policy_domain_secret_param import ContainerNetworkPolicyDomainSecretParam + +__all__ = ["ContainerNetworkPolicyAllowlistParam"] + + +class ContainerNetworkPolicyAllowlistParam(TypedDict, total=False): + allowed_domains: Required[SequenceNotStr[str]] + """A list of allowed domains when type is `allowlist`.""" + + type: Required[Literal["allowlist"]] + """Allow outbound network access only to specified domains. Always `allowlist`.""" + + domain_secrets: Iterable[ContainerNetworkPolicyDomainSecretParam] + """Optional domain-scoped secrets for allowlisted domains.""" diff --git a/src/openai/types/responses/container_network_policy_disabled.py b/src/openai/types/responses/container_network_policy_disabled.py new file mode 100644 index 0000000000..47891aa338 --- /dev/null +++ b/src/openai/types/responses/container_network_policy_disabled.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ContainerNetworkPolicyDisabled"] + + +class ContainerNetworkPolicyDisabled(BaseModel): + type: Literal["disabled"] + """Disable outbound network access. Always `disabled`.""" diff --git a/src/openai/types/responses/container_network_policy_disabled_param.py b/src/openai/types/responses/container_network_policy_disabled_param.py new file mode 100644 index 0000000000..0db7d8ff79 --- /dev/null +++ b/src/openai/types/responses/container_network_policy_disabled_param.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ContainerNetworkPolicyDisabledParam"] + + +class ContainerNetworkPolicyDisabledParam(TypedDict, total=False): + type: Required[Literal["disabled"]] + """Disable outbound network access. Always `disabled`.""" diff --git a/src/openai/types/responses/container_network_policy_domain_secret.py b/src/openai/types/responses/container_network_policy_domain_secret.py new file mode 100644 index 0000000000..d0e18ba977 --- /dev/null +++ b/src/openai/types/responses/container_network_policy_domain_secret.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["ContainerNetworkPolicyDomainSecret"] + + +class ContainerNetworkPolicyDomainSecret(BaseModel): + domain: str + """The domain associated with the secret.""" + + name: str + """The name of the secret to inject for the domain.""" + + value: str + """The secret value to inject for the domain.""" diff --git a/src/openai/types/responses/container_network_policy_domain_secret_param.py b/src/openai/types/responses/container_network_policy_domain_secret_param.py new file mode 100644 index 0000000000..619fbde7a2 --- /dev/null +++ b/src/openai/types/responses/container_network_policy_domain_secret_param.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ContainerNetworkPolicyDomainSecretParam"] + + +class ContainerNetworkPolicyDomainSecretParam(TypedDict, total=False): + domain: Required[str] + """The domain associated with the secret.""" + + name: Required[str] + """The name of the secret to inject for the domain.""" + + value: Required[str] + """The secret value to inject for the domain.""" diff --git a/src/openai/types/responses/container_reference.py b/src/openai/types/responses/container_reference.py new file mode 100644 index 0000000000..17065bf30a --- /dev/null +++ b/src/openai/types/responses/container_reference.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ContainerReference"] + + +class ContainerReference(BaseModel): + container_id: str + """The ID of the referenced container.""" + + type: Literal["container_reference"] + """References a container created with the /v1/containers endpoint""" diff --git a/src/openai/types/responses/container_reference_param.py b/src/openai/types/responses/container_reference_param.py new file mode 100644 index 0000000000..1da2fb6763 --- /dev/null +++ b/src/openai/types/responses/container_reference_param.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ContainerReferenceParam"] + + +class ContainerReferenceParam(TypedDict, total=False): + container_id: Required[str] + """The ID of the referenced container.""" + + type: Required[Literal["container_reference"]] + """References a container created with the /v1/containers endpoint""" diff --git a/src/openai/types/responses/custom_tool.py b/src/openai/types/responses/custom_tool.py new file mode 100644 index 0000000000..017c6d699b --- /dev/null +++ b/src/openai/types/responses/custom_tool.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from ..shared.custom_tool_input_format import CustomToolInputFormat + +__all__ = ["CustomTool"] + + +class CustomTool(BaseModel): + """A custom tool that processes input using a specified format. + + Learn more about [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + """ + + name: str + """The name of the custom tool, used to identify it in tool calls.""" + + type: Literal["custom"] + """The type of the custom tool. Always `custom`.""" + + defer_loading: Optional[bool] = None + """Whether this tool should be deferred and discovered via tool search.""" + + description: Optional[str] = None + """Optional description of the custom tool, used to provide more context.""" + + format: Optional[CustomToolInputFormat] = None + """The input format for the custom tool. Default is unconstrained text.""" diff --git a/src/openai/types/responses/custom_tool_param.py b/src/openai/types/responses/custom_tool_param.py new file mode 100644 index 0000000000..e64001366e --- /dev/null +++ b/src/openai/types/responses/custom_tool_param.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from ..shared_params.custom_tool_input_format import CustomToolInputFormat + +__all__ = ["CustomToolParam"] + + +class CustomToolParam(TypedDict, total=False): + """A custom tool that processes input using a specified format. + + Learn more about [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + """ + + name: Required[str] + """The name of the custom tool, used to identify it in tool calls.""" + + type: Required[Literal["custom"]] + """The type of the custom tool. Always `custom`.""" + + defer_loading: bool + """Whether this tool should be deferred and discovered via tool search.""" + + description: str + """Optional description of the custom tool, used to provide more context.""" + + format: CustomToolInputFormat + """The input format for the custom tool. Default is unconstrained text.""" diff --git a/src/openai/types/responses/easy_input_message.py b/src/openai/types/responses/easy_input_message.py new file mode 100644 index 0000000000..aa97827bc0 --- /dev/null +++ b/src/openai/types/responses/easy_input_message.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .response_input_message_content_list import ResponseInputMessageContentList + +__all__ = ["EasyInputMessage"] + + +class EasyInputMessage(BaseModel): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: Union[str, ResponseInputMessageContentList] + """ + Text, image, or audio input to the model, used to generate a response. Can also + contain previous assistant responses. + """ + + role: Literal["user", "assistant", "system", "developer"] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + phase: Optional[Literal["commentary", "final_answer"]] = None + """ + Labels an `assistant` message as intermediate commentary (`commentary`) or the + final answer (`final_answer`). For models like `gpt-5.3-codex` and beyond, when + sending follow-up requests, preserve and resend phase on all assistant messages + — dropping it can degrade performance. Not used for user messages. + """ + + type: Optional[Literal["message"]] = None + """The type of the message input. Always `message`.""" diff --git a/src/openai/types/responses/easy_input_message_param.py b/src/openai/types/responses/easy_input_message_param.py new file mode 100644 index 0000000000..bfc8d577ba --- /dev/null +++ b/src/openai/types/responses/easy_input_message_param.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, Required, TypedDict + +from .response_input_message_content_list_param import ResponseInputMessageContentListParam + +__all__ = ["EasyInputMessageParam"] + + +class EasyInputMessageParam(TypedDict, total=False): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. Messages with the + `assistant` role are presumed to have been generated by the model in previous + interactions. + """ + + content: Required[Union[str, ResponseInputMessageContentListParam]] + """ + Text, image, or audio input to the model, used to generate a response. Can also + contain previous assistant responses. + """ + + role: Required[Literal["user", "assistant", "system", "developer"]] + """The role of the message input. + + One of `user`, `assistant`, `system`, or `developer`. + """ + + phase: Optional[Literal["commentary", "final_answer"]] + """ + Labels an `assistant` message as intermediate commentary (`commentary`) or the + final answer (`final_answer`). For models like `gpt-5.3-codex` and beyond, when + sending follow-up requests, preserve and resend phase on all assistant messages + — dropping it can degrade performance. Not used for user messages. + """ + + type: Literal["message"] + """The type of the message input. Always `message`.""" diff --git a/src/openai/types/responses/file_search_tool.py b/src/openai/types/responses/file_search_tool.py new file mode 100644 index 0000000000..09c12876ca --- /dev/null +++ b/src/openai/types/responses/file_search_tool.py @@ -0,0 +1,69 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from ..shared.compound_filter import CompoundFilter +from ..shared.comparison_filter import ComparisonFilter + +__all__ = ["FileSearchTool", "Filters", "RankingOptions", "RankingOptionsHybridSearch"] + +Filters: TypeAlias = Union[ComparisonFilter, CompoundFilter, None] + + +class RankingOptionsHybridSearch(BaseModel): + """ + Weights that control how reciprocal rank fusion balances semantic embedding matches versus sparse keyword matches when hybrid search is enabled. + """ + + embedding_weight: float + """The weight of the embedding in the reciprocal ranking fusion.""" + + text_weight: float + """The weight of the text in the reciprocal ranking fusion.""" + + +class RankingOptions(BaseModel): + """Ranking options for search.""" + + hybrid_search: Optional[RankingOptionsHybridSearch] = None + """ + Weights that control how reciprocal rank fusion balances semantic embedding + matches versus sparse keyword matches when hybrid search is enabled. + """ + + ranker: Optional[Literal["auto", "default-2024-11-15"]] = None + """The ranker to use for the file search.""" + + score_threshold: Optional[float] = None + """The score threshold for the file search, a number between 0 and 1. + + Numbers closer to 1 will attempt to return only the most relevant results, but + may return fewer results. + """ + + +class FileSearchTool(BaseModel): + """A tool that searches for relevant content from uploaded files. + + Learn more about the [file search tool](https://platform.openai.com/docs/guides/tools-file-search). + """ + + type: Literal["file_search"] + """The type of the file search tool. Always `file_search`.""" + + vector_store_ids: List[str] + """The IDs of the vector stores to search.""" + + filters: Optional[Filters] = None + """A filter to apply.""" + + max_num_results: Optional[int] = None + """The maximum number of results to return. + + This number should be between 1 and 50 inclusive. + """ + + ranking_options: Optional[RankingOptions] = None + """Ranking options for search.""" diff --git a/src/openai/types/responses/file_search_tool_param.py b/src/openai/types/responses/file_search_tool_param.py new file mode 100644 index 0000000000..82831d0dc0 --- /dev/null +++ b/src/openai/types/responses/file_search_tool_param.py @@ -0,0 +1,71 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from ..shared_params.compound_filter import CompoundFilter +from ..shared_params.comparison_filter import ComparisonFilter + +__all__ = ["FileSearchToolParam", "Filters", "RankingOptions", "RankingOptionsHybridSearch"] + +Filters: TypeAlias = Union[ComparisonFilter, CompoundFilter] + + +class RankingOptionsHybridSearch(TypedDict, total=False): + """ + Weights that control how reciprocal rank fusion balances semantic embedding matches versus sparse keyword matches when hybrid search is enabled. + """ + + embedding_weight: Required[float] + """The weight of the embedding in the reciprocal ranking fusion.""" + + text_weight: Required[float] + """The weight of the text in the reciprocal ranking fusion.""" + + +class RankingOptions(TypedDict, total=False): + """Ranking options for search.""" + + hybrid_search: RankingOptionsHybridSearch + """ + Weights that control how reciprocal rank fusion balances semantic embedding + matches versus sparse keyword matches when hybrid search is enabled. + """ + + ranker: Literal["auto", "default-2024-11-15"] + """The ranker to use for the file search.""" + + score_threshold: float + """The score threshold for the file search, a number between 0 and 1. + + Numbers closer to 1 will attempt to return only the most relevant results, but + may return fewer results. + """ + + +class FileSearchToolParam(TypedDict, total=False): + """A tool that searches for relevant content from uploaded files. + + Learn more about the [file search tool](https://platform.openai.com/docs/guides/tools-file-search). + """ + + type: Required[Literal["file_search"]] + """The type of the file search tool. Always `file_search`.""" + + vector_store_ids: Required[SequenceNotStr[str]] + """The IDs of the vector stores to search.""" + + filters: Optional[Filters] + """A filter to apply.""" + + max_num_results: int + """The maximum number of results to return. + + This number should be between 1 and 50 inclusive. + """ + + ranking_options: RankingOptions + """Ranking options for search.""" diff --git a/src/openai/types/responses/function_shell_tool.py b/src/openai/types/responses/function_shell_tool.py new file mode 100644 index 0000000000..17d6bb36c9 --- /dev/null +++ b/src/openai/types/responses/function_shell_tool.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .container_auto import ContainerAuto +from .local_environment import LocalEnvironment +from .container_reference import ContainerReference + +__all__ = ["FunctionShellTool", "Environment"] + +Environment: TypeAlias = Annotated[ + Union[ContainerAuto, LocalEnvironment, ContainerReference, None], PropertyInfo(discriminator="type") +] + + +class FunctionShellTool(BaseModel): + """A tool that allows the model to execute shell commands.""" + + type: Literal["shell"] + """The type of the shell tool. Always `shell`.""" + + environment: Optional[Environment] = None diff --git a/src/openai/types/responses/function_shell_tool_param.py b/src/openai/types/responses/function_shell_tool_param.py new file mode 100644 index 0000000000..b8464ed341 --- /dev/null +++ b/src/openai/types/responses/function_shell_tool_param.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .container_auto_param import ContainerAutoParam +from .local_environment_param import LocalEnvironmentParam +from .container_reference_param import ContainerReferenceParam + +__all__ = ["FunctionShellToolParam", "Environment"] + +Environment: TypeAlias = Union[ContainerAutoParam, LocalEnvironmentParam, ContainerReferenceParam] + + +class FunctionShellToolParam(TypedDict, total=False): + """A tool that allows the model to execute shell commands.""" + + type: Required[Literal["shell"]] + """The type of the shell tool. Always `shell`.""" + + environment: Optional[Environment] diff --git a/src/openai/types/responses/function_tool.py b/src/openai/types/responses/function_tool.py new file mode 100644 index 0000000000..6e9751ad88 --- /dev/null +++ b/src/openai/types/responses/function_tool.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FunctionTool"] + + +class FunctionTool(BaseModel): + """Defines a function in your own code the model can choose to call. + + Learn more about [function calling](https://platform.openai.com/docs/guides/function-calling). + """ + + name: str + """The name of the function to call.""" + + parameters: Optional[Dict[str, object]] = None + """A JSON schema object describing the parameters of the function.""" + + strict: Optional[bool] = None + """Whether to enforce strict parameter validation. Default `true`.""" + + type: Literal["function"] + """The type of the function tool. Always `function`.""" + + defer_loading: Optional[bool] = None + """Whether this function is deferred and loaded via tool search.""" + + description: Optional[str] = None + """A description of the function. + + Used by the model to determine whether or not to call the function. + """ diff --git a/src/openai/types/responses/function_tool_param.py b/src/openai/types/responses/function_tool_param.py new file mode 100644 index 0000000000..e7978b44a2 --- /dev/null +++ b/src/openai/types/responses/function_tool_param.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["FunctionToolParam"] + + +class FunctionToolParam(TypedDict, total=False): + """Defines a function in your own code the model can choose to call. + + Learn more about [function calling](https://platform.openai.com/docs/guides/function-calling). + """ + + name: Required[str] + """The name of the function to call.""" + + parameters: Required[Optional[Dict[str, object]]] + """A JSON schema object describing the parameters of the function.""" + + strict: Required[Optional[bool]] + """Whether to enforce strict parameter validation. Default `true`.""" + + type: Required[Literal["function"]] + """The type of the function tool. Always `function`.""" + + defer_loading: bool + """Whether this function is deferred and loaded via tool search.""" + + description: Optional[str] + """A description of the function. + + Used by the model to determine whether or not to call the function. + """ diff --git a/src/openai/types/responses/inline_skill.py b/src/openai/types/responses/inline_skill.py new file mode 100644 index 0000000000..87c7ca5f7a --- /dev/null +++ b/src/openai/types/responses/inline_skill.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from .inline_skill_source import InlineSkillSource + +__all__ = ["InlineSkill"] + + +class InlineSkill(BaseModel): + description: str + """The description of the skill.""" + + name: str + """The name of the skill.""" + + source: InlineSkillSource + """Inline skill payload""" + + type: Literal["inline"] + """Defines an inline skill for this request.""" diff --git a/src/openai/types/responses/inline_skill_param.py b/src/openai/types/responses/inline_skill_param.py new file mode 100644 index 0000000000..f9181693be --- /dev/null +++ b/src/openai/types/responses/inline_skill_param.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .inline_skill_source_param import InlineSkillSourceParam + +__all__ = ["InlineSkillParam"] + + +class InlineSkillParam(TypedDict, total=False): + description: Required[str] + """The description of the skill.""" + + name: Required[str] + """The name of the skill.""" + + source: Required[InlineSkillSourceParam] + """Inline skill payload""" + + type: Required[Literal["inline"]] + """Defines an inline skill for this request.""" diff --git a/src/openai/types/responses/inline_skill_source.py b/src/openai/types/responses/inline_skill_source.py new file mode 100644 index 0000000000..2586cdb008 --- /dev/null +++ b/src/openai/types/responses/inline_skill_source.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InlineSkillSource"] + + +class InlineSkillSource(BaseModel): + """Inline skill payload""" + + data: str + """Base64-encoded skill zip bundle.""" + + media_type: Literal["application/zip"] + """The media type of the inline skill payload. Must be `application/zip`.""" + + type: Literal["base64"] + """The type of the inline skill source. Must be `base64`.""" diff --git a/src/openai/types/responses/inline_skill_source_param.py b/src/openai/types/responses/inline_skill_source_param.py new file mode 100644 index 0000000000..b14e63e2af --- /dev/null +++ b/src/openai/types/responses/inline_skill_source_param.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["InlineSkillSourceParam"] + + +class InlineSkillSourceParam(TypedDict, total=False): + """Inline skill payload""" + + data: Required[str] + """Base64-encoded skill zip bundle.""" + + media_type: Required[Literal["application/zip"]] + """The media type of the inline skill payload. Must be `application/zip`.""" + + type: Required[Literal["base64"]] + """The type of the inline skill source. Must be `base64`.""" diff --git a/src/openai/types/responses/input_item_list_params.py b/src/openai/types/responses/input_item_list_params.py new file mode 100644 index 0000000000..44a8dc5de3 --- /dev/null +++ b/src/openai/types/responses/input_item_list_params.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, TypedDict + +from .response_includable import ResponseIncludable + +__all__ = ["InputItemListParams"] + + +class InputItemListParams(TypedDict, total=False): + after: str + """An item ID to list items after, used in pagination.""" + + include: List[ResponseIncludable] + """Additional fields to include in the response. + + See the `include` parameter for Response creation above for more information. + """ + + limit: int + """A limit on the number of objects to be returned. + + Limit can range between 1 and 100, and the default is 20. + """ + + order: Literal["asc", "desc"] + """The order to return the input items in. Default is `desc`. + + - `asc`: Return the input items in ascending order. + - `desc`: Return the input items in descending order. + """ diff --git a/src/openai/types/responses/input_token_count_params.py b/src/openai/types/responses/input_token_count_params.py new file mode 100644 index 0000000000..d94eea689c --- /dev/null +++ b/src/openai/types/responses/input_token_count_params.py @@ -0,0 +1,153 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, TypeAlias, TypedDict + +from .tool_param import ToolParam +from .tool_choice_options import ToolChoiceOptions +from .tool_choice_mcp_param import ToolChoiceMcpParam +from .tool_choice_shell_param import ToolChoiceShellParam +from .tool_choice_types_param import ToolChoiceTypesParam +from ..shared_params.reasoning import Reasoning +from .tool_choice_custom_param import ToolChoiceCustomParam +from .response_input_item_param import ResponseInputItemParam +from .tool_choice_allowed_param import ToolChoiceAllowedParam +from .tool_choice_function_param import ToolChoiceFunctionParam +from .tool_choice_apply_patch_param import ToolChoiceApplyPatchParam +from .response_conversation_param_param import ResponseConversationParamParam +from .response_format_text_config_param import ResponseFormatTextConfigParam + +__all__ = ["InputTokenCountParams", "Conversation", "Text", "ToolChoice"] + + +class InputTokenCountParams(TypedDict, total=False): + conversation: Optional[Conversation] + """The conversation that this response belongs to. + + Items from this conversation are prepended to `input_items` for this response + request. Input items and output items from this response are automatically added + to this conversation after this response completes. + """ + + input: Union[str, Iterable[ResponseInputItemParam], None] + """Text, image, or file inputs to the model, used to generate a response""" + + instructions: Optional[str] + """ + A system (or developer) message inserted into the model's context. When used + along with `previous_response_id`, the instructions from a previous response + will not be carried over to the next response. This makes it simple to swap out + system (or developer) messages in new responses. + """ + + model: Optional[str] + """Model ID used to generate the response, like `gpt-4o` or `o3`. + + OpenAI offers a wide range of models with different capabilities, performance + characteristics, and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + """ + + parallel_tool_calls: Optional[bool] + """Whether to allow the model to run tool calls in parallel.""" + + personality: Union[str, Literal["friendly", "pragmatic"]] + """A model-owned style preset to apply to this request. + + Omit this parameter to use the model's default style. Supported values may + expand over time. Values must be at most 64 characters. + """ + + previous_response_id: Optional[str] + """The unique ID of the previous response to the model. + + Use this to create multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + """ + + reasoning: Optional[Reasoning] + """ + **gpt-5 and o-series models only** Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + text: Optional[Text] + """Configuration options for a text response from the model. + + Can be plain text or structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + tool_choice: Optional[ToolChoice] + """Controls which tool the model should use, if any.""" + + tools: Optional[Iterable[ToolParam]] + """An array of tools the model may call while generating a response. + + You can specify which tool to use by setting the `tool_choice` parameter. + """ + + truncation: Literal["auto", "disabled"] + """The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. - `disabled` (default): If the + input size will exceed the context window size for a model, the request will + fail with a 400 error. + """ + + +Conversation: TypeAlias = Union[str, ResponseConversationParamParam] + + +class Text(TypedDict, total=False): + """Configuration options for a text response from the model. + + Can be plain + text or structured JSON data. Learn more: + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + format: ResponseFormatTextConfigParam + """An object specifying the format that the model must output. + + Configuring `{ "type": "json_schema" }` enables Structured Outputs, which + ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + The default format is `{ "type": "text" }` with no additional options. + + **Not recommended for gpt-4o and newer models:** + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ + + verbosity: Optional[Literal["low", "medium", "high"]] + """Constrains the verbosity of the model's response. + + Lower values will result in more concise responses, while higher values will + result in more verbose responses. Currently supported values are `low`, + `medium`, and `high`. + """ + + +ToolChoice: TypeAlias = Union[ + ToolChoiceOptions, + ToolChoiceAllowedParam, + ToolChoiceTypesParam, + ToolChoiceFunctionParam, + ToolChoiceMcpParam, + ToolChoiceCustomParam, + ToolChoiceApplyPatchParam, + ToolChoiceShellParam, +] diff --git a/src/openai/types/responses/input_token_count_response.py b/src/openai/types/responses/input_token_count_response.py new file mode 100644 index 0000000000..30ddfc1217 --- /dev/null +++ b/src/openai/types/responses/input_token_count_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["InputTokenCountResponse"] + + +class InputTokenCountResponse(BaseModel): + input_tokens: int + + object: Literal["response.input_tokens"] diff --git a/src/openai/types/responses/local_environment.py b/src/openai/types/responses/local_environment.py new file mode 100644 index 0000000000..3ebdaa7e2a --- /dev/null +++ b/src/openai/types/responses/local_environment.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .local_skill import LocalSkill + +__all__ = ["LocalEnvironment"] + + +class LocalEnvironment(BaseModel): + type: Literal["local"] + """Use a local computer environment.""" + + skills: Optional[List[LocalSkill]] = None + """An optional list of skills.""" diff --git a/src/openai/types/responses/local_environment_param.py b/src/openai/types/responses/local_environment_param.py new file mode 100644 index 0000000000..0ed1e81ffc --- /dev/null +++ b/src/openai/types/responses/local_environment_param.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, TypedDict + +from .local_skill_param import LocalSkillParam + +__all__ = ["LocalEnvironmentParam"] + + +class LocalEnvironmentParam(TypedDict, total=False): + type: Required[Literal["local"]] + """Use a local computer environment.""" + + skills: Iterable[LocalSkillParam] + """An optional list of skills.""" diff --git a/src/openai/types/responses/local_skill.py b/src/openai/types/responses/local_skill.py new file mode 100644 index 0000000000..1033c319d4 --- /dev/null +++ b/src/openai/types/responses/local_skill.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["LocalSkill"] + + +class LocalSkill(BaseModel): + description: str + """The description of the skill.""" + + name: str + """The name of the skill.""" + + path: str + """The path to the directory containing the skill.""" diff --git a/src/openai/types/responses/local_skill_param.py b/src/openai/types/responses/local_skill_param.py new file mode 100644 index 0000000000..63011a49aa --- /dev/null +++ b/src/openai/types/responses/local_skill_param.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["LocalSkillParam"] + + +class LocalSkillParam(TypedDict, total=False): + description: Required[str] + """The description of the skill.""" + + name: Required[str] + """The name of the skill.""" + + path: Required[str] + """The path to the directory containing the skill.""" diff --git a/src/openai/types/responses/namespace_tool.py b/src/openai/types/responses/namespace_tool.py new file mode 100644 index 0000000000..88f76a9732 --- /dev/null +++ b/src/openai/types/responses/namespace_tool.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .custom_tool import CustomTool + +__all__ = ["NamespaceTool", "Tool", "ToolFunction"] + + +class ToolFunction(BaseModel): + name: str + + type: Literal["function"] + + defer_loading: Optional[bool] = None + """Whether this function should be deferred and discovered via tool search.""" + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +Tool: TypeAlias = Annotated[Union[ToolFunction, CustomTool], PropertyInfo(discriminator="type")] + + +class NamespaceTool(BaseModel): + """Groups function/custom tools under a shared namespace.""" + + description: str + """A description of the namespace shown to the model.""" + + name: str + """The namespace name used in tool calls (for example, `crm`).""" + + tools: List[Tool] + """The function/custom tools available inside this namespace.""" + + type: Literal["namespace"] + """The type of the tool. Always `namespace`.""" diff --git a/src/openai/types/responses/namespace_tool_param.py b/src/openai/types/responses/namespace_tool_param.py new file mode 100644 index 0000000000..cb1e5e17f4 --- /dev/null +++ b/src/openai/types/responses/namespace_tool_param.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .custom_tool_param import CustomToolParam + +__all__ = ["NamespaceToolParam", "Tool", "ToolFunction"] + + +class ToolFunction(TypedDict, total=False): + name: Required[str] + + type: Required[Literal["function"]] + + defer_loading: bool + """Whether this function should be deferred and discovered via tool search.""" + + description: Optional[str] + + parameters: Optional[object] + + strict: Optional[bool] + + +Tool: TypeAlias = Union[ToolFunction, CustomToolParam] + + +class NamespaceToolParam(TypedDict, total=False): + """Groups function/custom tools under a shared namespace.""" + + description: Required[str] + """A description of the namespace shown to the model.""" + + name: Required[str] + """The namespace name used in tool calls (for example, `crm`).""" + + tools: Required[Iterable[Tool]] + """The function/custom tools available inside this namespace.""" + + type: Required[Literal["namespace"]] + """The type of the tool. Always `namespace`.""" diff --git a/src/openai/types/responses/parsed_response.py b/src/openai/types/responses/parsed_response.py new file mode 100644 index 0000000000..0d1f18b472 --- /dev/null +++ b/src/openai/types/responses/parsed_response.py @@ -0,0 +1,122 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, List, Union, Generic, TypeVar, Optional +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from .response import Response +from ..._models import GenericModel +from .response_output_item import ( + McpCall, + McpListTools, + LocalShellCall, + AdditionalTools, + McpApprovalRequest, + ImageGenerationCall, + McpApprovalResponse, + LocalShellCallAction, + LocalShellCallOutput, +) +from .response_output_text import ResponseOutputText +from .response_output_message import ResponseOutputMessage +from .response_output_refusal import ResponseOutputRefusal +from .response_reasoning_item import ResponseReasoningItem +from .response_compaction_item import ResponseCompactionItem +from .response_custom_tool_call import ResponseCustomToolCall +from .response_tool_search_call import ResponseToolSearchCall +from .response_computer_tool_call import ResponseComputerToolCall +from .response_function_tool_call import ResponseFunctionToolCall +from .response_function_web_search import ResponseFunctionWebSearch +from .response_apply_patch_tool_call import ResponseApplyPatchToolCall +from .response_file_search_tool_call import ResponseFileSearchToolCall +from .response_tool_search_output_item import ResponseToolSearchOutputItem +from .response_function_shell_tool_call import ResponseFunctionShellToolCall +from .response_code_interpreter_tool_call import ResponseCodeInterpreterToolCall +from .response_apply_patch_tool_call_output import ResponseApplyPatchToolCallOutput +from .response_custom_tool_call_output_item import ResponseCustomToolCallOutputItem +from .response_computer_tool_call_output_item import ResponseComputerToolCallOutputItem +from .response_function_tool_call_output_item import ResponseFunctionToolCallOutputItem +from .response_function_shell_tool_call_output import ResponseFunctionShellToolCallOutput + +__all__ = ["ParsedResponse", "ParsedResponseOutputMessage", "ParsedResponseOutputText"] + +ContentType = TypeVar("ContentType") + +# we need to disable this check because we're overriding properties +# with subclasses of their types which is technically unsound as +# properties can be mutated. +# pyright: reportIncompatibleVariableOverride=false + + +class ParsedResponseOutputText(ResponseOutputText, GenericModel, Generic[ContentType]): + parsed: Optional[ContentType] = None + + +ParsedContent: TypeAlias = Annotated[ + Union[ParsedResponseOutputText[ContentType], ResponseOutputRefusal], + PropertyInfo(discriminator="type"), +] + + +class ParsedResponseOutputMessage(ResponseOutputMessage, GenericModel, Generic[ContentType]): + if TYPE_CHECKING: + content: List[ParsedContent[ContentType]] # type: ignore[assignment] + else: + content: List[ParsedContent] + + +class ParsedResponseFunctionToolCall(ResponseFunctionToolCall): + parsed_arguments: object = None + + __api_exclude__ = {"parsed_arguments"} + + +ParsedResponseOutputItem: TypeAlias = Annotated[ + Union[ + ParsedResponseOutputMessage[ContentType], + ParsedResponseFunctionToolCall, + ResponseFileSearchToolCall, + ResponseFunctionWebSearch, + ResponseComputerToolCall, + ResponseComputerToolCallOutputItem, + ResponseToolSearchCall, + ResponseToolSearchOutputItem, + AdditionalTools, + ResponseReasoningItem, + McpCall, + McpApprovalRequest, + McpApprovalResponse, + ImageGenerationCall, + LocalShellCall, + LocalShellCallOutput, + LocalShellCallAction, + McpListTools, + ResponseCodeInterpreterToolCall, + ResponseCompactionItem, + ResponseFunctionShellToolCall, + ResponseFunctionShellToolCallOutput, + ResponseApplyPatchToolCall, + ResponseApplyPatchToolCallOutput, + ResponseFunctionToolCallOutputItem, + ResponseCustomToolCall, + ResponseCustomToolCallOutputItem, + ], + PropertyInfo(discriminator="type"), +] + + +class ParsedResponse(Response, GenericModel, Generic[ContentType]): + if TYPE_CHECKING: + output: List[ParsedResponseOutputItem[ContentType]] # type: ignore[assignment] + else: + output: List[ParsedResponseOutputItem] + + @property + def output_parsed(self) -> Optional[ContentType]: + for output in self.output: + if output.type == "message": + for content in output.content: + if content.type == "output_text" and content.parsed: + return content.parsed + + return None diff --git a/src/openai/types/responses/response.py b/src/openai/types/responses/response.py new file mode 100644 index 0000000000..67102d2628 --- /dev/null +++ b/src/openai/types/responses/response.py @@ -0,0 +1,453 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from .tool import Tool +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .response_error import ResponseError +from .response_usage import ResponseUsage +from .response_prompt import ResponsePrompt +from .response_status import ResponseStatus +from .tool_choice_mcp import ToolChoiceMcp +from ..shared.metadata import Metadata +from ..shared.reasoning import Reasoning +from .tool_choice_shell import ToolChoiceShell +from .tool_choice_types import ToolChoiceTypes +from .tool_choice_custom import ToolChoiceCustom +from .response_input_item import ResponseInputItem +from .tool_choice_allowed import ToolChoiceAllowed +from .tool_choice_options import ToolChoiceOptions +from .response_output_item import ResponseOutputItem +from .response_text_config import ResponseTextConfig +from .tool_choice_function import ToolChoiceFunction +from ..shared.responses_model import ResponsesModel +from .tool_choice_apply_patch import ToolChoiceApplyPatch + +__all__ = [ + "Response", + "IncompleteDetails", + "ToolChoice", + "Conversation", + "Moderation", + "ModerationInput", + "ModerationInputModerationResult", + "ModerationInputError", + "ModerationOutput", + "ModerationOutputModerationResult", + "ModerationOutputError", +] + + +class IncompleteDetails(BaseModel): + """Details about why the response is incomplete.""" + + reason: Optional[Literal["max_output_tokens", "content_filter"]] = None + """The reason why the response is incomplete.""" + + +ToolChoice: TypeAlias = Union[ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMcp, + ToolChoiceCustom, + ToolChoiceApplyPatch, + ToolChoiceShell, +] + + +class Conversation(BaseModel): + """The conversation that this response belonged to. + + Input items and output items from this response were automatically added to this conversation. + """ + + id: str + """The unique ID of the conversation that this response was associated with.""" + + +class ModerationInputModerationResult(BaseModel): + """A moderation result produced for the response input or output.""" + + categories: Dict[str, bool] + """ + A dictionary of moderation categories to booleans, True if the input is flagged + under this category. + """ + + category_applied_input_types: Dict[str, List[Literal["text", "image"]]] + """Which modalities of input are reflected by the score for each category.""" + + category_scores: Dict[str, float] + """A dictionary of moderation categories to scores.""" + + flagged: bool + """A boolean indicating whether the content was flagged by any category.""" + + model: str + """The moderation model that produced this result.""" + + type: Literal["moderation_result"] + """ + The object type, which was always `moderation_result` for successful moderation + results. + """ + + +class ModerationInputError(BaseModel): + """An error produced while attempting moderation for the response input or output.""" + + code: str + """The error code.""" + + message: str + """The error message.""" + + type: Literal["error"] + """The object type, which was always `error` for moderation failures.""" + + +ModerationInput: TypeAlias = Annotated[ + Union[ModerationInputModerationResult, ModerationInputError], PropertyInfo(discriminator="type") +] + + +class ModerationOutputModerationResult(BaseModel): + """A moderation result produced for the response input or output.""" + + categories: Dict[str, bool] + """ + A dictionary of moderation categories to booleans, True if the input is flagged + under this category. + """ + + category_applied_input_types: Dict[str, List[Literal["text", "image"]]] + """Which modalities of input are reflected by the score for each category.""" + + category_scores: Dict[str, float] + """A dictionary of moderation categories to scores.""" + + flagged: bool + """A boolean indicating whether the content was flagged by any category.""" + + model: str + """The moderation model that produced this result.""" + + type: Literal["moderation_result"] + """ + The object type, which was always `moderation_result` for successful moderation + results. + """ + + +class ModerationOutputError(BaseModel): + """An error produced while attempting moderation for the response input or output.""" + + code: str + """The error code.""" + + message: str + """The error message.""" + + type: Literal["error"] + """The object type, which was always `error` for moderation failures.""" + + +ModerationOutput: TypeAlias = Annotated[ + Union[ModerationOutputModerationResult, ModerationOutputError], PropertyInfo(discriminator="type") +] + + +class Moderation(BaseModel): + """ + Moderation results for the response input and output, if moderated completions were requested. + """ + + input: ModerationInput + """Moderation for the response input.""" + + output: ModerationOutput + """Moderation for the response output.""" + + +class Response(BaseModel): + id: str + """Unique identifier for this Response.""" + + created_at: float + """Unix timestamp (in seconds) of when this Response was created.""" + + error: Optional[ResponseError] = None + """An error object returned when the model fails to generate a Response.""" + + incomplete_details: Optional[IncompleteDetails] = None + """Details about why the response is incomplete.""" + + instructions: Union[str, List[ResponseInputItem], None] = None + """A system (or developer) message inserted into the model's context. + + When using along with `previous_response_id`, the instructions from a previous + response will not be carried over to the next response. This makes it simple to + swap out system (or developer) messages in new responses. + """ + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: ResponsesModel + """Model ID used to generate the response, like `gpt-4o` or `o3`. + + OpenAI offers a wide range of models with different capabilities, performance + characteristics, and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + """ + + object: Literal["response"] + """The object type of this resource - always set to `response`.""" + + output: List[ResponseOutputItem] + """An array of content items generated by the model. + + - The length and order of items in the `output` array is dependent on the + model's response. + - Rather than accessing the first item in the `output` array and assuming it's + an `assistant` message with the content generated by the model, you might + consider using the `output_text` property where supported in SDKs. + """ + + parallel_tool_calls: bool + """Whether to allow the model to run tool calls in parallel.""" + + temperature: Optional[float] = None + """What sampling temperature to use, between 0 and 2. + + Higher values like 0.8 will make the output more random, while lower values like + 0.2 will make it more focused and deterministic. We generally recommend altering + this or `top_p` but not both. + """ + + tool_choice: ToolChoice + """ + How the model should select which tool (or tools) to use when generating a + response. See the `tools` parameter to see how to specify which tools the model + can call. + """ + + tools: List[Tool] + """An array of tools the model may call while generating a response. + + You can specify which tool to use by setting the `tool_choice` parameter. + + We support the following categories of tools: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **MCP Tools**: Integrations with third-party systems via custom MCP servers or + predefined connectors such as Google Drive and SharePoint. Learn more about + [MCP Tools](https://platform.openai.com/docs/guides/tools-connectors-mcp). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code with strongly typed arguments and outputs. + Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + You can also use custom tools to call your own code. + """ + + top_p: Optional[float] = None + """ + An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + """ + + background: Optional[bool] = None + """ + Whether to run the model response in the background. + [Learn more](https://platform.openai.com/docs/guides/background). + """ + + completed_at: Optional[float] = None + """ + Unix timestamp (in seconds) of when this Response was completed. Only present + when the status is `completed`. + """ + + conversation: Optional[Conversation] = None + """The conversation that this response belonged to. + + Input items and output items from this response were automatically added to this + conversation. + """ + + max_output_tokens: Optional[int] = None + """ + An upper bound for the number of tokens that can be generated for a response, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + """ + + max_tool_calls: Optional[int] = None + """ + The maximum number of total calls to built-in tools that can be processed in a + response. This maximum number applies across all built-in tool calls, not per + individual tool. Any further attempts to call a tool by the model will be + ignored. + """ + + moderation: Optional[Moderation] = None + """ + Moderation results for the response input and output, if moderated completions + were requested. + """ + + previous_response_id: Optional[str] = None + """The unique ID of the previous response to the model. + + Use this to create multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + """ + + prompt: Optional[ResponsePrompt] = None + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + prompt_cache_key: Optional[str] = None + """ + Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + """ + + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] = None + """The retention policy for the prompt cache. + + Set to `24h` to enable extended prompt caching, which keeps cached prefixes + active for longer, up to a maximum of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + """ + + reasoning: Optional[Reasoning] = None + """**gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + safety_identifier: Optional[str] = None + """ + A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + """ + + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] = None + """Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + """ + + status: Optional[ResponseStatus] = None + """The status of the response generation. + + One of `completed`, `failed`, `in_progress`, `cancelled`, `queued`, or + `incomplete`. + """ + + text: Optional[ResponseTextConfig] = None + """Configuration options for a text response from the model. + + Can be plain text or structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + top_logprobs: Optional[int] = None + """ + An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + """ + + truncation: Optional[Literal["auto", "disabled"]] = None + """The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. + - `disabled` (default): If the input size will exceed the context window size + for a model, the request will fail with a 400 error. + """ + + usage: Optional[ResponseUsage] = None + """ + Represents token usage details including input tokens, output tokens, a + breakdown of output tokens, and the total tokens used. + """ + + user: Optional[str] = None + """This field is being replaced by `safety_identifier` and `prompt_cache_key`. + + Use `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + """ + + @property + def output_text(self) -> str: + """Convenience property that aggregates all `output_text` items from the `output` list. + + If no `output_text` content blocks exist, then an empty string is returned. + """ + texts: List[str] = [] + for output in self.output: + if output.type == "message": + for content in output.content: + if content.type == "output_text": + texts.append(content.text) + + return "".join(texts) diff --git a/src/openai/types/responses/response_apply_patch_tool_call.py b/src/openai/types/responses/response_apply_patch_tool_call.py new file mode 100644 index 0000000000..7af1300265 --- /dev/null +++ b/src/openai/types/responses/response_apply_patch_tool_call.py @@ -0,0 +1,84 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = [ + "ResponseApplyPatchToolCall", + "Operation", + "OperationCreateFile", + "OperationDeleteFile", + "OperationUpdateFile", +] + + +class OperationCreateFile(BaseModel): + """Instruction describing how to create a file via the apply_patch tool.""" + + diff: str + """Diff to apply.""" + + path: str + """Path of the file to create.""" + + type: Literal["create_file"] + """Create a new file with the provided diff.""" + + +class OperationDeleteFile(BaseModel): + """Instruction describing how to delete a file via the apply_patch tool.""" + + path: str + """Path of the file to delete.""" + + type: Literal["delete_file"] + """Delete the specified file.""" + + +class OperationUpdateFile(BaseModel): + """Instruction describing how to update a file via the apply_patch tool.""" + + diff: str + """Diff to apply.""" + + path: str + """Path of the file to update.""" + + type: Literal["update_file"] + """Update an existing file with the provided diff.""" + + +Operation: TypeAlias = Annotated[ + Union[OperationCreateFile, OperationDeleteFile, OperationUpdateFile], PropertyInfo(discriminator="type") +] + + +class ResponseApplyPatchToolCall(BaseModel): + """A tool call that applies file diffs by creating, deleting, or updating files.""" + + id: str + """The unique ID of the apply patch tool call. + + Populated when this item is returned via API. + """ + + call_id: str + """The unique ID of the apply patch tool call generated by the model.""" + + operation: Operation + """ + One of the create_file, delete_file, or update_file operations applied via + apply_patch. + """ + + status: Literal["in_progress", "completed"] + """The status of the apply patch tool call. One of `in_progress` or `completed`.""" + + type: Literal["apply_patch_call"] + """The type of the item. Always `apply_patch_call`.""" + + created_by: Optional[str] = None + """The ID of the entity that created this tool call.""" diff --git a/src/openai/types/responses/response_apply_patch_tool_call_output.py b/src/openai/types/responses/response_apply_patch_tool_call_output.py new file mode 100644 index 0000000000..de63c6e2ee --- /dev/null +++ b/src/openai/types/responses/response_apply_patch_tool_call_output.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseApplyPatchToolCallOutput"] + + +class ResponseApplyPatchToolCallOutput(BaseModel): + """The output emitted by an apply patch tool call.""" + + id: str + """The unique ID of the apply patch tool call output. + + Populated when this item is returned via API. + """ + + call_id: str + """The unique ID of the apply patch tool call generated by the model.""" + + status: Literal["completed", "failed"] + """The status of the apply patch tool call output. One of `completed` or `failed`.""" + + type: Literal["apply_patch_call_output"] + """The type of the item. Always `apply_patch_call_output`.""" + + created_by: Optional[str] = None + """The ID of the entity that created this tool call output.""" + + output: Optional[str] = None + """Optional textual output returned by the apply patch tool.""" diff --git a/src/openai/types/responses/response_audio_delta_event.py b/src/openai/types/responses/response_audio_delta_event.py new file mode 100644 index 0000000000..e577d65d04 --- /dev/null +++ b/src/openai/types/responses/response_audio_delta_event.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseAudioDeltaEvent"] + + +class ResponseAudioDeltaEvent(BaseModel): + """Emitted when there is a partial audio response.""" + + delta: str + """A chunk of Base64 encoded response audio bytes.""" + + sequence_number: int + """A sequence number for this chunk of the stream response.""" + + type: Literal["response.audio.delta"] + """The type of the event. Always `response.audio.delta`.""" diff --git a/src/openai/types/responses/response_audio_done_event.py b/src/openai/types/responses/response_audio_done_event.py new file mode 100644 index 0000000000..f5f0401c86 --- /dev/null +++ b/src/openai/types/responses/response_audio_done_event.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseAudioDoneEvent"] + + +class ResponseAudioDoneEvent(BaseModel): + """Emitted when the audio response is complete.""" + + sequence_number: int + """The sequence number of the delta.""" + + type: Literal["response.audio.done"] + """The type of the event. Always `response.audio.done`.""" diff --git a/src/openai/types/responses/response_audio_transcript_delta_event.py b/src/openai/types/responses/response_audio_transcript_delta_event.py new file mode 100644 index 0000000000..03be59a29f --- /dev/null +++ b/src/openai/types/responses/response_audio_transcript_delta_event.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseAudioTranscriptDeltaEvent"] + + +class ResponseAudioTranscriptDeltaEvent(BaseModel): + """Emitted when there is a partial transcript of audio.""" + + delta: str + """The partial transcript of the audio response.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.audio.transcript.delta"] + """The type of the event. Always `response.audio.transcript.delta`.""" diff --git a/src/openai/types/responses/response_audio_transcript_done_event.py b/src/openai/types/responses/response_audio_transcript_done_event.py new file mode 100644 index 0000000000..87219e4844 --- /dev/null +++ b/src/openai/types/responses/response_audio_transcript_done_event.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseAudioTranscriptDoneEvent"] + + +class ResponseAudioTranscriptDoneEvent(BaseModel): + """Emitted when the full audio transcript is completed.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.audio.transcript.done"] + """The type of the event. Always `response.audio.transcript.done`.""" diff --git a/src/openai/types/responses/response_code_interpreter_call_code_delta_event.py b/src/openai/types/responses/response_code_interpreter_call_code_delta_event.py new file mode 100644 index 0000000000..c6bc8b73ea --- /dev/null +++ b/src/openai/types/responses/response_code_interpreter_call_code_delta_event.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCodeInterpreterCallCodeDeltaEvent"] + + +class ResponseCodeInterpreterCallCodeDeltaEvent(BaseModel): + """Emitted when a partial code snippet is streamed by the code interpreter.""" + + delta: str + """The partial code snippet being streamed by the code interpreter.""" + + item_id: str + """The unique identifier of the code interpreter tool call item.""" + + output_index: int + """ + The index of the output item in the response for which the code is being + streamed. + """ + + sequence_number: int + """The sequence number of this event, used to order streaming events.""" + + type: Literal["response.code_interpreter_call_code.delta"] + """The type of the event. Always `response.code_interpreter_call_code.delta`.""" diff --git a/src/openai/types/responses/response_code_interpreter_call_code_done_event.py b/src/openai/types/responses/response_code_interpreter_call_code_done_event.py new file mode 100644 index 0000000000..186c03711a --- /dev/null +++ b/src/openai/types/responses/response_code_interpreter_call_code_done_event.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCodeInterpreterCallCodeDoneEvent"] + + +class ResponseCodeInterpreterCallCodeDoneEvent(BaseModel): + """Emitted when the code snippet is finalized by the code interpreter.""" + + code: str + """The final code snippet output by the code interpreter.""" + + item_id: str + """The unique identifier of the code interpreter tool call item.""" + + output_index: int + """The index of the output item in the response for which the code is finalized.""" + + sequence_number: int + """The sequence number of this event, used to order streaming events.""" + + type: Literal["response.code_interpreter_call_code.done"] + """The type of the event. Always `response.code_interpreter_call_code.done`.""" diff --git a/src/openai/types/responses/response_code_interpreter_call_completed_event.py b/src/openai/types/responses/response_code_interpreter_call_completed_event.py new file mode 100644 index 0000000000..197e39e7e9 --- /dev/null +++ b/src/openai/types/responses/response_code_interpreter_call_completed_event.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCodeInterpreterCallCompletedEvent"] + + +class ResponseCodeInterpreterCallCompletedEvent(BaseModel): + """Emitted when the code interpreter call is completed.""" + + item_id: str + """The unique identifier of the code interpreter tool call item.""" + + output_index: int + """ + The index of the output item in the response for which the code interpreter call + is completed. + """ + + sequence_number: int + """The sequence number of this event, used to order streaming events.""" + + type: Literal["response.code_interpreter_call.completed"] + """The type of the event. Always `response.code_interpreter_call.completed`.""" diff --git a/src/openai/types/responses/response_code_interpreter_call_in_progress_event.py b/src/openai/types/responses/response_code_interpreter_call_in_progress_event.py new file mode 100644 index 0000000000..c775f1b864 --- /dev/null +++ b/src/openai/types/responses/response_code_interpreter_call_in_progress_event.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCodeInterpreterCallInProgressEvent"] + + +class ResponseCodeInterpreterCallInProgressEvent(BaseModel): + """Emitted when a code interpreter call is in progress.""" + + item_id: str + """The unique identifier of the code interpreter tool call item.""" + + output_index: int + """ + The index of the output item in the response for which the code interpreter call + is in progress. + """ + + sequence_number: int + """The sequence number of this event, used to order streaming events.""" + + type: Literal["response.code_interpreter_call.in_progress"] + """The type of the event. Always `response.code_interpreter_call.in_progress`.""" diff --git a/src/openai/types/responses/response_code_interpreter_call_interpreting_event.py b/src/openai/types/responses/response_code_interpreter_call_interpreting_event.py new file mode 100644 index 0000000000..85e9c87f08 --- /dev/null +++ b/src/openai/types/responses/response_code_interpreter_call_interpreting_event.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCodeInterpreterCallInterpretingEvent"] + + +class ResponseCodeInterpreterCallInterpretingEvent(BaseModel): + """Emitted when the code interpreter is actively interpreting the code snippet.""" + + item_id: str + """The unique identifier of the code interpreter tool call item.""" + + output_index: int + """ + The index of the output item in the response for which the code interpreter is + interpreting code. + """ + + sequence_number: int + """The sequence number of this event, used to order streaming events.""" + + type: Literal["response.code_interpreter_call.interpreting"] + """The type of the event. Always `response.code_interpreter_call.interpreting`.""" diff --git a/src/openai/types/responses/response_code_interpreter_tool_call.py b/src/openai/types/responses/response_code_interpreter_tool_call.py new file mode 100644 index 0000000000..d7e30f4920 --- /dev/null +++ b/src/openai/types/responses/response_code_interpreter_tool_call.py @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = ["ResponseCodeInterpreterToolCall", "Output", "OutputLogs", "OutputImage"] + + +class OutputLogs(BaseModel): + """The logs output from the code interpreter.""" + + logs: str + """The logs output from the code interpreter.""" + + type: Literal["logs"] + """The type of the output. Always `logs`.""" + + +class OutputImage(BaseModel): + """The image output from the code interpreter.""" + + type: Literal["image"] + """The type of the output. Always `image`.""" + + url: str + """The URL of the image output from the code interpreter.""" + + +Output: TypeAlias = Annotated[Union[OutputLogs, OutputImage], PropertyInfo(discriminator="type")] + + +class ResponseCodeInterpreterToolCall(BaseModel): + """A tool call to run code.""" + + id: str + """The unique ID of the code interpreter tool call.""" + + code: Optional[str] = None + """The code to run, or null if not available.""" + + container_id: str + """The ID of the container used to run the code.""" + + outputs: Optional[List[Output]] = None + """ + The outputs generated by the code interpreter, such as logs or images. Can be + null if no outputs are available. + """ + + status: Literal["in_progress", "completed", "incomplete", "interpreting", "failed"] + """The status of the code interpreter tool call. + + Valid values are `in_progress`, `completed`, `incomplete`, `interpreting`, and + `failed`. + """ + + type: Literal["code_interpreter_call"] + """The type of the code interpreter tool call. Always `code_interpreter_call`.""" diff --git a/src/openai/types/responses/response_code_interpreter_tool_call_param.py b/src/openai/types/responses/response_code_interpreter_tool_call_param.py new file mode 100644 index 0000000000..fc03a3fe48 --- /dev/null +++ b/src/openai/types/responses/response_code_interpreter_tool_call_param.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = ["ResponseCodeInterpreterToolCallParam", "Output", "OutputLogs", "OutputImage"] + + +class OutputLogs(TypedDict, total=False): + """The logs output from the code interpreter.""" + + logs: Required[str] + """The logs output from the code interpreter.""" + + type: Required[Literal["logs"]] + """The type of the output. Always `logs`.""" + + +class OutputImage(TypedDict, total=False): + """The image output from the code interpreter.""" + + type: Required[Literal["image"]] + """The type of the output. Always `image`.""" + + url: Required[str] + """The URL of the image output from the code interpreter.""" + + +Output: TypeAlias = Union[OutputLogs, OutputImage] + + +class ResponseCodeInterpreterToolCallParam(TypedDict, total=False): + """A tool call to run code.""" + + id: Required[str] + """The unique ID of the code interpreter tool call.""" + + code: Required[Optional[str]] + """The code to run, or null if not available.""" + + container_id: Required[str] + """The ID of the container used to run the code.""" + + outputs: Required[Optional[Iterable[Output]]] + """ + The outputs generated by the code interpreter, such as logs or images. Can be + null if no outputs are available. + """ + + status: Required[Literal["in_progress", "completed", "incomplete", "interpreting", "failed"]] + """The status of the code interpreter tool call. + + Valid values are `in_progress`, `completed`, `incomplete`, `interpreting`, and + `failed`. + """ + + type: Required[Literal["code_interpreter_call"]] + """The type of the code interpreter tool call. Always `code_interpreter_call`.""" diff --git a/src/openai/types/responses/response_compact_params.py b/src/openai/types/responses/response_compact_params.py new file mode 100644 index 0000000000..923a09e56d --- /dev/null +++ b/src/openai/types/responses/response_compact_params.py @@ -0,0 +1,148 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .response_input_item_param import ResponseInputItemParam + +__all__ = ["ResponseCompactParams"] + + +class ResponseCompactParams(TypedDict, total=False): + model: Required[ + Union[ + Literal[ + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gpt-5.4-mini-2026-03-17", + "gpt-5.4-nano-2026-03-17", + "gpt-5.3-chat-latest", + "gpt-5.2", + "gpt-5.2-2025-12-11", + "gpt-5.2-chat-latest", + "gpt-5.2-pro", + "gpt-5.2-pro-2025-12-11", + "gpt-5.1", + "gpt-5.1-2025-11-13", + "gpt-5.1-codex", + "gpt-5.1-mini", + "gpt-5.1-chat-latest", + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-5-chat-latest", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o4-mini", + "o4-mini-2025-04-16", + "o3", + "o3-2025-04-16", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "o1-preview", + "o1-preview-2024-09-12", + "o1-mini", + "o1-mini-2024-09-12", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-audio-preview", + "gpt-4o-audio-preview-2024-10-01", + "gpt-4o-audio-preview-2024-12-17", + "gpt-4o-audio-preview-2025-06-03", + "gpt-4o-mini-audio-preview", + "gpt-4o-mini-audio-preview-2024-12-17", + "gpt-4o-search-preview", + "gpt-4o-mini-search-preview", + "gpt-4o-search-preview-2025-03-11", + "gpt-4o-mini-search-preview-2025-03-11", + "chatgpt-4o-latest", + "codex-mini-latest", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0301", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613", + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max", + ], + str, + None, + ] + ] + """Model ID used to generate the response, like `gpt-5` or `o3`. + + OpenAI offers a wide range of models with different capabilities, performance + characteristics, and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + """ + + input: Union[str, Iterable[ResponseInputItemParam], None] + """Text, image, or file inputs to the model, used to generate a response""" + + instructions: Optional[str] + """ + A system (or developer) message inserted into the model's context. When used + along with `previous_response_id`, the instructions from a previous response + will not be carried over to the next response. This makes it simple to swap out + system (or developer) messages in new responses. + """ + + previous_response_id: Optional[str] + """The unique ID of the previous response to the model. + + Use this to create multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + """ + + prompt_cache_key: Optional[str] + """A key to use when reading from or writing to the prompt cache.""" + + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] + """How long to retain a prompt cache entry created by this request.""" + + service_tier: Optional[Literal["auto", "default", "flex", "priority"]] + """The service tier to use for this request.""" diff --git a/src/openai/types/responses/response_compaction_item.py b/src/openai/types/responses/response_compaction_item.py new file mode 100644 index 0000000000..36e953b127 --- /dev/null +++ b/src/openai/types/responses/response_compaction_item.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCompactionItem"] + + +class ResponseCompactionItem(BaseModel): + """ + A compaction item generated by the [`v1/responses/compact` API](https://platform.openai.com/docs/api-reference/responses/compact). + """ + + id: str + """The unique ID of the compaction item.""" + + encrypted_content: str + """The encrypted content that was produced by compaction.""" + + type: Literal["compaction"] + """The type of the item. Always `compaction`.""" + + created_by: Optional[str] = None + """The identifier of the actor that created the item.""" diff --git a/src/openai/types/responses/response_compaction_item_param.py b/src/openai/types/responses/response_compaction_item_param.py new file mode 100644 index 0000000000..5ef134b074 --- /dev/null +++ b/src/openai/types/responses/response_compaction_item_param.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCompactionItemParam"] + + +class ResponseCompactionItemParam(BaseModel): + """ + A compaction item generated by the [`v1/responses/compact` API](https://platform.openai.com/docs/api-reference/responses/compact). + """ + + encrypted_content: str + """The encrypted content of the compaction summary.""" + + type: Literal["compaction"] + """The type of the item. Always `compaction`.""" + + id: Optional[str] = None + """The ID of the compaction item.""" diff --git a/src/openai/types/responses/response_compaction_item_param_param.py b/src/openai/types/responses/response_compaction_item_param_param.py new file mode 100644 index 0000000000..b4d72c2e5f --- /dev/null +++ b/src/openai/types/responses/response_compaction_item_param_param.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseCompactionItemParamParam"] + + +class ResponseCompactionItemParamParam(TypedDict, total=False): + """ + A compaction item generated by the [`v1/responses/compact` API](https://platform.openai.com/docs/api-reference/responses/compact). + """ + + encrypted_content: Required[str] + """The encrypted content of the compaction summary.""" + + type: Required[Literal["compaction"]] + """The type of the item. Always `compaction`.""" + + id: Optional[str] + """The ID of the compaction item.""" diff --git a/src/openai/types/responses/response_completed_event.py b/src/openai/types/responses/response_completed_event.py new file mode 100644 index 0000000000..6dc958101c --- /dev/null +++ b/src/openai/types/responses/response_completed_event.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .response import Response +from ..._models import BaseModel + +__all__ = ["ResponseCompletedEvent"] + + +class ResponseCompletedEvent(BaseModel): + """Emitted when the model response is complete.""" + + response: Response + """Properties of the completed response.""" + + sequence_number: int + """The sequence number for this event.""" + + type: Literal["response.completed"] + """The type of the event. Always `response.completed`.""" diff --git a/src/openai/types/responses/response_computer_tool_call.py b/src/openai/types/responses/response_computer_tool_call.py new file mode 100644 index 0000000000..e0339e4afd --- /dev/null +++ b/src/openai/types/responses/response_computer_tool_call.py @@ -0,0 +1,259 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .computer_action_list import ComputerActionList + +__all__ = [ + "ResponseComputerToolCall", + "PendingSafetyCheck", + "Action", + "ActionClick", + "ActionDoubleClick", + "ActionDrag", + "ActionDragPath", + "ActionKeypress", + "ActionMove", + "ActionScreenshot", + "ActionScroll", + "ActionType", + "ActionWait", +] + + +class PendingSafetyCheck(BaseModel): + """A pending safety check for the computer call.""" + + id: str + """The ID of the pending safety check.""" + + code: Optional[str] = None + """The type of the pending safety check.""" + + message: Optional[str] = None + """Details about the pending safety check.""" + + +class ActionClick(BaseModel): + """A click action.""" + + button: Literal["left", "right", "wheel", "back", "forward"] + """Indicates which mouse button was pressed during the click. + + One of `left`, `right`, `wheel`, `back`, or `forward`. + """ + + type: Literal["click"] + """Specifies the event type. For a click action, this property is always `click`.""" + + x: int + """The x-coordinate where the click occurred.""" + + y: int + """The y-coordinate where the click occurred.""" + + keys: Optional[List[str]] = None + """The keys being held while clicking.""" + + +class ActionDoubleClick(BaseModel): + """A double click action.""" + + keys: Optional[List[str]] = None + """The keys being held while double-clicking.""" + + type: Literal["double_click"] + """Specifies the event type. + + For a double click action, this property is always set to `double_click`. + """ + + x: int + """The x-coordinate where the double click occurred.""" + + y: int + """The y-coordinate where the double click occurred.""" + + +class ActionDragPath(BaseModel): + """An x/y coordinate pair, e.g. `{ x: 100, y: 200 }`.""" + + x: int + """The x-coordinate.""" + + y: int + """The y-coordinate.""" + + +class ActionDrag(BaseModel): + """A drag action.""" + + path: List[ActionDragPath] + """An array of coordinates representing the path of the drag action. + + Coordinates will appear as an array of objects, eg + + ``` + [ + { x: 100, y: 200 }, + { x: 200, y: 300 } + ] + ``` + """ + + type: Literal["drag"] + """Specifies the event type. + + For a drag action, this property is always set to `drag`. + """ + + keys: Optional[List[str]] = None + """The keys being held while dragging the mouse.""" + + +class ActionKeypress(BaseModel): + """A collection of keypresses the model would like to perform.""" + + keys: List[str] + """The combination of keys the model is requesting to be pressed. + + This is an array of strings, each representing a key. + """ + + type: Literal["keypress"] + """Specifies the event type. + + For a keypress action, this property is always set to `keypress`. + """ + + +class ActionMove(BaseModel): + """A mouse move action.""" + + type: Literal["move"] + """Specifies the event type. + + For a move action, this property is always set to `move`. + """ + + x: int + """The x-coordinate to move to.""" + + y: int + """The y-coordinate to move to.""" + + keys: Optional[List[str]] = None + """The keys being held while moving the mouse.""" + + +class ActionScreenshot(BaseModel): + """A screenshot action.""" + + type: Literal["screenshot"] + """Specifies the event type. + + For a screenshot action, this property is always set to `screenshot`. + """ + + +class ActionScroll(BaseModel): + """A scroll action.""" + + scroll_x: int + """The horizontal scroll distance.""" + + scroll_y: int + """The vertical scroll distance.""" + + type: Literal["scroll"] + """Specifies the event type. + + For a scroll action, this property is always set to `scroll`. + """ + + x: int + """The x-coordinate where the scroll occurred.""" + + y: int + """The y-coordinate where the scroll occurred.""" + + keys: Optional[List[str]] = None + """The keys being held while scrolling.""" + + +class ActionType(BaseModel): + """An action to type in text.""" + + text: str + """The text to type.""" + + type: Literal["type"] + """Specifies the event type. + + For a type action, this property is always set to `type`. + """ + + +class ActionWait(BaseModel): + """A wait action.""" + + type: Literal["wait"] + """Specifies the event type. + + For a wait action, this property is always set to `wait`. + """ + + +Action: TypeAlias = Annotated[ + Union[ + ActionClick, + ActionDoubleClick, + ActionDrag, + ActionKeypress, + ActionMove, + ActionScreenshot, + ActionScroll, + ActionType, + ActionWait, + ], + PropertyInfo(discriminator="type"), +] + + +class ResponseComputerToolCall(BaseModel): + """A tool call to a computer use tool. + + See the + [computer use guide](https://platform.openai.com/docs/guides/tools-computer-use) for more information. + """ + + id: str + """The unique ID of the computer call.""" + + call_id: str + """An identifier used when responding to the tool call with output.""" + + pending_safety_checks: List[PendingSafetyCheck] + """The pending safety checks for the computer call.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + type: Literal["computer_call"] + """The type of the computer call. Always `computer_call`.""" + + action: Optional[Action] = None + """A click action.""" + + actions: Optional[ComputerActionList] = None + """Flattened batched actions for `computer_use`. + + Each action includes an `type` discriminator and action-specific fields. + """ diff --git a/src/openai/types/responses/response_computer_tool_call_output_item.py b/src/openai/types/responses/response_computer_tool_call_output_item.py new file mode 100644 index 0000000000..bf5555d056 --- /dev/null +++ b/src/openai/types/responses/response_computer_tool_call_output_item.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .response_computer_tool_call_output_screenshot import ResponseComputerToolCallOutputScreenshot + +__all__ = ["ResponseComputerToolCallOutputItem", "AcknowledgedSafetyCheck"] + + +class AcknowledgedSafetyCheck(BaseModel): + """A pending safety check for the computer call.""" + + id: str + """The ID of the pending safety check.""" + + code: Optional[str] = None + """The type of the pending safety check.""" + + message: Optional[str] = None + """Details about the pending safety check.""" + + +class ResponseComputerToolCallOutputItem(BaseModel): + id: str + """The unique ID of the computer call tool output.""" + + call_id: str + """The ID of the computer tool call that produced the output.""" + + output: ResponseComputerToolCallOutputScreenshot + """A computer screenshot image used with the computer use tool.""" + + status: Literal["completed", "incomplete", "failed", "in_progress"] + """The status of the message input. + + One of `in_progress`, `completed`, or `incomplete`. Populated when input items + are returned via API. + """ + + type: Literal["computer_call_output"] + """The type of the computer tool call output. Always `computer_call_output`.""" + + acknowledged_safety_checks: Optional[List[AcknowledgedSafetyCheck]] = None + """ + The safety checks reported by the API that have been acknowledged by the + developer. + """ + + created_by: Optional[str] = None + """The identifier of the actor that created the item.""" diff --git a/src/openai/types/responses/response_computer_tool_call_output_screenshot.py b/src/openai/types/responses/response_computer_tool_call_output_screenshot.py new file mode 100644 index 0000000000..2c16f215eb --- /dev/null +++ b/src/openai/types/responses/response_computer_tool_call_output_screenshot.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseComputerToolCallOutputScreenshot"] + + +class ResponseComputerToolCallOutputScreenshot(BaseModel): + """A computer screenshot image used with the computer use tool.""" + + type: Literal["computer_screenshot"] + """Specifies the event type. + + For a computer screenshot, this property is always set to `computer_screenshot`. + """ + + file_id: Optional[str] = None + """The identifier of an uploaded file that contains the screenshot.""" + + image_url: Optional[str] = None + """The URL of the screenshot image.""" diff --git a/src/openai/types/responses/response_computer_tool_call_output_screenshot_param.py b/src/openai/types/responses/response_computer_tool_call_output_screenshot_param.py new file mode 100644 index 0000000000..857ccf9fb9 --- /dev/null +++ b/src/openai/types/responses/response_computer_tool_call_output_screenshot_param.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseComputerToolCallOutputScreenshotParam"] + + +class ResponseComputerToolCallOutputScreenshotParam(TypedDict, total=False): + """A computer screenshot image used with the computer use tool.""" + + type: Required[Literal["computer_screenshot"]] + """Specifies the event type. + + For a computer screenshot, this property is always set to `computer_screenshot`. + """ + + file_id: str + """The identifier of an uploaded file that contains the screenshot.""" + + image_url: str + """The URL of the screenshot image.""" diff --git a/src/openai/types/responses/response_computer_tool_call_param.py b/src/openai/types/responses/response_computer_tool_call_param.py new file mode 100644 index 0000000000..3c121097e5 --- /dev/null +++ b/src/openai/types/responses/response_computer_tool_call_param.py @@ -0,0 +1,257 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from .computer_action_list_param import ComputerActionListParam + +__all__ = [ + "ResponseComputerToolCallParam", + "PendingSafetyCheck", + "Action", + "ActionClick", + "ActionDoubleClick", + "ActionDrag", + "ActionDragPath", + "ActionKeypress", + "ActionMove", + "ActionScreenshot", + "ActionScroll", + "ActionType", + "ActionWait", +] + + +class PendingSafetyCheck(TypedDict, total=False): + """A pending safety check for the computer call.""" + + id: Required[str] + """The ID of the pending safety check.""" + + code: Optional[str] + """The type of the pending safety check.""" + + message: Optional[str] + """Details about the pending safety check.""" + + +class ActionClick(TypedDict, total=False): + """A click action.""" + + button: Required[Literal["left", "right", "wheel", "back", "forward"]] + """Indicates which mouse button was pressed during the click. + + One of `left`, `right`, `wheel`, `back`, or `forward`. + """ + + type: Required[Literal["click"]] + """Specifies the event type. For a click action, this property is always `click`.""" + + x: Required[int] + """The x-coordinate where the click occurred.""" + + y: Required[int] + """The y-coordinate where the click occurred.""" + + keys: Optional[SequenceNotStr[str]] + """The keys being held while clicking.""" + + +class ActionDoubleClick(TypedDict, total=False): + """A double click action.""" + + keys: Required[Optional[SequenceNotStr[str]]] + """The keys being held while double-clicking.""" + + type: Required[Literal["double_click"]] + """Specifies the event type. + + For a double click action, this property is always set to `double_click`. + """ + + x: Required[int] + """The x-coordinate where the double click occurred.""" + + y: Required[int] + """The y-coordinate where the double click occurred.""" + + +class ActionDragPath(TypedDict, total=False): + """An x/y coordinate pair, e.g. `{ x: 100, y: 200 }`.""" + + x: Required[int] + """The x-coordinate.""" + + y: Required[int] + """The y-coordinate.""" + + +class ActionDrag(TypedDict, total=False): + """A drag action.""" + + path: Required[Iterable[ActionDragPath]] + """An array of coordinates representing the path of the drag action. + + Coordinates will appear as an array of objects, eg + + ``` + [ + { x: 100, y: 200 }, + { x: 200, y: 300 } + ] + ``` + """ + + type: Required[Literal["drag"]] + """Specifies the event type. + + For a drag action, this property is always set to `drag`. + """ + + keys: Optional[SequenceNotStr[str]] + """The keys being held while dragging the mouse.""" + + +class ActionKeypress(TypedDict, total=False): + """A collection of keypresses the model would like to perform.""" + + keys: Required[SequenceNotStr[str]] + """The combination of keys the model is requesting to be pressed. + + This is an array of strings, each representing a key. + """ + + type: Required[Literal["keypress"]] + """Specifies the event type. + + For a keypress action, this property is always set to `keypress`. + """ + + +class ActionMove(TypedDict, total=False): + """A mouse move action.""" + + type: Required[Literal["move"]] + """Specifies the event type. + + For a move action, this property is always set to `move`. + """ + + x: Required[int] + """The x-coordinate to move to.""" + + y: Required[int] + """The y-coordinate to move to.""" + + keys: Optional[SequenceNotStr[str]] + """The keys being held while moving the mouse.""" + + +class ActionScreenshot(TypedDict, total=False): + """A screenshot action.""" + + type: Required[Literal["screenshot"]] + """Specifies the event type. + + For a screenshot action, this property is always set to `screenshot`. + """ + + +class ActionScroll(TypedDict, total=False): + """A scroll action.""" + + scroll_x: Required[int] + """The horizontal scroll distance.""" + + scroll_y: Required[int] + """The vertical scroll distance.""" + + type: Required[Literal["scroll"]] + """Specifies the event type. + + For a scroll action, this property is always set to `scroll`. + """ + + x: Required[int] + """The x-coordinate where the scroll occurred.""" + + y: Required[int] + """The y-coordinate where the scroll occurred.""" + + keys: Optional[SequenceNotStr[str]] + """The keys being held while scrolling.""" + + +class ActionType(TypedDict, total=False): + """An action to type in text.""" + + text: Required[str] + """The text to type.""" + + type: Required[Literal["type"]] + """Specifies the event type. + + For a type action, this property is always set to `type`. + """ + + +class ActionWait(TypedDict, total=False): + """A wait action.""" + + type: Required[Literal["wait"]] + """Specifies the event type. + + For a wait action, this property is always set to `wait`. + """ + + +Action: TypeAlias = Union[ + ActionClick, + ActionDoubleClick, + ActionDrag, + ActionKeypress, + ActionMove, + ActionScreenshot, + ActionScroll, + ActionType, + ActionWait, +] + + +class ResponseComputerToolCallParam(TypedDict, total=False): + """A tool call to a computer use tool. + + See the + [computer use guide](https://platform.openai.com/docs/guides/tools-computer-use) for more information. + """ + + id: Required[str] + """The unique ID of the computer call.""" + + call_id: Required[str] + """An identifier used when responding to the tool call with output.""" + + pending_safety_checks: Required[Iterable[PendingSafetyCheck]] + """The pending safety checks for the computer call.""" + + status: Required[Literal["in_progress", "completed", "incomplete"]] + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + type: Required[Literal["computer_call"]] + """The type of the computer call. Always `computer_call`.""" + + action: Action + """A click action.""" + + actions: ComputerActionListParam + """Flattened batched actions for `computer_use`. + + Each action includes an `type` discriminator and action-specific fields. + """ diff --git a/src/openai/types/responses/response_container_reference.py b/src/openai/types/responses/response_container_reference.py new file mode 100644 index 0000000000..81f5bd8dad --- /dev/null +++ b/src/openai/types/responses/response_container_reference.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseContainerReference"] + + +class ResponseContainerReference(BaseModel): + """Represents a container created with /v1/containers.""" + + container_id: str + + type: Literal["container_reference"] + """The environment type. Always `container_reference`.""" diff --git a/src/openai/types/responses/response_content_part_added_event.py b/src/openai/types/responses/response_content_part_added_event.py new file mode 100644 index 0000000000..ec9893159d --- /dev/null +++ b/src/openai/types/responses/response_content_part_added_event.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .response_output_text import ResponseOutputText +from .response_output_refusal import ResponseOutputRefusal + +__all__ = ["ResponseContentPartAddedEvent", "Part", "PartReasoningText"] + + +class PartReasoningText(BaseModel): + """Reasoning text from the model.""" + + text: str + """The reasoning text from the model.""" + + type: Literal["reasoning_text"] + """The type of the reasoning text. Always `reasoning_text`.""" + + +Part: TypeAlias = Annotated[ + Union[ResponseOutputText, ResponseOutputRefusal, PartReasoningText], PropertyInfo(discriminator="type") +] + + +class ResponseContentPartAddedEvent(BaseModel): + """Emitted when a new content part is added.""" + + content_index: int + """The index of the content part that was added.""" + + item_id: str + """The ID of the output item that the content part was added to.""" + + output_index: int + """The index of the output item that the content part was added to.""" + + part: Part + """The content part that was added.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.content_part.added"] + """The type of the event. Always `response.content_part.added`.""" diff --git a/src/openai/types/responses/response_content_part_done_event.py b/src/openai/types/responses/response_content_part_done_event.py new file mode 100644 index 0000000000..f896ad8743 --- /dev/null +++ b/src/openai/types/responses/response_content_part_done_event.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .response_output_text import ResponseOutputText +from .response_output_refusal import ResponseOutputRefusal + +__all__ = ["ResponseContentPartDoneEvent", "Part", "PartReasoningText"] + + +class PartReasoningText(BaseModel): + """Reasoning text from the model.""" + + text: str + """The reasoning text from the model.""" + + type: Literal["reasoning_text"] + """The type of the reasoning text. Always `reasoning_text`.""" + + +Part: TypeAlias = Annotated[ + Union[ResponseOutputText, ResponseOutputRefusal, PartReasoningText], PropertyInfo(discriminator="type") +] + + +class ResponseContentPartDoneEvent(BaseModel): + """Emitted when a content part is done.""" + + content_index: int + """The index of the content part that is done.""" + + item_id: str + """The ID of the output item that the content part was added to.""" + + output_index: int + """The index of the output item that the content part was added to.""" + + part: Part + """The content part that is done.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.content_part.done"] + """The type of the event. Always `response.content_part.done`.""" diff --git a/src/openai/types/responses/response_conversation_param.py b/src/openai/types/responses/response_conversation_param.py new file mode 100644 index 0000000000..7db4129bf4 --- /dev/null +++ b/src/openai/types/responses/response_conversation_param.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["ResponseConversationParam"] + + +class ResponseConversationParam(BaseModel): + """The conversation that this response belongs to.""" + + id: str + """The unique ID of the conversation.""" diff --git a/src/openai/types/responses/response_conversation_param_param.py b/src/openai/types/responses/response_conversation_param_param.py new file mode 100644 index 0000000000..dba3628d0b --- /dev/null +++ b/src/openai/types/responses/response_conversation_param_param.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ResponseConversationParamParam"] + + +class ResponseConversationParamParam(TypedDict, total=False): + """The conversation that this response belongs to.""" + + id: Required[str] + """The unique ID of the conversation.""" diff --git a/src/openai/types/responses/response_create_params.py b/src/openai/types/responses/response_create_params.py new file mode 100644 index 0000000000..1b273928a1 --- /dev/null +++ b/src/openai/types/responses/response_create_params.py @@ -0,0 +1,372 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .tool_param import ToolParam +from .response_includable import ResponseIncludable +from .tool_choice_options import ToolChoiceOptions +from .response_input_param import ResponseInputParam +from .response_prompt_param import ResponsePromptParam +from .tool_choice_mcp_param import ToolChoiceMcpParam +from ..shared_params.metadata import Metadata +from .tool_choice_shell_param import ToolChoiceShellParam +from .tool_choice_types_param import ToolChoiceTypesParam +from ..shared_params.reasoning import Reasoning +from .tool_choice_custom_param import ToolChoiceCustomParam +from .tool_choice_allowed_param import ToolChoiceAllowedParam +from .response_text_config_param import ResponseTextConfigParam +from .tool_choice_function_param import ToolChoiceFunctionParam +from .tool_choice_apply_patch_param import ToolChoiceApplyPatchParam +from ..shared_params.responses_model import ResponsesModel +from .response_conversation_param_param import ResponseConversationParamParam + +__all__ = [ + "ResponseCreateParamsBase", + "ContextManagement", + "Conversation", + "Moderation", + "StreamOptions", + "ToolChoice", + "ResponseCreateParamsNonStreaming", + "ResponseCreateParamsStreaming", +] + + +class ResponseCreateParamsBase(TypedDict, total=False): + background: Optional[bool] + """ + Whether to run the model response in the background. + [Learn more](https://platform.openai.com/docs/guides/background). + """ + + context_management: Optional[Iterable[ContextManagement]] + """Context management configuration for this request.""" + + conversation: Optional[Conversation] + """The conversation that this response belongs to. + + Items from this conversation are prepended to `input_items` for this response + request. Input items and output items from this response are automatically added + to this conversation after this response completes. + """ + + include: Optional[List[ResponseIncludable]] + """Specify additional output data to include in the model response. + + Currently supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + """ + + input: Union[str, ResponseInputParam] + """Text, image, or file inputs to the model, used to generate a response. + + Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Image inputs](https://platform.openai.com/docs/guides/images) + - [File inputs](https://platform.openai.com/docs/guides/pdf-files) + - [Conversation state](https://platform.openai.com/docs/guides/conversation-state) + - [Function calling](https://platform.openai.com/docs/guides/function-calling) + """ + + instructions: Optional[str] + """A system (or developer) message inserted into the model's context. + + When using along with `previous_response_id`, the instructions from a previous + response will not be carried over to the next response. This makes it simple to + swap out system (or developer) messages in new responses. + """ + + max_output_tokens: Optional[int] + """ + An upper bound for the number of tokens that can be generated for a response, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + """ + + max_tool_calls: Optional[int] + """ + The maximum number of total calls to built-in tools that can be processed in a + response. This maximum number applies across all built-in tool calls, not per + individual tool. Any further attempts to call a tool by the model will be + ignored. + """ + + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: ResponsesModel + """Model ID used to generate the response, like `gpt-4o` or `o3`. + + OpenAI offers a wide range of models with different capabilities, performance + characteristics, and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + """ + + moderation: Optional[Moderation] + """Configuration for running moderation on the input and output of this response.""" + + parallel_tool_calls: Optional[bool] + """Whether to allow the model to run tool calls in parallel.""" + + previous_response_id: Optional[str] + """The unique ID of the previous response to the model. + + Use this to create multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + """ + + prompt: Optional[ResponsePromptParam] + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + prompt_cache_key: str + """ + Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + """ + + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] + """The retention policy for the prompt cache. + + Set to `24h` to enable extended prompt caching, which keeps cached prefixes + active for longer, up to a maximum of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + """ + + reasoning: Optional[Reasoning] + """**gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + safety_identifier: str + """ + A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + """ + + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] + """Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + """ + + store: Optional[bool] + """Whether to store the generated model response for later retrieval via API.""" + + stream_options: Optional[StreamOptions] + """Options for streaming responses. Only set this when you set `stream: true`.""" + + temperature: Optional[float] + """What sampling temperature to use, between 0 and 2. + + Higher values like 0.8 will make the output more random, while lower values like + 0.2 will make it more focused and deterministic. We generally recommend altering + this or `top_p` but not both. + """ + + text: ResponseTextConfigParam + """Configuration options for a text response from the model. + + Can be plain text or structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + tool_choice: ToolChoice + """ + How the model should select which tool (or tools) to use when generating a + response. See the `tools` parameter to see how to specify which tools the model + can call. + """ + + tools: Iterable[ToolParam] + """An array of tools the model may call while generating a response. + + You can specify which tool to use by setting the `tool_choice` parameter. + + We support the following categories of tools: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **MCP Tools**: Integrations with third-party systems via custom MCP servers or + predefined connectors such as Google Drive and SharePoint. Learn more about + [MCP Tools](https://platform.openai.com/docs/guides/tools-connectors-mcp). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code with strongly typed arguments and outputs. + Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + You can also use custom tools to call your own code. + """ + + top_logprobs: Optional[int] + """ + An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + """ + + top_p: Optional[float] + """ + An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + """ + + truncation: Optional[Literal["auto", "disabled"]] + """The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. + - `disabled` (default): If the input size will exceed the context window size + for a model, the request will fail with a 400 error. + """ + + user: str + """This field is being replaced by `safety_identifier` and `prompt_cache_key`. + + Use `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + """ + + +class ContextManagement(TypedDict, total=False): + type: Required[str] + """The context management entry type. Currently only 'compaction' is supported.""" + + compact_threshold: Optional[int] + """Token threshold at which compaction should be triggered for this entry.""" + + +Conversation: TypeAlias = Union[str, ResponseConversationParamParam] + + +class Moderation(TypedDict, total=False): + """Configuration for running moderation on the input and output of this response.""" + + model: Required[str] + """The moderation model to use for moderated completions, e.g. + + 'omni-moderation-latest'. + """ + + +class StreamOptions(TypedDict, total=False): + """Options for streaming responses. Only set this when you set `stream: true`.""" + + include_obfuscation: bool + """When true, stream obfuscation will be enabled. + + Stream obfuscation adds random characters to an `obfuscation` field on streaming + delta events to normalize payload sizes as a mitigation to certain side-channel + attacks. These obfuscation fields are included by default, but add a small + amount of overhead to the data stream. You can set `include_obfuscation` to + false to optimize for bandwidth if you trust the network links between your + application and the OpenAI API. + """ + + +ToolChoice: TypeAlias = Union[ + ToolChoiceOptions, + ToolChoiceAllowedParam, + ToolChoiceTypesParam, + ToolChoiceFunctionParam, + ToolChoiceMcpParam, + ToolChoiceCustomParam, + ToolChoiceApplyPatchParam, + ToolChoiceShellParam, +] + + +class ResponseCreateParamsNonStreaming(ResponseCreateParamsBase, total=False): + stream: Optional[Literal[False]] + """ + If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + """ + + +class ResponseCreateParamsStreaming(ResponseCreateParamsBase): + stream: Required[Literal[True]] + """ + If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + """ + + +ResponseCreateParams = Union[ResponseCreateParamsNonStreaming, ResponseCreateParamsStreaming] diff --git a/src/openai/types/responses/response_created_event.py b/src/openai/types/responses/response_created_event.py new file mode 100644 index 0000000000..308b2f4916 --- /dev/null +++ b/src/openai/types/responses/response_created_event.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .response import Response +from ..._models import BaseModel + +__all__ = ["ResponseCreatedEvent"] + + +class ResponseCreatedEvent(BaseModel): + """An event that is emitted when a response is created.""" + + response: Response + """The response that was created.""" + + sequence_number: int + """The sequence number for this event.""" + + type: Literal["response.created"] + """The type of the event. Always `response.created`.""" diff --git a/src/openai/types/responses/response_custom_tool_call.py b/src/openai/types/responses/response_custom_tool_call.py new file mode 100644 index 0000000000..965ed88f96 --- /dev/null +++ b/src/openai/types/responses/response_custom_tool_call.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCustomToolCall"] + + +class ResponseCustomToolCall(BaseModel): + """A call to a custom tool created by the model.""" + + call_id: str + """An identifier used to map this custom tool call to a tool call output.""" + + input: str + """The input for the custom tool call generated by the model.""" + + name: str + """The name of the custom tool being called.""" + + type: Literal["custom_tool_call"] + """The type of the custom tool call. Always `custom_tool_call`.""" + + id: Optional[str] = None + """The unique ID of the custom tool call in the OpenAI platform.""" + + namespace: Optional[str] = None + """The namespace of the custom tool being called.""" diff --git a/src/openai/types/responses/response_custom_tool_call_input_delta_event.py b/src/openai/types/responses/response_custom_tool_call_input_delta_event.py new file mode 100644 index 0000000000..7473d33d9a --- /dev/null +++ b/src/openai/types/responses/response_custom_tool_call_input_delta_event.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCustomToolCallInputDeltaEvent"] + + +class ResponseCustomToolCallInputDeltaEvent(BaseModel): + """Event representing a delta (partial update) to the input of a custom tool call.""" + + delta: str + """The incremental input data (delta) for the custom tool call.""" + + item_id: str + """Unique identifier for the API item associated with this event.""" + + output_index: int + """The index of the output this delta applies to.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.custom_tool_call_input.delta"] + """The event type identifier.""" diff --git a/src/openai/types/responses/response_custom_tool_call_input_done_event.py b/src/openai/types/responses/response_custom_tool_call_input_done_event.py new file mode 100644 index 0000000000..be47ae8e96 --- /dev/null +++ b/src/openai/types/responses/response_custom_tool_call_input_done_event.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCustomToolCallInputDoneEvent"] + + +class ResponseCustomToolCallInputDoneEvent(BaseModel): + """Event indicating that input for a custom tool call is complete.""" + + input: str + """The complete input data for the custom tool call.""" + + item_id: str + """Unique identifier for the API item associated with this event.""" + + output_index: int + """The index of the output this event applies to.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.custom_tool_call_input.done"] + """The event type identifier.""" diff --git a/src/openai/types/responses/response_custom_tool_call_item.py b/src/openai/types/responses/response_custom_tool_call_item.py new file mode 100644 index 0000000000..4f0f930674 --- /dev/null +++ b/src/openai/types/responses/response_custom_tool_call_item.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .response_custom_tool_call import ResponseCustomToolCall + +__all__ = ["ResponseCustomToolCallItem"] + + +class ResponseCustomToolCallItem(ResponseCustomToolCall): + """A call to a custom tool created by the model.""" + + id: str # type: ignore + """The unique ID of the custom tool call item.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + created_by: Optional[str] = None + """The identifier of the actor that created the item.""" diff --git a/src/openai/types/responses/response_custom_tool_call_output.py b/src/openai/types/responses/response_custom_tool_call_output.py new file mode 100644 index 0000000000..833956493b --- /dev/null +++ b/src/openai/types/responses/response_custom_tool_call_output.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .response_input_file import ResponseInputFile +from .response_input_text import ResponseInputText +from .response_input_image import ResponseInputImage + +__all__ = ["ResponseCustomToolCallOutput", "OutputOutputContentList"] + +OutputOutputContentList: TypeAlias = Annotated[ + Union[ResponseInputText, ResponseInputImage, ResponseInputFile], PropertyInfo(discriminator="type") +] + + +class ResponseCustomToolCallOutput(BaseModel): + """The output of a custom tool call from your code, being sent back to the model.""" + + call_id: str + """The call ID, used to map this custom tool call output to a custom tool call.""" + + output: Union[str, List[OutputOutputContentList]] + """ + The output from the custom tool call generated by your code. Can be a string or + an list of output content. + """ + + type: Literal["custom_tool_call_output"] + """The type of the custom tool call output. Always `custom_tool_call_output`.""" + + id: Optional[str] = None + """The unique ID of the custom tool call output in the OpenAI platform.""" diff --git a/src/openai/types/responses/response_custom_tool_call_output_item.py b/src/openai/types/responses/response_custom_tool_call_output_item.py new file mode 100644 index 0000000000..5e5a469e8d --- /dev/null +++ b/src/openai/types/responses/response_custom_tool_call_output_item.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .response_custom_tool_call_output import ResponseCustomToolCallOutput + +__all__ = ["ResponseCustomToolCallOutputItem"] + + +class ResponseCustomToolCallOutputItem(ResponseCustomToolCallOutput): + """The output of a custom tool call from your code, being sent back to the model.""" + + id: str # type: ignore + """The unique ID of the custom tool call output item.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + created_by: Optional[str] = None + """The identifier of the actor that created the item.""" diff --git a/src/openai/types/responses/response_custom_tool_call_output_param.py b/src/openai/types/responses/response_custom_tool_call_output_param.py new file mode 100644 index 0000000000..db0034216a --- /dev/null +++ b/src/openai/types/responses/response_custom_tool_call_output_param.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .response_input_file_param import ResponseInputFileParam +from .response_input_text_param import ResponseInputTextParam +from .response_input_image_param import ResponseInputImageParam + +__all__ = ["ResponseCustomToolCallOutputParam", "OutputOutputContentList"] + +OutputOutputContentList: TypeAlias = Union[ResponseInputTextParam, ResponseInputImageParam, ResponseInputFileParam] + + +class ResponseCustomToolCallOutputParam(TypedDict, total=False): + """The output of a custom tool call from your code, being sent back to the model.""" + + call_id: Required[str] + """The call ID, used to map this custom tool call output to a custom tool call.""" + + output: Required[Union[str, Iterable[OutputOutputContentList]]] + """ + The output from the custom tool call generated by your code. Can be a string or + an list of output content. + """ + + type: Required[Literal["custom_tool_call_output"]] + """The type of the custom tool call output. Always `custom_tool_call_output`.""" + + id: str + """The unique ID of the custom tool call output in the OpenAI platform.""" diff --git a/src/openai/types/responses/response_custom_tool_call_param.py b/src/openai/types/responses/response_custom_tool_call_param.py new file mode 100644 index 0000000000..9f82546ef1 --- /dev/null +++ b/src/openai/types/responses/response_custom_tool_call_param.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseCustomToolCallParam"] + + +class ResponseCustomToolCallParam(TypedDict, total=False): + """A call to a custom tool created by the model.""" + + call_id: Required[str] + """An identifier used to map this custom tool call to a tool call output.""" + + input: Required[str] + """The input for the custom tool call generated by the model.""" + + name: Required[str] + """The name of the custom tool being called.""" + + type: Required[Literal["custom_tool_call"]] + """The type of the custom tool call. Always `custom_tool_call`.""" + + id: str + """The unique ID of the custom tool call in the OpenAI platform.""" + + namespace: str + """The namespace of the custom tool being called.""" diff --git a/src/openai/types/responses/response_error.py b/src/openai/types/responses/response_error.py new file mode 100644 index 0000000000..90958d1c13 --- /dev/null +++ b/src/openai/types/responses/response_error.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseError"] + + +class ResponseError(BaseModel): + """An error object returned when the model fails to generate a Response.""" + + code: Literal[ + "server_error", + "rate_limit_exceeded", + "invalid_prompt", + "vector_store_timeout", + "invalid_image", + "invalid_image_format", + "invalid_base64_image", + "invalid_image_url", + "image_too_large", + "image_too_small", + "image_parse_error", + "image_content_policy_violation", + "invalid_image_mode", + "image_file_too_large", + "unsupported_image_media_type", + "empty_image_file", + "failed_to_download_image", + "image_file_not_found", + ] + """The error code for the response.""" + + message: str + """A human-readable description of the error.""" diff --git a/src/openai/types/responses/response_error_event.py b/src/openai/types/responses/response_error_event.py new file mode 100644 index 0000000000..1789f731b4 --- /dev/null +++ b/src/openai/types/responses/response_error_event.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseErrorEvent"] + + +class ResponseErrorEvent(BaseModel): + """Emitted when an error occurs.""" + + code: Optional[str] = None + """The error code.""" + + message: str + """The error message.""" + + param: Optional[str] = None + """The error parameter.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["error"] + """The type of the event. Always `error`.""" diff --git a/src/openai/types/responses/response_failed_event.py b/src/openai/types/responses/response_failed_event.py new file mode 100644 index 0000000000..2232c9678d --- /dev/null +++ b/src/openai/types/responses/response_failed_event.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .response import Response +from ..._models import BaseModel + +__all__ = ["ResponseFailedEvent"] + + +class ResponseFailedEvent(BaseModel): + """An event that is emitted when a response fails.""" + + response: Response + """The response that failed.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.failed"] + """The type of the event. Always `response.failed`.""" diff --git a/src/openai/types/responses/response_file_search_call_completed_event.py b/src/openai/types/responses/response_file_search_call_completed_event.py new file mode 100644 index 0000000000..88ffa5ac56 --- /dev/null +++ b/src/openai/types/responses/response_file_search_call_completed_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFileSearchCallCompletedEvent"] + + +class ResponseFileSearchCallCompletedEvent(BaseModel): + """Emitted when a file search call is completed (results found).""" + + item_id: str + """The ID of the output item that the file search call is initiated.""" + + output_index: int + """The index of the output item that the file search call is initiated.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.file_search_call.completed"] + """The type of the event. Always `response.file_search_call.completed`.""" diff --git a/src/openai/types/responses/response_file_search_call_in_progress_event.py b/src/openai/types/responses/response_file_search_call_in_progress_event.py new file mode 100644 index 0000000000..4f3504fda4 --- /dev/null +++ b/src/openai/types/responses/response_file_search_call_in_progress_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFileSearchCallInProgressEvent"] + + +class ResponseFileSearchCallInProgressEvent(BaseModel): + """Emitted when a file search call is initiated.""" + + item_id: str + """The ID of the output item that the file search call is initiated.""" + + output_index: int + """The index of the output item that the file search call is initiated.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.file_search_call.in_progress"] + """The type of the event. Always `response.file_search_call.in_progress`.""" diff --git a/src/openai/types/responses/response_file_search_call_searching_event.py b/src/openai/types/responses/response_file_search_call_searching_event.py new file mode 100644 index 0000000000..5bf1a076dd --- /dev/null +++ b/src/openai/types/responses/response_file_search_call_searching_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFileSearchCallSearchingEvent"] + + +class ResponseFileSearchCallSearchingEvent(BaseModel): + """Emitted when a file search is currently searching.""" + + item_id: str + """The ID of the output item that the file search call is initiated.""" + + output_index: int + """The index of the output item that the file search call is searching.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.file_search_call.searching"] + """The type of the event. Always `response.file_search_call.searching`.""" diff --git a/src/openai/types/responses/response_file_search_tool_call.py b/src/openai/types/responses/response_file_search_tool_call.py new file mode 100644 index 0000000000..fa45631345 --- /dev/null +++ b/src/openai/types/responses/response_file_search_tool_call.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFileSearchToolCall", "Result"] + + +class Result(BaseModel): + attributes: Optional[Dict[str, Union[str, float, bool]]] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. Keys are + strings with a maximum length of 64 characters. Values are strings with a + maximum length of 512 characters, booleans, or numbers. + """ + + file_id: Optional[str] = None + """The unique ID of the file.""" + + filename: Optional[str] = None + """The name of the file.""" + + score: Optional[float] = None + """The relevance score of the file - a value between 0 and 1.""" + + text: Optional[str] = None + """The text that was retrieved from the file.""" + + +class ResponseFileSearchToolCall(BaseModel): + """The results of a file search tool call. + + See the + [file search guide](https://platform.openai.com/docs/guides/tools-file-search) for more information. + """ + + id: str + """The unique ID of the file search tool call.""" + + queries: List[str] + """The queries used to search for files.""" + + status: Literal["in_progress", "searching", "completed", "incomplete", "failed"] + """The status of the file search tool call. + + One of `in_progress`, `searching`, `incomplete` or `failed`, + """ + + type: Literal["file_search_call"] + """The type of the file search tool call. Always `file_search_call`.""" + + results: Optional[List[Result]] = None + """The results of the file search tool call.""" diff --git a/src/openai/types/responses/response_file_search_tool_call_param.py b/src/openai/types/responses/response_file_search_tool_call_param.py new file mode 100644 index 0000000000..45a5bbb486 --- /dev/null +++ b/src/openai/types/responses/response_file_search_tool_call_param.py @@ -0,0 +1,59 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["ResponseFileSearchToolCallParam", "Result"] + + +class Result(TypedDict, total=False): + attributes: Optional[Dict[str, Union[str, float, bool]]] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. Keys are + strings with a maximum length of 64 characters. Values are strings with a + maximum length of 512 characters, booleans, or numbers. + """ + + file_id: str + """The unique ID of the file.""" + + filename: str + """The name of the file.""" + + score: float + """The relevance score of the file - a value between 0 and 1.""" + + text: str + """The text that was retrieved from the file.""" + + +class ResponseFileSearchToolCallParam(TypedDict, total=False): + """The results of a file search tool call. + + See the + [file search guide](https://platform.openai.com/docs/guides/tools-file-search) for more information. + """ + + id: Required[str] + """The unique ID of the file search tool call.""" + + queries: Required[SequenceNotStr[str]] + """The queries used to search for files.""" + + status: Required[Literal["in_progress", "searching", "completed", "incomplete", "failed"]] + """The status of the file search tool call. + + One of `in_progress`, `searching`, `incomplete` or `failed`, + """ + + type: Required[Literal["file_search_call"]] + """The type of the file search tool call. Always `file_search_call`.""" + + results: Optional[Iterable[Result]] + """The results of the file search tool call.""" diff --git a/src/openai/types/responses/response_format_text_config.py b/src/openai/types/responses/response_format_text_config.py new file mode 100644 index 0000000000..a4896bf9fe --- /dev/null +++ b/src/openai/types/responses/response_format_text_config.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..shared.response_format_text import ResponseFormatText +from ..shared.response_format_json_object import ResponseFormatJSONObject +from .response_format_text_json_schema_config import ResponseFormatTextJSONSchemaConfig + +__all__ = ["ResponseFormatTextConfig"] + +ResponseFormatTextConfig: TypeAlias = Annotated[ + Union[ResponseFormatText, ResponseFormatTextJSONSchemaConfig, ResponseFormatJSONObject], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/responses/response_format_text_config_param.py b/src/openai/types/responses/response_format_text_config_param.py new file mode 100644 index 0000000000..fcaf8f3fb6 --- /dev/null +++ b/src/openai/types/responses/response_format_text_config_param.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypeAlias + +from ..shared_params.response_format_text import ResponseFormatText +from ..shared_params.response_format_json_object import ResponseFormatJSONObject +from .response_format_text_json_schema_config_param import ResponseFormatTextJSONSchemaConfigParam + +__all__ = ["ResponseFormatTextConfigParam"] + +ResponseFormatTextConfigParam: TypeAlias = Union[ + ResponseFormatText, ResponseFormatTextJSONSchemaConfigParam, ResponseFormatJSONObject +] diff --git a/src/openai/types/responses/response_format_text_json_schema_config.py b/src/openai/types/responses/response_format_text_json_schema_config.py new file mode 100644 index 0000000000..b953112621 --- /dev/null +++ b/src/openai/types/responses/response_format_text_json_schema_config.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["ResponseFormatTextJSONSchemaConfig"] + + +class ResponseFormatTextJSONSchemaConfig(BaseModel): + """JSON Schema response format. + + Used to generate structured JSON responses. + Learn more about [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs). + """ + + name: str + """The name of the response format. + + Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length + of 64. + """ + + schema_: Dict[str, object] = FieldInfo(alias="schema") + """ + The schema for the response format, described as a JSON Schema object. Learn how + to build JSON schemas [here](https://json-schema.org/). + """ + + type: Literal["json_schema"] + """The type of response format being defined. Always `json_schema`.""" + + description: Optional[str] = None + """ + A description of what the response format is for, used by the model to determine + how to respond in the format. + """ + + strict: Optional[bool] = None + """ + Whether to enable strict schema adherence when generating the output. If set to + true, the model will always follow the exact schema defined in the `schema` + field. Only a subset of JSON Schema is supported when `strict` is `true`. To + learn more, read the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + """ diff --git a/src/openai/types/responses/response_format_text_json_schema_config_param.py b/src/openai/types/responses/response_format_text_json_schema_config_param.py new file mode 100644 index 0000000000..6f5c633106 --- /dev/null +++ b/src/openai/types/responses/response_format_text_json_schema_config_param.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseFormatTextJSONSchemaConfigParam"] + + +class ResponseFormatTextJSONSchemaConfigParam(TypedDict, total=False): + """JSON Schema response format. + + Used to generate structured JSON responses. + Learn more about [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs). + """ + + name: Required[str] + """The name of the response format. + + Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length + of 64. + """ + + schema: Required[Dict[str, object]] + """ + The schema for the response format, described as a JSON Schema object. Learn how + to build JSON schemas [here](https://json-schema.org/). + """ + + type: Required[Literal["json_schema"]] + """The type of response format being defined. Always `json_schema`.""" + + description: str + """ + A description of what the response format is for, used by the model to determine + how to respond in the format. + """ + + strict: Optional[bool] + """ + Whether to enable strict schema adherence when generating the output. If set to + true, the model will always follow the exact schema defined in the `schema` + field. Only a subset of JSON Schema is supported when `strict` is `true`. To + learn more, read the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + """ diff --git a/src/openai/types/responses/response_function_call_arguments_delta_event.py b/src/openai/types/responses/response_function_call_arguments_delta_event.py new file mode 100644 index 0000000000..0798c2e123 --- /dev/null +++ b/src/openai/types/responses/response_function_call_arguments_delta_event.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFunctionCallArgumentsDeltaEvent"] + + +class ResponseFunctionCallArgumentsDeltaEvent(BaseModel): + """Emitted when there is a partial function-call arguments delta.""" + + delta: str + """The function-call arguments delta that is added.""" + + item_id: str + """The ID of the output item that the function-call arguments delta is added to.""" + + output_index: int + """ + The index of the output item that the function-call arguments delta is added to. + """ + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.function_call_arguments.delta"] + """The type of the event. Always `response.function_call_arguments.delta`.""" diff --git a/src/openai/types/responses/response_function_call_arguments_done_event.py b/src/openai/types/responses/response_function_call_arguments_done_event.py new file mode 100644 index 0000000000..543cd073a2 --- /dev/null +++ b/src/openai/types/responses/response_function_call_arguments_done_event.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFunctionCallArgumentsDoneEvent"] + + +class ResponseFunctionCallArgumentsDoneEvent(BaseModel): + """Emitted when function-call arguments are finalized.""" + + arguments: str + """The function-call arguments.""" + + item_id: str + """The ID of the item.""" + + name: str + """The name of the function that was called.""" + + output_index: int + """The index of the output item.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.function_call_arguments.done"] diff --git a/src/openai/types/responses/response_function_call_output_item.py b/src/openai/types/responses/response_function_call_output_item.py new file mode 100644 index 0000000000..41898f9eda --- /dev/null +++ b/src/openai/types/responses/response_function_call_output_item.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from .response_input_file_content import ResponseInputFileContent +from .response_input_text_content import ResponseInputTextContent +from .response_input_image_content import ResponseInputImageContent + +__all__ = ["ResponseFunctionCallOutputItem"] + +ResponseFunctionCallOutputItem: TypeAlias = Annotated[ + Union[ResponseInputTextContent, ResponseInputImageContent, ResponseInputFileContent], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/responses/response_function_call_output_item_list.py b/src/openai/types/responses/response_function_call_output_item_list.py new file mode 100644 index 0000000000..13db577160 --- /dev/null +++ b/src/openai/types/responses/response_function_call_output_item_list.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .response_function_call_output_item import ResponseFunctionCallOutputItem + +__all__ = ["ResponseFunctionCallOutputItemList"] + +ResponseFunctionCallOutputItemList: TypeAlias = List[ResponseFunctionCallOutputItem] diff --git a/src/openai/types/responses/response_function_call_output_item_list_param.py b/src/openai/types/responses/response_function_call_output_item_list_param.py new file mode 100644 index 0000000000..8c286d3cf0 --- /dev/null +++ b/src/openai/types/responses/response_function_call_output_item_list_param.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union +from typing_extensions import TypeAlias + +from .response_input_file_content_param import ResponseInputFileContentParam +from .response_input_text_content_param import ResponseInputTextContentParam +from .response_input_image_content_param import ResponseInputImageContentParam + +__all__ = ["ResponseFunctionCallOutputItemListParam", "ResponseFunctionCallOutputItemParam"] + +ResponseFunctionCallOutputItemParam: TypeAlias = Union[ + ResponseInputTextContentParam, ResponseInputImageContentParam, ResponseInputFileContentParam +] + +ResponseFunctionCallOutputItemListParam: TypeAlias = List[ResponseFunctionCallOutputItemParam] diff --git a/src/openai/types/responses/response_function_call_output_item_param.py b/src/openai/types/responses/response_function_call_output_item_param.py new file mode 100644 index 0000000000..2a703cac1e --- /dev/null +++ b/src/openai/types/responses/response_function_call_output_item_param.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypeAlias + +from .response_input_file_content_param import ResponseInputFileContentParam +from .response_input_text_content_param import ResponseInputTextContentParam +from .response_input_image_content_param import ResponseInputImageContentParam + +__all__ = ["ResponseFunctionCallOutputItemParam"] + +ResponseFunctionCallOutputItemParam: TypeAlias = Union[ + ResponseInputTextContentParam, ResponseInputImageContentParam, ResponseInputFileContentParam +] diff --git a/src/openai/types/responses/response_function_shell_call_output_content.py b/src/openai/types/responses/response_function_shell_call_output_content.py new file mode 100644 index 0000000000..dae48f14da --- /dev/null +++ b/src/openai/types/responses/response_function_shell_call_output_content.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = ["ResponseFunctionShellCallOutputContent", "Outcome", "OutcomeTimeout", "OutcomeExit"] + + +class OutcomeTimeout(BaseModel): + """Indicates that the shell call exceeded its configured time limit.""" + + type: Literal["timeout"] + """The outcome type. Always `timeout`.""" + + +class OutcomeExit(BaseModel): + """Indicates that the shell commands finished and returned an exit code.""" + + exit_code: int + """The exit code returned by the shell process.""" + + type: Literal["exit"] + """The outcome type. Always `exit`.""" + + +Outcome: TypeAlias = Annotated[Union[OutcomeTimeout, OutcomeExit], PropertyInfo(discriminator="type")] + + +class ResponseFunctionShellCallOutputContent(BaseModel): + """Captured stdout and stderr for a portion of a shell tool call output.""" + + outcome: Outcome + """The exit or timeout outcome associated with this shell call.""" + + stderr: str + """Captured stderr output for the shell call.""" + + stdout: str + """Captured stdout output for the shell call.""" diff --git a/src/openai/types/responses/response_function_shell_call_output_content_param.py b/src/openai/types/responses/response_function_shell_call_output_content_param.py new file mode 100644 index 0000000000..4d8ea70d08 --- /dev/null +++ b/src/openai/types/responses/response_function_shell_call_output_content_param.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = ["ResponseFunctionShellCallOutputContentParam", "Outcome", "OutcomeTimeout", "OutcomeExit"] + + +class OutcomeTimeout(TypedDict, total=False): + """Indicates that the shell call exceeded its configured time limit.""" + + type: Required[Literal["timeout"]] + """The outcome type. Always `timeout`.""" + + +class OutcomeExit(TypedDict, total=False): + """Indicates that the shell commands finished and returned an exit code.""" + + exit_code: Required[int] + """The exit code returned by the shell process.""" + + type: Required[Literal["exit"]] + """The outcome type. Always `exit`.""" + + +Outcome: TypeAlias = Union[OutcomeTimeout, OutcomeExit] + + +class ResponseFunctionShellCallOutputContentParam(TypedDict, total=False): + """Captured stdout and stderr for a portion of a shell tool call output.""" + + outcome: Required[Outcome] + """The exit or timeout outcome associated with this shell call.""" + + stderr: Required[str] + """Captured stderr output for the shell call.""" + + stdout: Required[str] + """Captured stdout output for the shell call.""" diff --git a/src/openai/types/responses/response_function_shell_tool_call.py b/src/openai/types/responses/response_function_shell_tool_call.py new file mode 100644 index 0000000000..22c75cf043 --- /dev/null +++ b/src/openai/types/responses/response_function_shell_tool_call.py @@ -0,0 +1,59 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .response_local_environment import ResponseLocalEnvironment +from .response_container_reference import ResponseContainerReference + +__all__ = ["ResponseFunctionShellToolCall", "Action", "Environment"] + + +class Action(BaseModel): + """The shell commands and limits that describe how to run the tool call.""" + + commands: List[str] + + max_output_length: Optional[int] = None + """Optional maximum number of characters to return from each command.""" + + timeout_ms: Optional[int] = None + """Optional timeout in milliseconds for the commands.""" + + +Environment: TypeAlias = Annotated[ + Union[ResponseLocalEnvironment, ResponseContainerReference, None], PropertyInfo(discriminator="type") +] + + +class ResponseFunctionShellToolCall(BaseModel): + """A tool call that executes one or more shell commands in a managed environment.""" + + id: str + """The unique ID of the shell tool call. + + Populated when this item is returned via API. + """ + + action: Action + """The shell commands and limits that describe how to run the tool call.""" + + call_id: str + """The unique ID of the shell tool call generated by the model.""" + + environment: Optional[Environment] = None + """Represents the use of a local environment to perform shell actions.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the shell call. + + One of `in_progress`, `completed`, or `incomplete`. + """ + + type: Literal["shell_call"] + """The type of the item. Always `shell_call`.""" + + created_by: Optional[str] = None + """The ID of the entity that created this tool call.""" diff --git a/src/openai/types/responses/response_function_shell_tool_call_output.py b/src/openai/types/responses/response_function_shell_tool_call_output.py new file mode 100644 index 0000000000..7196ab47f7 --- /dev/null +++ b/src/openai/types/responses/response_function_shell_tool_call_output.py @@ -0,0 +1,88 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = [ + "ResponseFunctionShellToolCallOutput", + "Output", + "OutputOutcome", + "OutputOutcomeTimeout", + "OutputOutcomeExit", +] + + +class OutputOutcomeTimeout(BaseModel): + """Indicates that the shell call exceeded its configured time limit.""" + + type: Literal["timeout"] + """The outcome type. Always `timeout`.""" + + +class OutputOutcomeExit(BaseModel): + """Indicates that the shell commands finished and returned an exit code.""" + + exit_code: int + """Exit code from the shell process.""" + + type: Literal["exit"] + """The outcome type. Always `exit`.""" + + +OutputOutcome: TypeAlias = Annotated[Union[OutputOutcomeTimeout, OutputOutcomeExit], PropertyInfo(discriminator="type")] + + +class Output(BaseModel): + """The content of a shell tool call output that was emitted.""" + + outcome: OutputOutcome + """ + Represents either an exit outcome (with an exit code) or a timeout outcome for a + shell call output chunk. + """ + + stderr: str + """The standard error output that was captured.""" + + stdout: str + """The standard output that was captured.""" + + created_by: Optional[str] = None + """The identifier of the actor that created the item.""" + + +class ResponseFunctionShellToolCallOutput(BaseModel): + """The output of a shell tool call that was emitted.""" + + id: str + """The unique ID of the shell call output. + + Populated when this item is returned via API. + """ + + call_id: str + """The unique ID of the shell tool call generated by the model.""" + + max_output_length: Optional[int] = None + """The maximum length of the shell command output. + + This is generated by the model and should be passed back with the raw output. + """ + + output: List[Output] + """An array of shell call output contents""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the shell call output. + + One of `in_progress`, `completed`, or `incomplete`. + """ + + type: Literal["shell_call_output"] + """The type of the shell call output. Always `shell_call_output`.""" + + created_by: Optional[str] = None + """The identifier of the actor that created the item.""" diff --git a/src/openai/types/responses/response_function_tool_call.py b/src/openai/types/responses/response_function_tool_call.py new file mode 100644 index 0000000000..3ff4c67a3f --- /dev/null +++ b/src/openai/types/responses/response_function_tool_call.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFunctionToolCall"] + + +class ResponseFunctionToolCall(BaseModel): + """A tool call to run a function. + + See the + [function calling guide](https://platform.openai.com/docs/guides/function-calling) for more information. + """ + + arguments: str + """A JSON string of the arguments to pass to the function.""" + + call_id: str + """The unique ID of the function tool call generated by the model.""" + + name: str + """The name of the function to run.""" + + type: Literal["function_call"] + """The type of the function tool call. Always `function_call`.""" + + id: Optional[str] = None + """The unique ID of the function tool call.""" + + namespace: Optional[str] = None + """The namespace of the function to run.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ diff --git a/src/openai/types/responses/response_function_tool_call_item.py b/src/openai/types/responses/response_function_tool_call_item.py new file mode 100644 index 0000000000..c8a4488949 --- /dev/null +++ b/src/openai/types/responses/response_function_tool_call_item.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .response_function_tool_call import ResponseFunctionToolCall + +__all__ = ["ResponseFunctionToolCallItem"] + + +class ResponseFunctionToolCallItem(ResponseFunctionToolCall): + """A tool call to run a function. + + See the + [function calling guide](https://platform.openai.com/docs/guides/function-calling) for more information. + """ + + id: str # type: ignore + """The unique ID of the function tool call.""" + + status: Literal["in_progress", "completed", "incomplete"] # type: ignore + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + created_by: Optional[str] = None + """The identifier of the actor that created the item.""" diff --git a/src/openai/types/responses/response_function_tool_call_output_item.py b/src/openai/types/responses/response_function_tool_call_output_item.py new file mode 100644 index 0000000000..e40feeb37e --- /dev/null +++ b/src/openai/types/responses/response_function_tool_call_output_item.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .response_input_file import ResponseInputFile +from .response_input_text import ResponseInputText +from .response_input_image import ResponseInputImage + +__all__ = ["ResponseFunctionToolCallOutputItem", "OutputOutputContentList"] + +OutputOutputContentList: TypeAlias = Annotated[ + Union[ResponseInputText, ResponseInputImage, ResponseInputFile], PropertyInfo(discriminator="type") +] + + +class ResponseFunctionToolCallOutputItem(BaseModel): + id: str + """The unique ID of the function call tool output.""" + + call_id: str + """The unique ID of the function tool call generated by the model.""" + + output: Union[str, List[OutputOutputContentList]] + """ + The output from the function call generated by your code. Can be a string or an + list of output content. + """ + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + type: Literal["function_call_output"] + """The type of the function tool call output. Always `function_call_output`.""" + + created_by: Optional[str] = None + """The identifier of the actor that created the item.""" diff --git a/src/openai/types/responses/response_function_tool_call_param.py b/src/openai/types/responses/response_function_tool_call_param.py new file mode 100644 index 0000000000..5183e9e233 --- /dev/null +++ b/src/openai/types/responses/response_function_tool_call_param.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseFunctionToolCallParam"] + + +class ResponseFunctionToolCallParam(TypedDict, total=False): + """A tool call to run a function. + + See the + [function calling guide](https://platform.openai.com/docs/guides/function-calling) for more information. + """ + + arguments: Required[str] + """A JSON string of the arguments to pass to the function.""" + + call_id: Required[str] + """The unique ID of the function tool call generated by the model.""" + + name: Required[str] + """The name of the function to run.""" + + type: Required[Literal["function_call"]] + """The type of the function tool call. Always `function_call`.""" + + id: str + """The unique ID of the function tool call.""" + + namespace: str + """The namespace of the function to run.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ diff --git a/src/openai/types/responses/response_function_web_search.py b/src/openai/types/responses/response_function_web_search.py new file mode 100644 index 0000000000..3584992b7d --- /dev/null +++ b/src/openai/types/responses/response_function_web_search.py @@ -0,0 +1,91 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = [ + "ResponseFunctionWebSearch", + "Action", + "ActionSearch", + "ActionSearchSource", + "ActionOpenPage", + "ActionFind", +] + + +class ActionSearchSource(BaseModel): + """A source used in the search.""" + + type: Literal["url"] + """The type of source. Always `url`.""" + + url: str + """The URL of the source.""" + + +class ActionSearch(BaseModel): + """Action type "search" - Performs a web search query.""" + + type: Literal["search"] + """The action type.""" + + queries: Optional[List[str]] = None + """The search queries.""" + + query: Optional[str] = None + """The search query.""" + + sources: Optional[List[ActionSearchSource]] = None + """The sources used in the search.""" + + +class ActionOpenPage(BaseModel): + """Action type "open_page" - Opens a specific URL from search results.""" + + type: Literal["open_page"] + """The action type.""" + + url: Optional[str] = None + """The URL opened by the model.""" + + +class ActionFind(BaseModel): + """Action type "find_in_page": Searches for a pattern within a loaded page.""" + + pattern: str + """The pattern or text to search for within the page.""" + + type: Literal["find_in_page"] + """The action type.""" + + url: str + """The URL of the page searched for the pattern.""" + + +Action: TypeAlias = Annotated[Union[ActionSearch, ActionOpenPage, ActionFind], PropertyInfo(discriminator="type")] + + +class ResponseFunctionWebSearch(BaseModel): + """The results of a web search tool call. + + See the + [web search guide](https://platform.openai.com/docs/guides/tools-web-search) for more information. + """ + + id: str + """The unique ID of the web search tool call.""" + + action: Action + """ + An object describing the specific action taken in this web search call. Includes + details on how the model used the web (search, open_page, find_in_page). + """ + + status: Literal["in_progress", "searching", "completed", "failed"] + """The status of the web search tool call.""" + + type: Literal["web_search_call"] + """The type of the web search tool call. Always `web_search_call`.""" diff --git a/src/openai/types/responses/response_function_web_search_param.py b/src/openai/types/responses/response_function_web_search_param.py new file mode 100644 index 0000000000..9e31a46be1 --- /dev/null +++ b/src/openai/types/responses/response_function_web_search_param.py @@ -0,0 +1,92 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr + +__all__ = [ + "ResponseFunctionWebSearchParam", + "Action", + "ActionSearch", + "ActionSearchSource", + "ActionOpenPage", + "ActionFind", +] + + +class ActionSearchSource(TypedDict, total=False): + """A source used in the search.""" + + type: Required[Literal["url"]] + """The type of source. Always `url`.""" + + url: Required[str] + """The URL of the source.""" + + +class ActionSearch(TypedDict, total=False): + """Action type "search" - Performs a web search query.""" + + type: Required[Literal["search"]] + """The action type.""" + + queries: SequenceNotStr[str] + """The search queries.""" + + query: str + """The search query.""" + + sources: Iterable[ActionSearchSource] + """The sources used in the search.""" + + +class ActionOpenPage(TypedDict, total=False): + """Action type "open_page" - Opens a specific URL from search results.""" + + type: Required[Literal["open_page"]] + """The action type.""" + + url: Optional[str] + """The URL opened by the model.""" + + +class ActionFind(TypedDict, total=False): + """Action type "find_in_page": Searches for a pattern within a loaded page.""" + + pattern: Required[str] + """The pattern or text to search for within the page.""" + + type: Required[Literal["find_in_page"]] + """The action type.""" + + url: Required[str] + """The URL of the page searched for the pattern.""" + + +Action: TypeAlias = Union[ActionSearch, ActionOpenPage, ActionFind] + + +class ResponseFunctionWebSearchParam(TypedDict, total=False): + """The results of a web search tool call. + + See the + [web search guide](https://platform.openai.com/docs/guides/tools-web-search) for more information. + """ + + id: Required[str] + """The unique ID of the web search tool call.""" + + action: Required[Action] + """ + An object describing the specific action taken in this web search call. Includes + details on how the model used the web (search, open_page, find_in_page). + """ + + status: Required[Literal["in_progress", "searching", "completed", "failed"]] + """The status of the web search tool call.""" + + type: Required[Literal["web_search_call"]] + """The type of the web search tool call. Always `web_search_call`.""" diff --git a/src/openai/types/responses/response_image_gen_call_completed_event.py b/src/openai/types/responses/response_image_gen_call_completed_event.py new file mode 100644 index 0000000000..f6ce9d0fd8 --- /dev/null +++ b/src/openai/types/responses/response_image_gen_call_completed_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseImageGenCallCompletedEvent"] + + +class ResponseImageGenCallCompletedEvent(BaseModel): + """ + Emitted when an image generation tool call has completed and the final image is available. + """ + + item_id: str + """The unique identifier of the image generation item being processed.""" + + output_index: int + """The index of the output item in the response's output array.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.image_generation_call.completed"] + """The type of the event. Always 'response.image_generation_call.completed'.""" diff --git a/src/openai/types/responses/response_image_gen_call_generating_event.py b/src/openai/types/responses/response_image_gen_call_generating_event.py new file mode 100644 index 0000000000..8e3026d0dc --- /dev/null +++ b/src/openai/types/responses/response_image_gen_call_generating_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseImageGenCallGeneratingEvent"] + + +class ResponseImageGenCallGeneratingEvent(BaseModel): + """ + Emitted when an image generation tool call is actively generating an image (intermediate state). + """ + + item_id: str + """The unique identifier of the image generation item being processed.""" + + output_index: int + """The index of the output item in the response's output array.""" + + sequence_number: int + """The sequence number of the image generation item being processed.""" + + type: Literal["response.image_generation_call.generating"] + """The type of the event. Always 'response.image_generation_call.generating'.""" diff --git a/src/openai/types/responses/response_image_gen_call_in_progress_event.py b/src/openai/types/responses/response_image_gen_call_in_progress_event.py new file mode 100644 index 0000000000..60726a22b4 --- /dev/null +++ b/src/openai/types/responses/response_image_gen_call_in_progress_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseImageGenCallInProgressEvent"] + + +class ResponseImageGenCallInProgressEvent(BaseModel): + """Emitted when an image generation tool call is in progress.""" + + item_id: str + """The unique identifier of the image generation item being processed.""" + + output_index: int + """The index of the output item in the response's output array.""" + + sequence_number: int + """The sequence number of the image generation item being processed.""" + + type: Literal["response.image_generation_call.in_progress"] + """The type of the event. Always 'response.image_generation_call.in_progress'.""" diff --git a/src/openai/types/responses/response_image_gen_call_partial_image_event.py b/src/openai/types/responses/response_image_gen_call_partial_image_event.py new file mode 100644 index 0000000000..289d5d44c0 --- /dev/null +++ b/src/openai/types/responses/response_image_gen_call_partial_image_event.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseImageGenCallPartialImageEvent"] + + +class ResponseImageGenCallPartialImageEvent(BaseModel): + """Emitted when a partial image is available during image generation streaming.""" + + item_id: str + """The unique identifier of the image generation item being processed.""" + + output_index: int + """The index of the output item in the response's output array.""" + + partial_image_b64: str + """Base64-encoded partial image data, suitable for rendering as an image.""" + + partial_image_index: int + """ + 0-based index for the partial image (backend is 1-based, but this is 0-based for + the user). + """ + + sequence_number: int + """The sequence number of the image generation item being processed.""" + + type: Literal["response.image_generation_call.partial_image"] + """The type of the event. Always 'response.image_generation_call.partial_image'.""" diff --git a/src/openai/types/responses/response_in_progress_event.py b/src/openai/types/responses/response_in_progress_event.py new file mode 100644 index 0000000000..9d9bbd94b0 --- /dev/null +++ b/src/openai/types/responses/response_in_progress_event.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .response import Response +from ..._models import BaseModel + +__all__ = ["ResponseInProgressEvent"] + + +class ResponseInProgressEvent(BaseModel): + """Emitted when the response is in progress.""" + + response: Response + """The response that is in progress.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.in_progress"] + """The type of the event. Always `response.in_progress`.""" diff --git a/src/openai/types/responses/response_includable.py b/src/openai/types/responses/response_includable.py new file mode 100644 index 0000000000..675c83405a --- /dev/null +++ b/src/openai/types/responses/response_includable.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ResponseIncludable"] + +ResponseIncludable: TypeAlias = Literal[ + "file_search_call.results", + "web_search_call.results", + "web_search_call.action.sources", + "message.input_image.image_url", + "computer_call_output.output.image_url", + "code_interpreter_call.outputs", + "reasoning.encrypted_content", + "message.output_text.logprobs", +] diff --git a/src/openai/types/responses/response_incomplete_event.py b/src/openai/types/responses/response_incomplete_event.py new file mode 100644 index 0000000000..ef99c5f0b2 --- /dev/null +++ b/src/openai/types/responses/response_incomplete_event.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .response import Response +from ..._models import BaseModel + +__all__ = ["ResponseIncompleteEvent"] + + +class ResponseIncompleteEvent(BaseModel): + """An event that is emitted when a response finishes as incomplete.""" + + response: Response + """The response that was incomplete.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.incomplete"] + """The type of the event. Always `response.incomplete`.""" diff --git a/src/openai/types/responses/response_input.py b/src/openai/types/responses/response_input.py new file mode 100644 index 0000000000..e2180dec05 --- /dev/null +++ b/src/openai/types/responses/response_input.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .response_input_item import ResponseInputItem + +__all__ = ["ResponseInput"] + +ResponseInput: TypeAlias = List[ResponseInputItem] diff --git a/src/openai/types/responses/response_input_audio.py b/src/openai/types/responses/response_input_audio.py new file mode 100644 index 0000000000..f362ba4133 --- /dev/null +++ b/src/openai/types/responses/response_input_audio.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseInputAudio", "InputAudio"] + + +class InputAudio(BaseModel): + data: str + """Base64-encoded audio data.""" + + format: Literal["mp3", "wav"] + """The format of the audio data. Currently supported formats are `mp3` and `wav`.""" + + +class ResponseInputAudio(BaseModel): + """An audio input to the model.""" + + input_audio: InputAudio + + type: Literal["input_audio"] + """The type of the input item. Always `input_audio`.""" diff --git a/src/openai/types/responses/response_input_audio_param.py b/src/openai/types/responses/response_input_audio_param.py new file mode 100644 index 0000000000..0be935c54d --- /dev/null +++ b/src/openai/types/responses/response_input_audio_param.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseInputAudioParam", "InputAudio"] + + +class InputAudio(TypedDict, total=False): + data: Required[str] + """Base64-encoded audio data.""" + + format: Required[Literal["mp3", "wav"]] + """The format of the audio data. Currently supported formats are `mp3` and `wav`.""" + + +class ResponseInputAudioParam(TypedDict, total=False): + """An audio input to the model.""" + + input_audio: Required[InputAudio] + + type: Required[Literal["input_audio"]] + """The type of the input item. Always `input_audio`.""" diff --git a/src/openai/types/responses/response_input_content.py b/src/openai/types/responses/response_input_content.py new file mode 100644 index 0000000000..1726909a17 --- /dev/null +++ b/src/openai/types/responses/response_input_content.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from .response_input_file import ResponseInputFile +from .response_input_text import ResponseInputText +from .response_input_image import ResponseInputImage + +__all__ = ["ResponseInputContent"] + +ResponseInputContent: TypeAlias = Annotated[ + Union[ResponseInputText, ResponseInputImage, ResponseInputFile], PropertyInfo(discriminator="type") +] diff --git a/src/openai/types/responses/response_input_content_param.py b/src/openai/types/responses/response_input_content_param.py new file mode 100644 index 0000000000..7791cdfd8e --- /dev/null +++ b/src/openai/types/responses/response_input_content_param.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypeAlias + +from .response_input_file_param import ResponseInputFileParam +from .response_input_text_param import ResponseInputTextParam +from .response_input_image_param import ResponseInputImageParam + +__all__ = ["ResponseInputContentParam"] + +ResponseInputContentParam: TypeAlias = Union[ResponseInputTextParam, ResponseInputImageParam, ResponseInputFileParam] diff --git a/src/openai/types/responses/response_input_file.py b/src/openai/types/responses/response_input_file.py new file mode 100644 index 0000000000..f07ff7c049 --- /dev/null +++ b/src/openai/types/responses/response_input_file.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseInputFile"] + + +class ResponseInputFile(BaseModel): + """A file input to the model.""" + + type: Literal["input_file"] + """The type of the input item. Always `input_file`.""" + + detail: Optional[Literal["low", "high"]] = None + """The detail level of the file to be sent to the model. + + Use `low` for the default rendering behavior, or `high` to render the file at + higher quality. Defaults to `low`. + """ + + file_data: Optional[str] = None + """The content of the file to be sent to the model.""" + + file_id: Optional[str] = None + """The ID of the file to be sent to the model.""" + + file_url: Optional[str] = None + """The URL of the file to be sent to the model.""" + + filename: Optional[str] = None + """The name of the file to be sent to the model.""" diff --git a/src/openai/types/responses/response_input_file_content.py b/src/openai/types/responses/response_input_file_content.py new file mode 100644 index 0000000000..a0c2de4823 --- /dev/null +++ b/src/openai/types/responses/response_input_file_content.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseInputFileContent"] + + +class ResponseInputFileContent(BaseModel): + """A file input to the model.""" + + type: Literal["input_file"] + """The type of the input item. Always `input_file`.""" + + detail: Optional[Literal["low", "high"]] = None + """The detail level of the file to be sent to the model. + + Use `low` for the default rendering behavior, or `high` to render the file at + higher quality. Defaults to `low`. + """ + + file_data: Optional[str] = None + """The base64-encoded data of the file to be sent to the model.""" + + file_id: Optional[str] = None + """The ID of the file to be sent to the model.""" + + file_url: Optional[str] = None + """The URL of the file to be sent to the model.""" + + filename: Optional[str] = None + """The name of the file to be sent to the model.""" diff --git a/src/openai/types/responses/response_input_file_content_param.py b/src/openai/types/responses/response_input_file_content_param.py new file mode 100644 index 0000000000..4206ea09f3 --- /dev/null +++ b/src/openai/types/responses/response_input_file_content_param.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseInputFileContentParam"] + + +class ResponseInputFileContentParam(TypedDict, total=False): + """A file input to the model.""" + + type: Required[Literal["input_file"]] + """The type of the input item. Always `input_file`.""" + + detail: Literal["low", "high"] + """The detail level of the file to be sent to the model. + + Use `low` for the default rendering behavior, or `high` to render the file at + higher quality. Defaults to `low`. + """ + + file_data: Optional[str] + """The base64-encoded data of the file to be sent to the model.""" + + file_id: Optional[str] + """The ID of the file to be sent to the model.""" + + file_url: Optional[str] + """The URL of the file to be sent to the model.""" + + filename: Optional[str] + """The name of the file to be sent to the model.""" diff --git a/src/openai/types/responses/response_input_file_param.py b/src/openai/types/responses/response_input_file_param.py new file mode 100644 index 0000000000..a6bdb6e18c --- /dev/null +++ b/src/openai/types/responses/response_input_file_param.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseInputFileParam"] + + +class ResponseInputFileParam(TypedDict, total=False): + """A file input to the model.""" + + type: Required[Literal["input_file"]] + """The type of the input item. Always `input_file`.""" + + detail: Literal["low", "high"] + """The detail level of the file to be sent to the model. + + Use `low` for the default rendering behavior, or `high` to render the file at + higher quality. Defaults to `low`. + """ + + file_data: str + """The content of the file to be sent to the model.""" + + file_id: Optional[str] + """The ID of the file to be sent to the model.""" + + file_url: str + """The URL of the file to be sent to the model.""" + + filename: str + """The name of the file to be sent to the model.""" diff --git a/src/openai/types/responses/response_input_image.py b/src/openai/types/responses/response_input_image.py new file mode 100644 index 0000000000..63c4b42cbd --- /dev/null +++ b/src/openai/types/responses/response_input_image.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseInputImage"] + + +class ResponseInputImage(BaseModel): + """An image input to the model. + + Learn about [image inputs](https://platform.openai.com/docs/guides/vision). + """ + + detail: Literal["low", "high", "auto", "original"] + """The detail level of the image to be sent to the model. + + One of `high`, `low`, `auto`, or `original`. Defaults to `auto`. + """ + + type: Literal["input_image"] + """The type of the input item. Always `input_image`.""" + + file_id: Optional[str] = None + """The ID of the file to be sent to the model.""" + + image_url: Optional[str] = None + """The URL of the image to be sent to the model. + + A fully qualified URL or base64 encoded image in a data URL. + """ diff --git a/src/openai/types/responses/response_input_image_content.py b/src/openai/types/responses/response_input_image_content.py new file mode 100644 index 0000000000..d9619f65d8 --- /dev/null +++ b/src/openai/types/responses/response_input_image_content.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseInputImageContent"] + + +class ResponseInputImageContent(BaseModel): + """An image input to the model. + + Learn about [image inputs](https://platform.openai.com/docs/guides/vision) + """ + + type: Literal["input_image"] + """The type of the input item. Always `input_image`.""" + + detail: Optional[Literal["low", "high", "auto", "original"]] = None + """The detail level of the image to be sent to the model. + + One of `high`, `low`, `auto`, or `original`. Defaults to `auto`. + """ + + file_id: Optional[str] = None + """The ID of the file to be sent to the model.""" + + image_url: Optional[str] = None + """The URL of the image to be sent to the model. + + A fully qualified URL or base64 encoded image in a data URL. + """ diff --git a/src/openai/types/responses/response_input_image_content_param.py b/src/openai/types/responses/response_input_image_content_param.py new file mode 100644 index 0000000000..743642d4cf --- /dev/null +++ b/src/openai/types/responses/response_input_image_content_param.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseInputImageContentParam"] + + +class ResponseInputImageContentParam(TypedDict, total=False): + """An image input to the model. + + Learn about [image inputs](https://platform.openai.com/docs/guides/vision) + """ + + type: Required[Literal["input_image"]] + """The type of the input item. Always `input_image`.""" + + detail: Optional[Literal["low", "high", "auto", "original"]] + """The detail level of the image to be sent to the model. + + One of `high`, `low`, `auto`, or `original`. Defaults to `auto`. + """ + + file_id: Optional[str] + """The ID of the file to be sent to the model.""" + + image_url: Optional[str] + """The URL of the image to be sent to the model. + + A fully qualified URL or base64 encoded image in a data URL. + """ diff --git a/src/openai/types/responses/response_input_image_param.py b/src/openai/types/responses/response_input_image_param.py new file mode 100644 index 0000000000..118f0b5f72 --- /dev/null +++ b/src/openai/types/responses/response_input_image_param.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseInputImageParam"] + + +class ResponseInputImageParam(TypedDict, total=False): + """An image input to the model. + + Learn about [image inputs](https://platform.openai.com/docs/guides/vision). + """ + + detail: Required[Literal["low", "high", "auto", "original"]] + """The detail level of the image to be sent to the model. + + One of `high`, `low`, `auto`, or `original`. Defaults to `auto`. + """ + + type: Required[Literal["input_image"]] + """The type of the input item. Always `input_image`.""" + + file_id: Optional[str] + """The ID of the file to be sent to the model.""" + + image_url: Optional[str] + """The URL of the image to be sent to the model. + + A fully qualified URL or base64 encoded image in a data URL. + """ diff --git a/src/openai/types/responses/response_input_item.py b/src/openai/types/responses/response_input_item.py new file mode 100644 index 0000000000..f7f67c3414 --- /dev/null +++ b/src/openai/types/responses/response_input_item.py @@ -0,0 +1,598 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from .tool import Tool +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .local_environment import LocalEnvironment +from .easy_input_message import EasyInputMessage +from .container_reference import ContainerReference +from .response_output_message import ResponseOutputMessage +from .response_reasoning_item import ResponseReasoningItem +from .response_custom_tool_call import ResponseCustomToolCall +from .response_computer_tool_call import ResponseComputerToolCall +from .response_function_tool_call import ResponseFunctionToolCall +from .response_function_web_search import ResponseFunctionWebSearch +from .response_compaction_item_param import ResponseCompactionItemParam +from .response_file_search_tool_call import ResponseFileSearchToolCall +from .response_custom_tool_call_output import ResponseCustomToolCallOutput +from .response_code_interpreter_tool_call import ResponseCodeInterpreterToolCall +from .response_input_message_content_list import ResponseInputMessageContentList +from .response_tool_search_output_item_param import ResponseToolSearchOutputItemParam +from .response_function_call_output_item_list import ResponseFunctionCallOutputItemList +from .response_function_shell_call_output_content import ResponseFunctionShellCallOutputContent +from .response_computer_tool_call_output_screenshot import ResponseComputerToolCallOutputScreenshot + +__all__ = [ + "ResponseInputItem", + "Message", + "ComputerCallOutput", + "ComputerCallOutputAcknowledgedSafetyCheck", + "FunctionCallOutput", + "ToolSearchCall", + "AdditionalTools", + "ImageGenerationCall", + "LocalShellCall", + "LocalShellCallAction", + "LocalShellCallOutput", + "ShellCall", + "ShellCallAction", + "ShellCallEnvironment", + "ShellCallOutput", + "ApplyPatchCall", + "ApplyPatchCallOperation", + "ApplyPatchCallOperationCreateFile", + "ApplyPatchCallOperationDeleteFile", + "ApplyPatchCallOperationUpdateFile", + "ApplyPatchCallOutput", + "McpListTools", + "McpListToolsTool", + "McpApprovalRequest", + "McpApprovalResponse", + "McpCall", + "CompactionTrigger", + "ItemReference", +] + + +class Message(BaseModel): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. + """ + + content: ResponseInputMessageContentList + """ + A list of one or many input items to the model, containing different content + types. + """ + + role: Literal["user", "system", "developer"] + """The role of the message input. One of `user`, `system`, or `developer`.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + type: Optional[Literal["message"]] = None + """The type of the message input. Always set to `message`.""" + + +class ComputerCallOutputAcknowledgedSafetyCheck(BaseModel): + """A pending safety check for the computer call.""" + + id: str + """The ID of the pending safety check.""" + + code: Optional[str] = None + """The type of the pending safety check.""" + + message: Optional[str] = None + """Details about the pending safety check.""" + + +class ComputerCallOutput(BaseModel): + """The output of a computer tool call.""" + + call_id: str + """The ID of the computer tool call that produced the output.""" + + output: ResponseComputerToolCallOutputScreenshot + """A computer screenshot image used with the computer use tool.""" + + type: Literal["computer_call_output"] + """The type of the computer tool call output. Always `computer_call_output`.""" + + id: Optional[str] = None + """The ID of the computer tool call output.""" + + acknowledged_safety_checks: Optional[List[ComputerCallOutputAcknowledgedSafetyCheck]] = None + """ + The safety checks reported by the API that have been acknowledged by the + developer. + """ + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the message input. + + One of `in_progress`, `completed`, or `incomplete`. Populated when input items + are returned via API. + """ + + +class FunctionCallOutput(BaseModel): + """The output of a function tool call.""" + + call_id: str + """The unique ID of the function tool call generated by the model.""" + + output: Union[str, ResponseFunctionCallOutputItemList] + """Text, image, or file output of the function tool call.""" + + type: Literal["function_call_output"] + """The type of the function tool call output. Always `function_call_output`.""" + + id: Optional[str] = None + """The unique ID of the function tool call output. + + Populated when this item is returned via API. + """ + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + +class ToolSearchCall(BaseModel): + arguments: object + """The arguments supplied to the tool search call.""" + + type: Literal["tool_search_call"] + """The item type. Always `tool_search_call`.""" + + id: Optional[str] = None + """The unique ID of this tool search call.""" + + call_id: Optional[str] = None + """The unique ID of the tool search call generated by the model.""" + + execution: Optional[Literal["server", "client"]] = None + """Whether tool search was executed by the server or by the client.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the tool search call.""" + + +class AdditionalTools(BaseModel): + role: Literal["developer"] + """The role that provided the additional tools. Only `developer` is supported.""" + + tools: List[Tool] + """A list of additional tools made available at this item.""" + + type: Literal["additional_tools"] + """The item type. Always `additional_tools`.""" + + id: Optional[str] = None + """The unique ID of this additional tools item.""" + + +class ImageGenerationCall(BaseModel): + """An image generation request made by the model.""" + + id: str + """The unique ID of the image generation call.""" + + result: Optional[str] = None + """The generated image encoded in base64.""" + + status: Literal["in_progress", "completed", "generating", "failed"] + """The status of the image generation call.""" + + type: Literal["image_generation_call"] + """The type of the image generation call. Always `image_generation_call`.""" + + +class LocalShellCallAction(BaseModel): + """Execute a shell command on the server.""" + + command: List[str] + """The command to run.""" + + env: Dict[str, str] + """Environment variables to set for the command.""" + + type: Literal["exec"] + """The type of the local shell action. Always `exec`.""" + + timeout_ms: Optional[int] = None + """Optional timeout in milliseconds for the command.""" + + user: Optional[str] = None + """Optional user to run the command as.""" + + working_directory: Optional[str] = None + """Optional working directory to run the command in.""" + + +class LocalShellCall(BaseModel): + """A tool call to run a command on the local shell.""" + + id: str + """The unique ID of the local shell call.""" + + action: LocalShellCallAction + """Execute a shell command on the server.""" + + call_id: str + """The unique ID of the local shell tool call generated by the model.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the local shell call.""" + + type: Literal["local_shell_call"] + """The type of the local shell call. Always `local_shell_call`.""" + + +class LocalShellCallOutput(BaseModel): + """The output of a local shell tool call.""" + + id: str + """The unique ID of the local shell tool call generated by the model.""" + + output: str + """A JSON string of the output of the local shell tool call.""" + + type: Literal["local_shell_call_output"] + """The type of the local shell tool call output. Always `local_shell_call_output`.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the item. One of `in_progress`, `completed`, or `incomplete`.""" + + +class ShellCallAction(BaseModel): + """The shell commands and limits that describe how to run the tool call.""" + + commands: List[str] + """Ordered shell commands for the execution environment to run.""" + + max_output_length: Optional[int] = None + """ + Maximum number of UTF-8 characters to capture from combined stdout and stderr + output. + """ + + timeout_ms: Optional[int] = None + """Maximum wall-clock time in milliseconds to allow the shell commands to run.""" + + +ShellCallEnvironment: TypeAlias = Annotated[ + Union[LocalEnvironment, ContainerReference, None], PropertyInfo(discriminator="type") +] + + +class ShellCall(BaseModel): + """A tool representing a request to execute one or more shell commands.""" + + action: ShellCallAction + """The shell commands and limits that describe how to run the tool call.""" + + call_id: str + """The unique ID of the shell tool call generated by the model.""" + + type: Literal["shell_call"] + """The type of the item. Always `shell_call`.""" + + id: Optional[str] = None + """The unique ID of the shell tool call. + + Populated when this item is returned via API. + """ + + environment: Optional[ShellCallEnvironment] = None + """The environment to execute the shell commands in.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the shell call. + + One of `in_progress`, `completed`, or `incomplete`. + """ + + +class ShellCallOutput(BaseModel): + """The streamed output items emitted by a shell tool call.""" + + call_id: str + """The unique ID of the shell tool call generated by the model.""" + + output: List[ResponseFunctionShellCallOutputContent] + """ + Captured chunks of stdout and stderr output, along with their associated + outcomes. + """ + + type: Literal["shell_call_output"] + """The type of the item. Always `shell_call_output`.""" + + id: Optional[str] = None + """The unique ID of the shell tool call output. + + Populated when this item is returned via API. + """ + + max_output_length: Optional[int] = None + """ + The maximum number of UTF-8 characters captured for this shell call's combined + output. + """ + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the shell call output.""" + + +class ApplyPatchCallOperationCreateFile(BaseModel): + """Instruction for creating a new file via the apply_patch tool.""" + + diff: str + """Unified diff content to apply when creating the file.""" + + path: str + """Path of the file to create relative to the workspace root.""" + + type: Literal["create_file"] + """The operation type. Always `create_file`.""" + + +class ApplyPatchCallOperationDeleteFile(BaseModel): + """Instruction for deleting an existing file via the apply_patch tool.""" + + path: str + """Path of the file to delete relative to the workspace root.""" + + type: Literal["delete_file"] + """The operation type. Always `delete_file`.""" + + +class ApplyPatchCallOperationUpdateFile(BaseModel): + """Instruction for updating an existing file via the apply_patch tool.""" + + diff: str + """Unified diff content to apply to the existing file.""" + + path: str + """Path of the file to update relative to the workspace root.""" + + type: Literal["update_file"] + """The operation type. Always `update_file`.""" + + +ApplyPatchCallOperation: TypeAlias = Annotated[ + Union[ApplyPatchCallOperationCreateFile, ApplyPatchCallOperationDeleteFile, ApplyPatchCallOperationUpdateFile], + PropertyInfo(discriminator="type"), +] + + +class ApplyPatchCall(BaseModel): + """ + A tool call representing a request to create, delete, or update files using diff patches. + """ + + call_id: str + """The unique ID of the apply patch tool call generated by the model.""" + + operation: ApplyPatchCallOperation + """ + The specific create, delete, or update instruction for the apply_patch tool + call. + """ + + status: Literal["in_progress", "completed"] + """The status of the apply patch tool call. One of `in_progress` or `completed`.""" + + type: Literal["apply_patch_call"] + """The type of the item. Always `apply_patch_call`.""" + + id: Optional[str] = None + """The unique ID of the apply patch tool call. + + Populated when this item is returned via API. + """ + + +class ApplyPatchCallOutput(BaseModel): + """The streamed output emitted by an apply patch tool call.""" + + call_id: str + """The unique ID of the apply patch tool call generated by the model.""" + + status: Literal["completed", "failed"] + """The status of the apply patch tool call output. One of `completed` or `failed`.""" + + type: Literal["apply_patch_call_output"] + """The type of the item. Always `apply_patch_call_output`.""" + + id: Optional[str] = None + """The unique ID of the apply patch tool call output. + + Populated when this item is returned via API. + """ + + output: Optional[str] = None + """ + Optional human-readable log text from the apply patch tool (e.g., patch results + or errors). + """ + + +class McpListToolsTool(BaseModel): + """A tool available on an MCP server.""" + + input_schema: object + """The JSON schema describing the tool's input.""" + + name: str + """The name of the tool.""" + + annotations: Optional[object] = None + """Additional annotations about the tool.""" + + description: Optional[str] = None + """The description of the tool.""" + + +class McpListTools(BaseModel): + """A list of tools available on an MCP server.""" + + id: str + """The unique ID of the list.""" + + server_label: str + """The label of the MCP server.""" + + tools: List[McpListToolsTool] + """The tools available on the server.""" + + type: Literal["mcp_list_tools"] + """The type of the item. Always `mcp_list_tools`.""" + + error: Optional[str] = None + """Error message if the server could not list tools.""" + + +class McpApprovalRequest(BaseModel): + """A request for human approval of a tool invocation.""" + + id: str + """The unique ID of the approval request.""" + + arguments: str + """A JSON string of arguments for the tool.""" + + name: str + """The name of the tool to run.""" + + server_label: str + """The label of the MCP server making the request.""" + + type: Literal["mcp_approval_request"] + """The type of the item. Always `mcp_approval_request`.""" + + +class McpApprovalResponse(BaseModel): + """A response to an MCP approval request.""" + + approval_request_id: str + """The ID of the approval request being answered.""" + + approve: bool + """Whether the request was approved.""" + + type: Literal["mcp_approval_response"] + """The type of the item. Always `mcp_approval_response`.""" + + id: Optional[str] = None + """The unique ID of the approval response""" + + reason: Optional[str] = None + """Optional reason for the decision.""" + + +class McpCall(BaseModel): + """An invocation of a tool on an MCP server.""" + + id: str + """The unique ID of the tool call.""" + + arguments: str + """A JSON string of the arguments passed to the tool.""" + + name: str + """The name of the tool that was run.""" + + server_label: str + """The label of the MCP server running the tool.""" + + type: Literal["mcp_call"] + """The type of the item. Always `mcp_call`.""" + + approval_request_id: Optional[str] = None + """ + Unique identifier for the MCP tool call approval request. Include this value in + a subsequent `mcp_approval_response` input to approve or reject the + corresponding tool call. + """ + + error: Optional[str] = None + """The error from the tool call, if any.""" + + output: Optional[str] = None + """The output from the tool call.""" + + status: Optional[Literal["in_progress", "completed", "incomplete", "calling", "failed"]] = None + """The status of the tool call. + + One of `in_progress`, `completed`, `incomplete`, `calling`, or `failed`. + """ + + +class CompactionTrigger(BaseModel): + """Compacts the current context. Must be the final input item.""" + + type: Literal["compaction_trigger"] + """The type of the item. Always `compaction_trigger`.""" + + +class ItemReference(BaseModel): + """An internal identifier for an item to reference.""" + + id: str + """The ID of the item to reference.""" + + type: Optional[Literal["item_reference"]] = None + """The type of item to reference. Always `item_reference`.""" + + +ResponseInputItem: TypeAlias = Annotated[ + Union[ + EasyInputMessage, + Message, + ResponseOutputMessage, + ResponseFileSearchToolCall, + ResponseComputerToolCall, + ComputerCallOutput, + ResponseFunctionWebSearch, + ResponseFunctionToolCall, + FunctionCallOutput, + ToolSearchCall, + ResponseToolSearchOutputItemParam, + AdditionalTools, + ResponseReasoningItem, + ResponseCompactionItemParam, + ImageGenerationCall, + ResponseCodeInterpreterToolCall, + LocalShellCall, + LocalShellCallOutput, + ShellCall, + ShellCallOutput, + ApplyPatchCall, + ApplyPatchCallOutput, + McpListTools, + McpApprovalRequest, + McpApprovalResponse, + McpCall, + ResponseCustomToolCallOutput, + ResponseCustomToolCall, + CompactionTrigger, + ItemReference, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/responses/response_input_item_param.py b/src/openai/types/responses/response_input_item_param.py new file mode 100644 index 0000000000..0b0d68cdbd --- /dev/null +++ b/src/openai/types/responses/response_input_item_param.py @@ -0,0 +1,593 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from .tool_param import ToolParam +from .local_environment_param import LocalEnvironmentParam +from .easy_input_message_param import EasyInputMessageParam +from .container_reference_param import ContainerReferenceParam +from .response_output_message_param import ResponseOutputMessageParam +from .response_reasoning_item_param import ResponseReasoningItemParam +from .response_custom_tool_call_param import ResponseCustomToolCallParam +from .response_computer_tool_call_param import ResponseComputerToolCallParam +from .response_function_tool_call_param import ResponseFunctionToolCallParam +from .response_function_web_search_param import ResponseFunctionWebSearchParam +from .response_compaction_item_param_param import ResponseCompactionItemParamParam +from .response_file_search_tool_call_param import ResponseFileSearchToolCallParam +from .response_custom_tool_call_output_param import ResponseCustomToolCallOutputParam +from .response_code_interpreter_tool_call_param import ResponseCodeInterpreterToolCallParam +from .response_input_message_content_list_param import ResponseInputMessageContentListParam +from .response_tool_search_output_item_param_param import ResponseToolSearchOutputItemParamParam +from .response_function_call_output_item_list_param import ResponseFunctionCallOutputItemListParam +from .response_function_shell_call_output_content_param import ResponseFunctionShellCallOutputContentParam +from .response_computer_tool_call_output_screenshot_param import ResponseComputerToolCallOutputScreenshotParam + +__all__ = [ + "ResponseInputItemParam", + "Message", + "ComputerCallOutput", + "ComputerCallOutputAcknowledgedSafetyCheck", + "FunctionCallOutput", + "ToolSearchCall", + "AdditionalTools", + "ImageGenerationCall", + "LocalShellCall", + "LocalShellCallAction", + "LocalShellCallOutput", + "ShellCall", + "ShellCallAction", + "ShellCallEnvironment", + "ShellCallOutput", + "ApplyPatchCall", + "ApplyPatchCallOperation", + "ApplyPatchCallOperationCreateFile", + "ApplyPatchCallOperationDeleteFile", + "ApplyPatchCallOperationUpdateFile", + "ApplyPatchCallOutput", + "McpListTools", + "McpListToolsTool", + "McpApprovalRequest", + "McpApprovalResponse", + "McpCall", + "CompactionTrigger", + "ItemReference", +] + + +class Message(TypedDict, total=False): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. + """ + + content: Required[ResponseInputMessageContentListParam] + """ + A list of one or many input items to the model, containing different content + types. + """ + + role: Required[Literal["user", "system", "developer"]] + """The role of the message input. One of `user`, `system`, or `developer`.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + type: Literal["message"] + """The type of the message input. Always set to `message`.""" + + +class ComputerCallOutputAcknowledgedSafetyCheck(TypedDict, total=False): + """A pending safety check for the computer call.""" + + id: Required[str] + """The ID of the pending safety check.""" + + code: Optional[str] + """The type of the pending safety check.""" + + message: Optional[str] + """Details about the pending safety check.""" + + +class ComputerCallOutput(TypedDict, total=False): + """The output of a computer tool call.""" + + call_id: Required[str] + """The ID of the computer tool call that produced the output.""" + + output: Required[ResponseComputerToolCallOutputScreenshotParam] + """A computer screenshot image used with the computer use tool.""" + + type: Required[Literal["computer_call_output"]] + """The type of the computer tool call output. Always `computer_call_output`.""" + + id: Optional[str] + """The ID of the computer tool call output.""" + + acknowledged_safety_checks: Optional[Iterable[ComputerCallOutputAcknowledgedSafetyCheck]] + """ + The safety checks reported by the API that have been acknowledged by the + developer. + """ + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the message input. + + One of `in_progress`, `completed`, or `incomplete`. Populated when input items + are returned via API. + """ + + +class FunctionCallOutput(TypedDict, total=False): + """The output of a function tool call.""" + + call_id: Required[str] + """The unique ID of the function tool call generated by the model.""" + + output: Required[Union[str, ResponseFunctionCallOutputItemListParam]] + """Text, image, or file output of the function tool call.""" + + type: Required[Literal["function_call_output"]] + """The type of the function tool call output. Always `function_call_output`.""" + + id: Optional[str] + """The unique ID of the function tool call output. + + Populated when this item is returned via API. + """ + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + +class ToolSearchCall(TypedDict, total=False): + arguments: Required[object] + """The arguments supplied to the tool search call.""" + + type: Required[Literal["tool_search_call"]] + """The item type. Always `tool_search_call`.""" + + id: Optional[str] + """The unique ID of this tool search call.""" + + call_id: Optional[str] + """The unique ID of the tool search call generated by the model.""" + + execution: Literal["server", "client"] + """Whether tool search was executed by the server or by the client.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the tool search call.""" + + +class AdditionalTools(TypedDict, total=False): + role: Required[Literal["developer"]] + """The role that provided the additional tools. Only `developer` is supported.""" + + tools: Required[Iterable[ToolParam]] + """A list of additional tools made available at this item.""" + + type: Required[Literal["additional_tools"]] + """The item type. Always `additional_tools`.""" + + id: Optional[str] + """The unique ID of this additional tools item.""" + + +class ImageGenerationCall(TypedDict, total=False): + """An image generation request made by the model.""" + + id: Required[str] + """The unique ID of the image generation call.""" + + result: Required[Optional[str]] + """The generated image encoded in base64.""" + + status: Required[Literal["in_progress", "completed", "generating", "failed"]] + """The status of the image generation call.""" + + type: Required[Literal["image_generation_call"]] + """The type of the image generation call. Always `image_generation_call`.""" + + +class LocalShellCallAction(TypedDict, total=False): + """Execute a shell command on the server.""" + + command: Required[SequenceNotStr[str]] + """The command to run.""" + + env: Required[Dict[str, str]] + """Environment variables to set for the command.""" + + type: Required[Literal["exec"]] + """The type of the local shell action. Always `exec`.""" + + timeout_ms: Optional[int] + """Optional timeout in milliseconds for the command.""" + + user: Optional[str] + """Optional user to run the command as.""" + + working_directory: Optional[str] + """Optional working directory to run the command in.""" + + +class LocalShellCall(TypedDict, total=False): + """A tool call to run a command on the local shell.""" + + id: Required[str] + """The unique ID of the local shell call.""" + + action: Required[LocalShellCallAction] + """Execute a shell command on the server.""" + + call_id: Required[str] + """The unique ID of the local shell tool call generated by the model.""" + + status: Required[Literal["in_progress", "completed", "incomplete"]] + """The status of the local shell call.""" + + type: Required[Literal["local_shell_call"]] + """The type of the local shell call. Always `local_shell_call`.""" + + +class LocalShellCallOutput(TypedDict, total=False): + """The output of a local shell tool call.""" + + id: Required[str] + """The unique ID of the local shell tool call generated by the model.""" + + output: Required[str] + """A JSON string of the output of the local shell tool call.""" + + type: Required[Literal["local_shell_call_output"]] + """The type of the local shell tool call output. Always `local_shell_call_output`.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the item. One of `in_progress`, `completed`, or `incomplete`.""" + + +class ShellCallAction(TypedDict, total=False): + """The shell commands and limits that describe how to run the tool call.""" + + commands: Required[SequenceNotStr[str]] + """Ordered shell commands for the execution environment to run.""" + + max_output_length: Optional[int] + """ + Maximum number of UTF-8 characters to capture from combined stdout and stderr + output. + """ + + timeout_ms: Optional[int] + """Maximum wall-clock time in milliseconds to allow the shell commands to run.""" + + +ShellCallEnvironment: TypeAlias = Union[LocalEnvironmentParam, ContainerReferenceParam] + + +class ShellCall(TypedDict, total=False): + """A tool representing a request to execute one or more shell commands.""" + + action: Required[ShellCallAction] + """The shell commands and limits that describe how to run the tool call.""" + + call_id: Required[str] + """The unique ID of the shell tool call generated by the model.""" + + type: Required[Literal["shell_call"]] + """The type of the item. Always `shell_call`.""" + + id: Optional[str] + """The unique ID of the shell tool call. + + Populated when this item is returned via API. + """ + + environment: Optional[ShellCallEnvironment] + """The environment to execute the shell commands in.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the shell call. + + One of `in_progress`, `completed`, or `incomplete`. + """ + + +class ShellCallOutput(TypedDict, total=False): + """The streamed output items emitted by a shell tool call.""" + + call_id: Required[str] + """The unique ID of the shell tool call generated by the model.""" + + output: Required[Iterable[ResponseFunctionShellCallOutputContentParam]] + """ + Captured chunks of stdout and stderr output, along with their associated + outcomes. + """ + + type: Required[Literal["shell_call_output"]] + """The type of the item. Always `shell_call_output`.""" + + id: Optional[str] + """The unique ID of the shell tool call output. + + Populated when this item is returned via API. + """ + + max_output_length: Optional[int] + """ + The maximum number of UTF-8 characters captured for this shell call's combined + output. + """ + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the shell call output.""" + + +class ApplyPatchCallOperationCreateFile(TypedDict, total=False): + """Instruction for creating a new file via the apply_patch tool.""" + + diff: Required[str] + """Unified diff content to apply when creating the file.""" + + path: Required[str] + """Path of the file to create relative to the workspace root.""" + + type: Required[Literal["create_file"]] + """The operation type. Always `create_file`.""" + + +class ApplyPatchCallOperationDeleteFile(TypedDict, total=False): + """Instruction for deleting an existing file via the apply_patch tool.""" + + path: Required[str] + """Path of the file to delete relative to the workspace root.""" + + type: Required[Literal["delete_file"]] + """The operation type. Always `delete_file`.""" + + +class ApplyPatchCallOperationUpdateFile(TypedDict, total=False): + """Instruction for updating an existing file via the apply_patch tool.""" + + diff: Required[str] + """Unified diff content to apply to the existing file.""" + + path: Required[str] + """Path of the file to update relative to the workspace root.""" + + type: Required[Literal["update_file"]] + """The operation type. Always `update_file`.""" + + +ApplyPatchCallOperation: TypeAlias = Union[ + ApplyPatchCallOperationCreateFile, ApplyPatchCallOperationDeleteFile, ApplyPatchCallOperationUpdateFile +] + + +class ApplyPatchCall(TypedDict, total=False): + """ + A tool call representing a request to create, delete, or update files using diff patches. + """ + + call_id: Required[str] + """The unique ID of the apply patch tool call generated by the model.""" + + operation: Required[ApplyPatchCallOperation] + """ + The specific create, delete, or update instruction for the apply_patch tool + call. + """ + + status: Required[Literal["in_progress", "completed"]] + """The status of the apply patch tool call. One of `in_progress` or `completed`.""" + + type: Required[Literal["apply_patch_call"]] + """The type of the item. Always `apply_patch_call`.""" + + id: Optional[str] + """The unique ID of the apply patch tool call. + + Populated when this item is returned via API. + """ + + +class ApplyPatchCallOutput(TypedDict, total=False): + """The streamed output emitted by an apply patch tool call.""" + + call_id: Required[str] + """The unique ID of the apply patch tool call generated by the model.""" + + status: Required[Literal["completed", "failed"]] + """The status of the apply patch tool call output. One of `completed` or `failed`.""" + + type: Required[Literal["apply_patch_call_output"]] + """The type of the item. Always `apply_patch_call_output`.""" + + id: Optional[str] + """The unique ID of the apply patch tool call output. + + Populated when this item is returned via API. + """ + + output: Optional[str] + """ + Optional human-readable log text from the apply patch tool (e.g., patch results + or errors). + """ + + +class McpListToolsTool(TypedDict, total=False): + """A tool available on an MCP server.""" + + input_schema: Required[object] + """The JSON schema describing the tool's input.""" + + name: Required[str] + """The name of the tool.""" + + annotations: Optional[object] + """Additional annotations about the tool.""" + + description: Optional[str] + """The description of the tool.""" + + +class McpListTools(TypedDict, total=False): + """A list of tools available on an MCP server.""" + + id: Required[str] + """The unique ID of the list.""" + + server_label: Required[str] + """The label of the MCP server.""" + + tools: Required[Iterable[McpListToolsTool]] + """The tools available on the server.""" + + type: Required[Literal["mcp_list_tools"]] + """The type of the item. Always `mcp_list_tools`.""" + + error: Optional[str] + """Error message if the server could not list tools.""" + + +class McpApprovalRequest(TypedDict, total=False): + """A request for human approval of a tool invocation.""" + + id: Required[str] + """The unique ID of the approval request.""" + + arguments: Required[str] + """A JSON string of arguments for the tool.""" + + name: Required[str] + """The name of the tool to run.""" + + server_label: Required[str] + """The label of the MCP server making the request.""" + + type: Required[Literal["mcp_approval_request"]] + """The type of the item. Always `mcp_approval_request`.""" + + +class McpApprovalResponse(TypedDict, total=False): + """A response to an MCP approval request.""" + + approval_request_id: Required[str] + """The ID of the approval request being answered.""" + + approve: Required[bool] + """Whether the request was approved.""" + + type: Required[Literal["mcp_approval_response"]] + """The type of the item. Always `mcp_approval_response`.""" + + id: Optional[str] + """The unique ID of the approval response""" + + reason: Optional[str] + """Optional reason for the decision.""" + + +class McpCall(TypedDict, total=False): + """An invocation of a tool on an MCP server.""" + + id: Required[str] + """The unique ID of the tool call.""" + + arguments: Required[str] + """A JSON string of the arguments passed to the tool.""" + + name: Required[str] + """The name of the tool that was run.""" + + server_label: Required[str] + """The label of the MCP server running the tool.""" + + type: Required[Literal["mcp_call"]] + """The type of the item. Always `mcp_call`.""" + + approval_request_id: Optional[str] + """ + Unique identifier for the MCP tool call approval request. Include this value in + a subsequent `mcp_approval_response` input to approve or reject the + corresponding tool call. + """ + + error: Optional[str] + """The error from the tool call, if any.""" + + output: Optional[str] + """The output from the tool call.""" + + status: Literal["in_progress", "completed", "incomplete", "calling", "failed"] + """The status of the tool call. + + One of `in_progress`, `completed`, `incomplete`, `calling`, or `failed`. + """ + + +class CompactionTrigger(TypedDict, total=False): + """Compacts the current context. Must be the final input item.""" + + type: Required[Literal["compaction_trigger"]] + """The type of the item. Always `compaction_trigger`.""" + + +class ItemReference(TypedDict, total=False): + """An internal identifier for an item to reference.""" + + id: Required[str] + """The ID of the item to reference.""" + + type: Optional[Literal["item_reference"]] + """The type of item to reference. Always `item_reference`.""" + + +ResponseInputItemParam: TypeAlias = Union[ + EasyInputMessageParam, + Message, + ResponseOutputMessageParam, + ResponseFileSearchToolCallParam, + ResponseComputerToolCallParam, + ComputerCallOutput, + ResponseFunctionWebSearchParam, + ResponseFunctionToolCallParam, + FunctionCallOutput, + ToolSearchCall, + ResponseToolSearchOutputItemParamParam, + AdditionalTools, + ResponseReasoningItemParam, + ResponseCompactionItemParamParam, + ImageGenerationCall, + ResponseCodeInterpreterToolCallParam, + LocalShellCall, + LocalShellCallOutput, + ShellCall, + ShellCallOutput, + ApplyPatchCall, + ApplyPatchCallOutput, + McpListTools, + McpApprovalRequest, + McpApprovalResponse, + McpCall, + ResponseCustomToolCallOutputParam, + ResponseCustomToolCallParam, + CompactionTrigger, + ItemReference, +] diff --git a/src/openai/types/responses/response_input_message_content_list.py b/src/openai/types/responses/response_input_message_content_list.py new file mode 100644 index 0000000000..99b7c10f12 --- /dev/null +++ b/src/openai/types/responses/response_input_message_content_list.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .response_input_content import ResponseInputContent + +__all__ = ["ResponseInputMessageContentList"] + +ResponseInputMessageContentList: TypeAlias = List[ResponseInputContent] diff --git a/src/openai/types/responses/response_input_message_content_list_param.py b/src/openai/types/responses/response_input_message_content_list_param.py new file mode 100644 index 0000000000..080613df0d --- /dev/null +++ b/src/openai/types/responses/response_input_message_content_list_param.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union +from typing_extensions import TypeAlias + +from .response_input_file_param import ResponseInputFileParam +from .response_input_text_param import ResponseInputTextParam +from .response_input_image_param import ResponseInputImageParam + +__all__ = ["ResponseInputMessageContentListParam", "ResponseInputContentParam"] + +ResponseInputContentParam: TypeAlias = Union[ResponseInputTextParam, ResponseInputImageParam, ResponseInputFileParam] + +ResponseInputMessageContentListParam: TypeAlias = List[ResponseInputContentParam] diff --git a/src/openai/types/responses/response_input_message_item.py b/src/openai/types/responses/response_input_message_item.py new file mode 100644 index 0000000000..788c92c9bd --- /dev/null +++ b/src/openai/types/responses/response_input_message_item.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .response_input_message_content_list import ResponseInputMessageContentList + +__all__ = ["ResponseInputMessageItem"] + + +class ResponseInputMessageItem(BaseModel): + id: str + """The unique ID of the message input.""" + + content: ResponseInputMessageContentList + """ + A list of one or many input items to the model, containing different content + types. + """ + + role: Literal["user", "system", "developer"] + """The role of the message input. One of `user`, `system`, or `developer`.""" + + type: Literal["message"] + """The type of the message input. Always set to `message`.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ diff --git a/src/openai/types/responses/response_input_param.py b/src/openai/types/responses/response_input_param.py new file mode 100644 index 0000000000..f45aa276c8 --- /dev/null +++ b/src/openai/types/responses/response_input_param.py @@ -0,0 +1,596 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from .tool_param import ToolParam +from .local_environment_param import LocalEnvironmentParam +from .easy_input_message_param import EasyInputMessageParam +from .container_reference_param import ContainerReferenceParam +from .response_output_message_param import ResponseOutputMessageParam +from .response_reasoning_item_param import ResponseReasoningItemParam +from .response_custom_tool_call_param import ResponseCustomToolCallParam +from .response_computer_tool_call_param import ResponseComputerToolCallParam +from .response_function_tool_call_param import ResponseFunctionToolCallParam +from .response_function_web_search_param import ResponseFunctionWebSearchParam +from .response_compaction_item_param_param import ResponseCompactionItemParamParam +from .response_file_search_tool_call_param import ResponseFileSearchToolCallParam +from .response_custom_tool_call_output_param import ResponseCustomToolCallOutputParam +from .response_code_interpreter_tool_call_param import ResponseCodeInterpreterToolCallParam +from .response_input_message_content_list_param import ResponseInputMessageContentListParam +from .response_tool_search_output_item_param_param import ResponseToolSearchOutputItemParamParam +from .response_function_call_output_item_list_param import ResponseFunctionCallOutputItemListParam +from .response_function_shell_call_output_content_param import ResponseFunctionShellCallOutputContentParam +from .response_computer_tool_call_output_screenshot_param import ResponseComputerToolCallOutputScreenshotParam + +__all__ = [ + "ResponseInputParam", + "ResponseInputItemParam", + "Message", + "ComputerCallOutput", + "ComputerCallOutputAcknowledgedSafetyCheck", + "FunctionCallOutput", + "ToolSearchCall", + "AdditionalTools", + "ImageGenerationCall", + "LocalShellCall", + "LocalShellCallAction", + "LocalShellCallOutput", + "ShellCall", + "ShellCallAction", + "ShellCallEnvironment", + "ShellCallOutput", + "ApplyPatchCall", + "ApplyPatchCallOperation", + "ApplyPatchCallOperationCreateFile", + "ApplyPatchCallOperationDeleteFile", + "ApplyPatchCallOperationUpdateFile", + "ApplyPatchCallOutput", + "McpListTools", + "McpListToolsTool", + "McpApprovalRequest", + "McpApprovalResponse", + "McpCall", + "CompactionTrigger", + "ItemReference", +] + + +class Message(TypedDict, total=False): + """ + A message input to the model with a role indicating instruction following + hierarchy. Instructions given with the `developer` or `system` role take + precedence over instructions given with the `user` role. + """ + + content: Required[ResponseInputMessageContentListParam] + """ + A list of one or many input items to the model, containing different content + types. + """ + + role: Required[Literal["user", "system", "developer"]] + """The role of the message input. One of `user`, `system`, or `developer`.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + type: Literal["message"] + """The type of the message input. Always set to `message`.""" + + +class ComputerCallOutputAcknowledgedSafetyCheck(TypedDict, total=False): + """A pending safety check for the computer call.""" + + id: Required[str] + """The ID of the pending safety check.""" + + code: Optional[str] + """The type of the pending safety check.""" + + message: Optional[str] + """Details about the pending safety check.""" + + +class ComputerCallOutput(TypedDict, total=False): + """The output of a computer tool call.""" + + call_id: Required[str] + """The ID of the computer tool call that produced the output.""" + + output: Required[ResponseComputerToolCallOutputScreenshotParam] + """A computer screenshot image used with the computer use tool.""" + + type: Required[Literal["computer_call_output"]] + """The type of the computer tool call output. Always `computer_call_output`.""" + + id: Optional[str] + """The ID of the computer tool call output.""" + + acknowledged_safety_checks: Optional[Iterable[ComputerCallOutputAcknowledgedSafetyCheck]] + """ + The safety checks reported by the API that have been acknowledged by the + developer. + """ + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the message input. + + One of `in_progress`, `completed`, or `incomplete`. Populated when input items + are returned via API. + """ + + +class FunctionCallOutput(TypedDict, total=False): + """The output of a function tool call.""" + + call_id: Required[str] + """The unique ID of the function tool call generated by the model.""" + + output: Required[Union[str, ResponseFunctionCallOutputItemListParam]] + """Text, image, or file output of the function tool call.""" + + type: Required[Literal["function_call_output"]] + """The type of the function tool call output. Always `function_call_output`.""" + + id: Optional[str] + """The unique ID of the function tool call output. + + Populated when this item is returned via API. + """ + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ + + +class ToolSearchCall(TypedDict, total=False): + arguments: Required[object] + """The arguments supplied to the tool search call.""" + + type: Required[Literal["tool_search_call"]] + """The item type. Always `tool_search_call`.""" + + id: Optional[str] + """The unique ID of this tool search call.""" + + call_id: Optional[str] + """The unique ID of the tool search call generated by the model.""" + + execution: Literal["server", "client"] + """Whether tool search was executed by the server or by the client.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the tool search call.""" + + +class AdditionalTools(TypedDict, total=False): + role: Required[Literal["developer"]] + """The role that provided the additional tools. Only `developer` is supported.""" + + tools: Required[Iterable[ToolParam]] + """A list of additional tools made available at this item.""" + + type: Required[Literal["additional_tools"]] + """The item type. Always `additional_tools`.""" + + id: Optional[str] + """The unique ID of this additional tools item.""" + + +class ImageGenerationCall(TypedDict, total=False): + """An image generation request made by the model.""" + + id: Required[str] + """The unique ID of the image generation call.""" + + result: Required[Optional[str]] + """The generated image encoded in base64.""" + + status: Required[Literal["in_progress", "completed", "generating", "failed"]] + """The status of the image generation call.""" + + type: Required[Literal["image_generation_call"]] + """The type of the image generation call. Always `image_generation_call`.""" + + +class LocalShellCallAction(TypedDict, total=False): + """Execute a shell command on the server.""" + + command: Required[SequenceNotStr[str]] + """The command to run.""" + + env: Required[Dict[str, str]] + """Environment variables to set for the command.""" + + type: Required[Literal["exec"]] + """The type of the local shell action. Always `exec`.""" + + timeout_ms: Optional[int] + """Optional timeout in milliseconds for the command.""" + + user: Optional[str] + """Optional user to run the command as.""" + + working_directory: Optional[str] + """Optional working directory to run the command in.""" + + +class LocalShellCall(TypedDict, total=False): + """A tool call to run a command on the local shell.""" + + id: Required[str] + """The unique ID of the local shell call.""" + + action: Required[LocalShellCallAction] + """Execute a shell command on the server.""" + + call_id: Required[str] + """The unique ID of the local shell tool call generated by the model.""" + + status: Required[Literal["in_progress", "completed", "incomplete"]] + """The status of the local shell call.""" + + type: Required[Literal["local_shell_call"]] + """The type of the local shell call. Always `local_shell_call`.""" + + +class LocalShellCallOutput(TypedDict, total=False): + """The output of a local shell tool call.""" + + id: Required[str] + """The unique ID of the local shell tool call generated by the model.""" + + output: Required[str] + """A JSON string of the output of the local shell tool call.""" + + type: Required[Literal["local_shell_call_output"]] + """The type of the local shell tool call output. Always `local_shell_call_output`.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the item. One of `in_progress`, `completed`, or `incomplete`.""" + + +class ShellCallAction(TypedDict, total=False): + """The shell commands and limits that describe how to run the tool call.""" + + commands: Required[SequenceNotStr[str]] + """Ordered shell commands for the execution environment to run.""" + + max_output_length: Optional[int] + """ + Maximum number of UTF-8 characters to capture from combined stdout and stderr + output. + """ + + timeout_ms: Optional[int] + """Maximum wall-clock time in milliseconds to allow the shell commands to run.""" + + +ShellCallEnvironment: TypeAlias = Union[LocalEnvironmentParam, ContainerReferenceParam] + + +class ShellCall(TypedDict, total=False): + """A tool representing a request to execute one or more shell commands.""" + + action: Required[ShellCallAction] + """The shell commands and limits that describe how to run the tool call.""" + + call_id: Required[str] + """The unique ID of the shell tool call generated by the model.""" + + type: Required[Literal["shell_call"]] + """The type of the item. Always `shell_call`.""" + + id: Optional[str] + """The unique ID of the shell tool call. + + Populated when this item is returned via API. + """ + + environment: Optional[ShellCallEnvironment] + """The environment to execute the shell commands in.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the shell call. + + One of `in_progress`, `completed`, or `incomplete`. + """ + + +class ShellCallOutput(TypedDict, total=False): + """The streamed output items emitted by a shell tool call.""" + + call_id: Required[str] + """The unique ID of the shell tool call generated by the model.""" + + output: Required[Iterable[ResponseFunctionShellCallOutputContentParam]] + """ + Captured chunks of stdout and stderr output, along with their associated + outcomes. + """ + + type: Required[Literal["shell_call_output"]] + """The type of the item. Always `shell_call_output`.""" + + id: Optional[str] + """The unique ID of the shell tool call output. + + Populated when this item is returned via API. + """ + + max_output_length: Optional[int] + """ + The maximum number of UTF-8 characters captured for this shell call's combined + output. + """ + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the shell call output.""" + + +class ApplyPatchCallOperationCreateFile(TypedDict, total=False): + """Instruction for creating a new file via the apply_patch tool.""" + + diff: Required[str] + """Unified diff content to apply when creating the file.""" + + path: Required[str] + """Path of the file to create relative to the workspace root.""" + + type: Required[Literal["create_file"]] + """The operation type. Always `create_file`.""" + + +class ApplyPatchCallOperationDeleteFile(TypedDict, total=False): + """Instruction for deleting an existing file via the apply_patch tool.""" + + path: Required[str] + """Path of the file to delete relative to the workspace root.""" + + type: Required[Literal["delete_file"]] + """The operation type. Always `delete_file`.""" + + +class ApplyPatchCallOperationUpdateFile(TypedDict, total=False): + """Instruction for updating an existing file via the apply_patch tool.""" + + diff: Required[str] + """Unified diff content to apply to the existing file.""" + + path: Required[str] + """Path of the file to update relative to the workspace root.""" + + type: Required[Literal["update_file"]] + """The operation type. Always `update_file`.""" + + +ApplyPatchCallOperation: TypeAlias = Union[ + ApplyPatchCallOperationCreateFile, ApplyPatchCallOperationDeleteFile, ApplyPatchCallOperationUpdateFile +] + + +class ApplyPatchCall(TypedDict, total=False): + """ + A tool call representing a request to create, delete, or update files using diff patches. + """ + + call_id: Required[str] + """The unique ID of the apply patch tool call generated by the model.""" + + operation: Required[ApplyPatchCallOperation] + """ + The specific create, delete, or update instruction for the apply_patch tool + call. + """ + + status: Required[Literal["in_progress", "completed"]] + """The status of the apply patch tool call. One of `in_progress` or `completed`.""" + + type: Required[Literal["apply_patch_call"]] + """The type of the item. Always `apply_patch_call`.""" + + id: Optional[str] + """The unique ID of the apply patch tool call. + + Populated when this item is returned via API. + """ + + +class ApplyPatchCallOutput(TypedDict, total=False): + """The streamed output emitted by an apply patch tool call.""" + + call_id: Required[str] + """The unique ID of the apply patch tool call generated by the model.""" + + status: Required[Literal["completed", "failed"]] + """The status of the apply patch tool call output. One of `completed` or `failed`.""" + + type: Required[Literal["apply_patch_call_output"]] + """The type of the item. Always `apply_patch_call_output`.""" + + id: Optional[str] + """The unique ID of the apply patch tool call output. + + Populated when this item is returned via API. + """ + + output: Optional[str] + """ + Optional human-readable log text from the apply patch tool (e.g., patch results + or errors). + """ + + +class McpListToolsTool(TypedDict, total=False): + """A tool available on an MCP server.""" + + input_schema: Required[object] + """The JSON schema describing the tool's input.""" + + name: Required[str] + """The name of the tool.""" + + annotations: Optional[object] + """Additional annotations about the tool.""" + + description: Optional[str] + """The description of the tool.""" + + +class McpListTools(TypedDict, total=False): + """A list of tools available on an MCP server.""" + + id: Required[str] + """The unique ID of the list.""" + + server_label: Required[str] + """The label of the MCP server.""" + + tools: Required[Iterable[McpListToolsTool]] + """The tools available on the server.""" + + type: Required[Literal["mcp_list_tools"]] + """The type of the item. Always `mcp_list_tools`.""" + + error: Optional[str] + """Error message if the server could not list tools.""" + + +class McpApprovalRequest(TypedDict, total=False): + """A request for human approval of a tool invocation.""" + + id: Required[str] + """The unique ID of the approval request.""" + + arguments: Required[str] + """A JSON string of arguments for the tool.""" + + name: Required[str] + """The name of the tool to run.""" + + server_label: Required[str] + """The label of the MCP server making the request.""" + + type: Required[Literal["mcp_approval_request"]] + """The type of the item. Always `mcp_approval_request`.""" + + +class McpApprovalResponse(TypedDict, total=False): + """A response to an MCP approval request.""" + + approval_request_id: Required[str] + """The ID of the approval request being answered.""" + + approve: Required[bool] + """Whether the request was approved.""" + + type: Required[Literal["mcp_approval_response"]] + """The type of the item. Always `mcp_approval_response`.""" + + id: Optional[str] + """The unique ID of the approval response""" + + reason: Optional[str] + """Optional reason for the decision.""" + + +class McpCall(TypedDict, total=False): + """An invocation of a tool on an MCP server.""" + + id: Required[str] + """The unique ID of the tool call.""" + + arguments: Required[str] + """A JSON string of the arguments passed to the tool.""" + + name: Required[str] + """The name of the tool that was run.""" + + server_label: Required[str] + """The label of the MCP server running the tool.""" + + type: Required[Literal["mcp_call"]] + """The type of the item. Always `mcp_call`.""" + + approval_request_id: Optional[str] + """ + Unique identifier for the MCP tool call approval request. Include this value in + a subsequent `mcp_approval_response` input to approve or reject the + corresponding tool call. + """ + + error: Optional[str] + """The error from the tool call, if any.""" + + output: Optional[str] + """The output from the tool call.""" + + status: Literal["in_progress", "completed", "incomplete", "calling", "failed"] + """The status of the tool call. + + One of `in_progress`, `completed`, `incomplete`, `calling`, or `failed`. + """ + + +class CompactionTrigger(TypedDict, total=False): + """Compacts the current context. Must be the final input item.""" + + type: Required[Literal["compaction_trigger"]] + """The type of the item. Always `compaction_trigger`.""" + + +class ItemReference(TypedDict, total=False): + """An internal identifier for an item to reference.""" + + id: Required[str] + """The ID of the item to reference.""" + + type: Optional[Literal["item_reference"]] + """The type of item to reference. Always `item_reference`.""" + + +ResponseInputItemParam: TypeAlias = Union[ + EasyInputMessageParam, + Message, + ResponseOutputMessageParam, + ResponseFileSearchToolCallParam, + ResponseComputerToolCallParam, + ComputerCallOutput, + ResponseFunctionWebSearchParam, + ResponseFunctionToolCallParam, + FunctionCallOutput, + ToolSearchCall, + ResponseToolSearchOutputItemParamParam, + AdditionalTools, + ResponseReasoningItemParam, + ResponseCompactionItemParamParam, + ImageGenerationCall, + ResponseCodeInterpreterToolCallParam, + LocalShellCall, + LocalShellCallOutput, + ShellCall, + ShellCallOutput, + ApplyPatchCall, + ApplyPatchCallOutput, + McpListTools, + McpApprovalRequest, + McpApprovalResponse, + McpCall, + ResponseCustomToolCallOutputParam, + ResponseCustomToolCallParam, + CompactionTrigger, + ItemReference, +] + +ResponseInputParam: TypeAlias = List[ResponseInputItemParam] diff --git a/src/openai/types/responses/response_input_text.py b/src/openai/types/responses/response_input_text.py new file mode 100644 index 0000000000..1e06ba71f3 --- /dev/null +++ b/src/openai/types/responses/response_input_text.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseInputText"] + + +class ResponseInputText(BaseModel): + """A text input to the model.""" + + text: str + """The text input to the model.""" + + type: Literal["input_text"] + """The type of the input item. Always `input_text`.""" diff --git a/src/openai/types/responses/response_input_text_content.py b/src/openai/types/responses/response_input_text_content.py new file mode 100644 index 0000000000..66dbb8b0d0 --- /dev/null +++ b/src/openai/types/responses/response_input_text_content.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseInputTextContent"] + + +class ResponseInputTextContent(BaseModel): + """A text input to the model.""" + + text: str + """The text input to the model.""" + + type: Literal["input_text"] + """The type of the input item. Always `input_text`.""" diff --git a/src/openai/types/responses/response_input_text_content_param.py b/src/openai/types/responses/response_input_text_content_param.py new file mode 100644 index 0000000000..013f22d0df --- /dev/null +++ b/src/openai/types/responses/response_input_text_content_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseInputTextContentParam"] + + +class ResponseInputTextContentParam(TypedDict, total=False): + """A text input to the model.""" + + text: Required[str] + """The text input to the model.""" + + type: Required[Literal["input_text"]] + """The type of the input item. Always `input_text`.""" diff --git a/src/openai/types/responses/response_input_text_param.py b/src/openai/types/responses/response_input_text_param.py new file mode 100644 index 0000000000..e1a2976e2e --- /dev/null +++ b/src/openai/types/responses/response_input_text_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseInputTextParam"] + + +class ResponseInputTextParam(TypedDict, total=False): + """A text input to the model.""" + + text: Required[str] + """The text input to the model.""" + + type: Required[Literal["input_text"]] + """The type of the input item. Always `input_text`.""" diff --git a/src/openai/types/responses/response_item.py b/src/openai/types/responses/response_item.py new file mode 100644 index 0000000000..eb2cd44192 --- /dev/null +++ b/src/openai/types/responses/response_item.py @@ -0,0 +1,273 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from .tool import Tool +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .response_output_message import ResponseOutputMessage +from .response_reasoning_item import ResponseReasoningItem +from .response_compaction_item import ResponseCompactionItem +from .response_tool_search_call import ResponseToolSearchCall +from .response_computer_tool_call import ResponseComputerToolCall +from .response_input_message_item import ResponseInputMessageItem +from .response_function_web_search import ResponseFunctionWebSearch +from .response_apply_patch_tool_call import ResponseApplyPatchToolCall +from .response_custom_tool_call_item import ResponseCustomToolCallItem +from .response_file_search_tool_call import ResponseFileSearchToolCall +from .response_function_tool_call_item import ResponseFunctionToolCallItem +from .response_tool_search_output_item import ResponseToolSearchOutputItem +from .response_function_shell_tool_call import ResponseFunctionShellToolCall +from .response_code_interpreter_tool_call import ResponseCodeInterpreterToolCall +from .response_apply_patch_tool_call_output import ResponseApplyPatchToolCallOutput +from .response_custom_tool_call_output_item import ResponseCustomToolCallOutputItem +from .response_computer_tool_call_output_item import ResponseComputerToolCallOutputItem +from .response_function_tool_call_output_item import ResponseFunctionToolCallOutputItem +from .response_function_shell_tool_call_output import ResponseFunctionShellToolCallOutput + +__all__ = [ + "ResponseItem", + "AdditionalTools", + "ImageGenerationCall", + "LocalShellCall", + "LocalShellCallAction", + "LocalShellCallOutput", + "McpListTools", + "McpListToolsTool", + "McpApprovalRequest", + "McpApprovalResponse", + "McpCall", +] + + +class AdditionalTools(BaseModel): + id: str + """The unique ID of the additional tools item.""" + + role: Literal["unknown", "user", "assistant", "system", "critic", "discriminator", "developer", "tool"] + """The role that provided the additional tools.""" + + tools: List[Tool] + """The additional tool definitions made available at this item.""" + + type: Literal["additional_tools"] + """The type of the item. Always `additional_tools`.""" + + +class ImageGenerationCall(BaseModel): + """An image generation request made by the model.""" + + id: str + """The unique ID of the image generation call.""" + + result: Optional[str] = None + """The generated image encoded in base64.""" + + status: Literal["in_progress", "completed", "generating", "failed"] + """The status of the image generation call.""" + + type: Literal["image_generation_call"] + """The type of the image generation call. Always `image_generation_call`.""" + + +class LocalShellCallAction(BaseModel): + """Execute a shell command on the server.""" + + command: List[str] + """The command to run.""" + + env: Dict[str, str] + """Environment variables to set for the command.""" + + type: Literal["exec"] + """The type of the local shell action. Always `exec`.""" + + timeout_ms: Optional[int] = None + """Optional timeout in milliseconds for the command.""" + + user: Optional[str] = None + """Optional user to run the command as.""" + + working_directory: Optional[str] = None + """Optional working directory to run the command in.""" + + +class LocalShellCall(BaseModel): + """A tool call to run a command on the local shell.""" + + id: str + """The unique ID of the local shell call.""" + + action: LocalShellCallAction + """Execute a shell command on the server.""" + + call_id: str + """The unique ID of the local shell tool call generated by the model.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the local shell call.""" + + type: Literal["local_shell_call"] + """The type of the local shell call. Always `local_shell_call`.""" + + +class LocalShellCallOutput(BaseModel): + """The output of a local shell tool call.""" + + id: str + """The unique ID of the local shell tool call generated by the model.""" + + output: str + """A JSON string of the output of the local shell tool call.""" + + type: Literal["local_shell_call_output"] + """The type of the local shell tool call output. Always `local_shell_call_output`.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the item. One of `in_progress`, `completed`, or `incomplete`.""" + + +class McpListToolsTool(BaseModel): + """A tool available on an MCP server.""" + + input_schema: object + """The JSON schema describing the tool's input.""" + + name: str + """The name of the tool.""" + + annotations: Optional[object] = None + """Additional annotations about the tool.""" + + description: Optional[str] = None + """The description of the tool.""" + + +class McpListTools(BaseModel): + """A list of tools available on an MCP server.""" + + id: str + """The unique ID of the list.""" + + server_label: str + """The label of the MCP server.""" + + tools: List[McpListToolsTool] + """The tools available on the server.""" + + type: Literal["mcp_list_tools"] + """The type of the item. Always `mcp_list_tools`.""" + + error: Optional[str] = None + """Error message if the server could not list tools.""" + + +class McpApprovalRequest(BaseModel): + """A request for human approval of a tool invocation.""" + + id: str + """The unique ID of the approval request.""" + + arguments: str + """A JSON string of arguments for the tool.""" + + name: str + """The name of the tool to run.""" + + server_label: str + """The label of the MCP server making the request.""" + + type: Literal["mcp_approval_request"] + """The type of the item. Always `mcp_approval_request`.""" + + +class McpApprovalResponse(BaseModel): + """A response to an MCP approval request.""" + + id: str + """The unique ID of the approval response""" + + approval_request_id: str + """The ID of the approval request being answered.""" + + approve: bool + """Whether the request was approved.""" + + type: Literal["mcp_approval_response"] + """The type of the item. Always `mcp_approval_response`.""" + + reason: Optional[str] = None + """Optional reason for the decision.""" + + +class McpCall(BaseModel): + """An invocation of a tool on an MCP server.""" + + id: str + """The unique ID of the tool call.""" + + arguments: str + """A JSON string of the arguments passed to the tool.""" + + name: str + """The name of the tool that was run.""" + + server_label: str + """The label of the MCP server running the tool.""" + + type: Literal["mcp_call"] + """The type of the item. Always `mcp_call`.""" + + approval_request_id: Optional[str] = None + """ + Unique identifier for the MCP tool call approval request. Include this value in + a subsequent `mcp_approval_response` input to approve or reject the + corresponding tool call. + """ + + error: Optional[str] = None + """The error from the tool call, if any.""" + + output: Optional[str] = None + """The output from the tool call.""" + + status: Optional[Literal["in_progress", "completed", "incomplete", "calling", "failed"]] = None + """The status of the tool call. + + One of `in_progress`, `completed`, `incomplete`, `calling`, or `failed`. + """ + + +ResponseItem: TypeAlias = Annotated[ + Union[ + ResponseInputMessageItem, + ResponseOutputMessage, + ResponseFileSearchToolCall, + ResponseComputerToolCall, + ResponseComputerToolCallOutputItem, + ResponseFunctionWebSearch, + ResponseFunctionToolCallItem, + ResponseFunctionToolCallOutputItem, + ResponseToolSearchCall, + ResponseToolSearchOutputItem, + AdditionalTools, + ResponseReasoningItem, + ResponseCompactionItem, + ImageGenerationCall, + ResponseCodeInterpreterToolCall, + LocalShellCall, + LocalShellCallOutput, + ResponseFunctionShellToolCall, + ResponseFunctionShellToolCallOutput, + ResponseApplyPatchToolCall, + ResponseApplyPatchToolCallOutput, + McpListTools, + McpApprovalRequest, + McpApprovalResponse, + McpCall, + ResponseCustomToolCallItem, + ResponseCustomToolCallOutputItem, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/responses/response_item_list.py b/src/openai/types/responses/response_item_list.py new file mode 100644 index 0000000000..e2b5a1a961 --- /dev/null +++ b/src/openai/types/responses/response_item_list.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import Literal + +from ..._models import BaseModel +from .response_item import ResponseItem + +__all__ = ["ResponseItemList"] + + +class ResponseItemList(BaseModel): + """A list of Response items.""" + + data: List[ResponseItem] + """A list of items used to generate this response.""" + + first_id: str + """The ID of the first item in the list.""" + + has_more: bool + """Whether there are more items available.""" + + last_id: str + """The ID of the last item in the list.""" + + object: Literal["list"] + """The type of object returned, must be `list`.""" diff --git a/src/openai/types/responses/response_local_environment.py b/src/openai/types/responses/response_local_environment.py new file mode 100644 index 0000000000..6467fcb4c5 --- /dev/null +++ b/src/openai/types/responses/response_local_environment.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseLocalEnvironment"] + + +class ResponseLocalEnvironment(BaseModel): + """Represents the use of a local environment to perform shell actions.""" + + type: Literal["local"] + """The environment type. Always `local`.""" diff --git a/src/openai/types/responses/response_mcp_call_arguments_delta_event.py b/src/openai/types/responses/response_mcp_call_arguments_delta_event.py new file mode 100644 index 0000000000..303ef494a3 --- /dev/null +++ b/src/openai/types/responses/response_mcp_call_arguments_delta_event.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpCallArgumentsDeltaEvent"] + + +class ResponseMcpCallArgumentsDeltaEvent(BaseModel): + """ + Emitted when there is a delta (partial update) to the arguments of an MCP tool call. + """ + + delta: str + """ + A JSON string containing the partial update to the arguments for the MCP tool + call. + """ + + item_id: str + """The unique identifier of the MCP tool call item being processed.""" + + output_index: int + """The index of the output item in the response's output array.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.mcp_call_arguments.delta"] + """The type of the event. Always 'response.mcp_call_arguments.delta'.""" diff --git a/src/openai/types/responses/response_mcp_call_arguments_done_event.py b/src/openai/types/responses/response_mcp_call_arguments_done_event.py new file mode 100644 index 0000000000..59e71be77c --- /dev/null +++ b/src/openai/types/responses/response_mcp_call_arguments_done_event.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpCallArgumentsDoneEvent"] + + +class ResponseMcpCallArgumentsDoneEvent(BaseModel): + """Emitted when the arguments for an MCP tool call are finalized.""" + + arguments: str + """A JSON string containing the finalized arguments for the MCP tool call.""" + + item_id: str + """The unique identifier of the MCP tool call item being processed.""" + + output_index: int + """The index of the output item in the response's output array.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.mcp_call_arguments.done"] + """The type of the event. Always 'response.mcp_call_arguments.done'.""" diff --git a/src/openai/types/responses/response_mcp_call_completed_event.py b/src/openai/types/responses/response_mcp_call_completed_event.py new file mode 100644 index 0000000000..bee54d4039 --- /dev/null +++ b/src/openai/types/responses/response_mcp_call_completed_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpCallCompletedEvent"] + + +class ResponseMcpCallCompletedEvent(BaseModel): + """Emitted when an MCP tool call has completed successfully.""" + + item_id: str + """The ID of the MCP tool call item that completed.""" + + output_index: int + """The index of the output item that completed.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.mcp_call.completed"] + """The type of the event. Always 'response.mcp_call.completed'.""" diff --git a/src/openai/types/responses/response_mcp_call_failed_event.py b/src/openai/types/responses/response_mcp_call_failed_event.py new file mode 100644 index 0000000000..cb3130b155 --- /dev/null +++ b/src/openai/types/responses/response_mcp_call_failed_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpCallFailedEvent"] + + +class ResponseMcpCallFailedEvent(BaseModel): + """Emitted when an MCP tool call has failed.""" + + item_id: str + """The ID of the MCP tool call item that failed.""" + + output_index: int + """The index of the output item that failed.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.mcp_call.failed"] + """The type of the event. Always 'response.mcp_call.failed'.""" diff --git a/src/openai/types/responses/response_mcp_call_in_progress_event.py b/src/openai/types/responses/response_mcp_call_in_progress_event.py new file mode 100644 index 0000000000..7cf6a1decf --- /dev/null +++ b/src/openai/types/responses/response_mcp_call_in_progress_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpCallInProgressEvent"] + + +class ResponseMcpCallInProgressEvent(BaseModel): + """Emitted when an MCP tool call is in progress.""" + + item_id: str + """The unique identifier of the MCP tool call item being processed.""" + + output_index: int + """The index of the output item in the response's output array.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.mcp_call.in_progress"] + """The type of the event. Always 'response.mcp_call.in_progress'.""" diff --git a/src/openai/types/responses/response_mcp_list_tools_completed_event.py b/src/openai/types/responses/response_mcp_list_tools_completed_event.py new file mode 100644 index 0000000000..685ba59c4d --- /dev/null +++ b/src/openai/types/responses/response_mcp_list_tools_completed_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpListToolsCompletedEvent"] + + +class ResponseMcpListToolsCompletedEvent(BaseModel): + """Emitted when the list of available MCP tools has been successfully retrieved.""" + + item_id: str + """The ID of the MCP tool call item that produced this output.""" + + output_index: int + """The index of the output item that was processed.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.mcp_list_tools.completed"] + """The type of the event. Always 'response.mcp_list_tools.completed'.""" diff --git a/src/openai/types/responses/response_mcp_list_tools_failed_event.py b/src/openai/types/responses/response_mcp_list_tools_failed_event.py new file mode 100644 index 0000000000..c5fa54d231 --- /dev/null +++ b/src/openai/types/responses/response_mcp_list_tools_failed_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpListToolsFailedEvent"] + + +class ResponseMcpListToolsFailedEvent(BaseModel): + """Emitted when the attempt to list available MCP tools has failed.""" + + item_id: str + """The ID of the MCP tool call item that failed.""" + + output_index: int + """The index of the output item that failed.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.mcp_list_tools.failed"] + """The type of the event. Always 'response.mcp_list_tools.failed'.""" diff --git a/src/openai/types/responses/response_mcp_list_tools_in_progress_event.py b/src/openai/types/responses/response_mcp_list_tools_in_progress_event.py new file mode 100644 index 0000000000..403fdbdeb3 --- /dev/null +++ b/src/openai/types/responses/response_mcp_list_tools_in_progress_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseMcpListToolsInProgressEvent"] + + +class ResponseMcpListToolsInProgressEvent(BaseModel): + """ + Emitted when the system is in the process of retrieving the list of available MCP tools. + """ + + item_id: str + """The ID of the MCP tool call item that is being processed.""" + + output_index: int + """The index of the output item that is being processed.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.mcp_list_tools.in_progress"] + """The type of the event. Always 'response.mcp_list_tools.in_progress'.""" diff --git a/src/openai/types/responses/response_output_item.py b/src/openai/types/responses/response_output_item.py new file mode 100644 index 0000000000..ee7057348b --- /dev/null +++ b/src/openai/types/responses/response_output_item.py @@ -0,0 +1,271 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from .tool import Tool +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .response_output_message import ResponseOutputMessage +from .response_reasoning_item import ResponseReasoningItem +from .response_compaction_item import ResponseCompactionItem +from .response_custom_tool_call import ResponseCustomToolCall +from .response_tool_search_call import ResponseToolSearchCall +from .response_computer_tool_call import ResponseComputerToolCall +from .response_function_tool_call import ResponseFunctionToolCall +from .response_function_web_search import ResponseFunctionWebSearch +from .response_apply_patch_tool_call import ResponseApplyPatchToolCall +from .response_file_search_tool_call import ResponseFileSearchToolCall +from .response_tool_search_output_item import ResponseToolSearchOutputItem +from .response_function_shell_tool_call import ResponseFunctionShellToolCall +from .response_code_interpreter_tool_call import ResponseCodeInterpreterToolCall +from .response_apply_patch_tool_call_output import ResponseApplyPatchToolCallOutput +from .response_custom_tool_call_output_item import ResponseCustomToolCallOutputItem +from .response_computer_tool_call_output_item import ResponseComputerToolCallOutputItem +from .response_function_tool_call_output_item import ResponseFunctionToolCallOutputItem +from .response_function_shell_tool_call_output import ResponseFunctionShellToolCallOutput + +__all__ = [ + "ResponseOutputItem", + "AdditionalTools", + "ImageGenerationCall", + "LocalShellCall", + "LocalShellCallAction", + "LocalShellCallOutput", + "McpCall", + "McpListTools", + "McpListToolsTool", + "McpApprovalRequest", + "McpApprovalResponse", +] + + +class AdditionalTools(BaseModel): + id: str + """The unique ID of the additional tools item.""" + + role: Literal["unknown", "user", "assistant", "system", "critic", "discriminator", "developer", "tool"] + """The role that provided the additional tools.""" + + tools: List[Tool] + """The additional tool definitions made available at this item.""" + + type: Literal["additional_tools"] + """The type of the item. Always `additional_tools`.""" + + +class ImageGenerationCall(BaseModel): + """An image generation request made by the model.""" + + id: str + """The unique ID of the image generation call.""" + + result: Optional[str] = None + """The generated image encoded in base64.""" + + status: Literal["in_progress", "completed", "generating", "failed"] + """The status of the image generation call.""" + + type: Literal["image_generation_call"] + """The type of the image generation call. Always `image_generation_call`.""" + + +class LocalShellCallAction(BaseModel): + """Execute a shell command on the server.""" + + command: List[str] + """The command to run.""" + + env: Dict[str, str] + """Environment variables to set for the command.""" + + type: Literal["exec"] + """The type of the local shell action. Always `exec`.""" + + timeout_ms: Optional[int] = None + """Optional timeout in milliseconds for the command.""" + + user: Optional[str] = None + """Optional user to run the command as.""" + + working_directory: Optional[str] = None + """Optional working directory to run the command in.""" + + +class LocalShellCall(BaseModel): + """A tool call to run a command on the local shell.""" + + id: str + """The unique ID of the local shell call.""" + + action: LocalShellCallAction + """Execute a shell command on the server.""" + + call_id: str + """The unique ID of the local shell tool call generated by the model.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the local shell call.""" + + type: Literal["local_shell_call"] + """The type of the local shell call. Always `local_shell_call`.""" + + +class LocalShellCallOutput(BaseModel): + """The output of a local shell tool call.""" + + id: str + """The unique ID of the local shell tool call generated by the model.""" + + output: str + """A JSON string of the output of the local shell tool call.""" + + type: Literal["local_shell_call_output"] + """The type of the local shell tool call output. Always `local_shell_call_output`.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the item. One of `in_progress`, `completed`, or `incomplete`.""" + + +class McpCall(BaseModel): + """An invocation of a tool on an MCP server.""" + + id: str + """The unique ID of the tool call.""" + + arguments: str + """A JSON string of the arguments passed to the tool.""" + + name: str + """The name of the tool that was run.""" + + server_label: str + """The label of the MCP server running the tool.""" + + type: Literal["mcp_call"] + """The type of the item. Always `mcp_call`.""" + + approval_request_id: Optional[str] = None + """ + Unique identifier for the MCP tool call approval request. Include this value in + a subsequent `mcp_approval_response` input to approve or reject the + corresponding tool call. + """ + + error: Optional[str] = None + """The error from the tool call, if any.""" + + output: Optional[str] = None + """The output from the tool call.""" + + status: Optional[Literal["in_progress", "completed", "incomplete", "calling", "failed"]] = None + """The status of the tool call. + + One of `in_progress`, `completed`, `incomplete`, `calling`, or `failed`. + """ + + +class McpListToolsTool(BaseModel): + """A tool available on an MCP server.""" + + input_schema: object + """The JSON schema describing the tool's input.""" + + name: str + """The name of the tool.""" + + annotations: Optional[object] = None + """Additional annotations about the tool.""" + + description: Optional[str] = None + """The description of the tool.""" + + +class McpListTools(BaseModel): + """A list of tools available on an MCP server.""" + + id: str + """The unique ID of the list.""" + + server_label: str + """The label of the MCP server.""" + + tools: List[McpListToolsTool] + """The tools available on the server.""" + + type: Literal["mcp_list_tools"] + """The type of the item. Always `mcp_list_tools`.""" + + error: Optional[str] = None + """Error message if the server could not list tools.""" + + +class McpApprovalRequest(BaseModel): + """A request for human approval of a tool invocation.""" + + id: str + """The unique ID of the approval request.""" + + arguments: str + """A JSON string of arguments for the tool.""" + + name: str + """The name of the tool to run.""" + + server_label: str + """The label of the MCP server making the request.""" + + type: Literal["mcp_approval_request"] + """The type of the item. Always `mcp_approval_request`.""" + + +class McpApprovalResponse(BaseModel): + """A response to an MCP approval request.""" + + id: str + """The unique ID of the approval response""" + + approval_request_id: str + """The ID of the approval request being answered.""" + + approve: bool + """Whether the request was approved.""" + + type: Literal["mcp_approval_response"] + """The type of the item. Always `mcp_approval_response`.""" + + reason: Optional[str] = None + """Optional reason for the decision.""" + + +ResponseOutputItem: TypeAlias = Annotated[ + Union[ + ResponseOutputMessage, + ResponseFileSearchToolCall, + ResponseFunctionToolCall, + ResponseFunctionToolCallOutputItem, + ResponseFunctionWebSearch, + ResponseComputerToolCall, + ResponseComputerToolCallOutputItem, + ResponseReasoningItem, + ResponseToolSearchCall, + ResponseToolSearchOutputItem, + AdditionalTools, + ResponseCompactionItem, + ImageGenerationCall, + ResponseCodeInterpreterToolCall, + LocalShellCall, + LocalShellCallOutput, + ResponseFunctionShellToolCall, + ResponseFunctionShellToolCallOutput, + ResponseApplyPatchToolCall, + ResponseApplyPatchToolCallOutput, + McpCall, + McpListTools, + McpApprovalRequest, + McpApprovalResponse, + ResponseCustomToolCall, + ResponseCustomToolCallOutputItem, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/responses/response_output_item_added_event.py b/src/openai/types/responses/response_output_item_added_event.py new file mode 100644 index 0000000000..a42f6281e3 --- /dev/null +++ b/src/openai/types/responses/response_output_item_added_event.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from .response_output_item import ResponseOutputItem + +__all__ = ["ResponseOutputItemAddedEvent"] + + +class ResponseOutputItemAddedEvent(BaseModel): + """Emitted when a new output item is added.""" + + item: ResponseOutputItem + """The output item that was added.""" + + output_index: int + """The index of the output item that was added.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.output_item.added"] + """The type of the event. Always `response.output_item.added`.""" diff --git a/src/openai/types/responses/response_output_item_done_event.py b/src/openai/types/responses/response_output_item_done_event.py new file mode 100644 index 0000000000..50b99da569 --- /dev/null +++ b/src/openai/types/responses/response_output_item_done_event.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from .response_output_item import ResponseOutputItem + +__all__ = ["ResponseOutputItemDoneEvent"] + + +class ResponseOutputItemDoneEvent(BaseModel): + """Emitted when an output item is marked done.""" + + item: ResponseOutputItem + """The output item that was marked done.""" + + output_index: int + """The index of the output item that was marked done.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.output_item.done"] + """The type of the event. Always `response.output_item.done`.""" diff --git a/src/openai/types/responses/response_output_message.py b/src/openai/types/responses/response_output_message.py new file mode 100644 index 0000000000..760d72d58d --- /dev/null +++ b/src/openai/types/responses/response_output_message.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .response_output_text import ResponseOutputText +from .response_output_refusal import ResponseOutputRefusal + +__all__ = ["ResponseOutputMessage", "Content"] + +Content: TypeAlias = Annotated[Union[ResponseOutputText, ResponseOutputRefusal], PropertyInfo(discriminator="type")] + + +class ResponseOutputMessage(BaseModel): + """An output message from the model.""" + + id: str + """The unique ID of the output message.""" + + content: List[Content] + """The content of the output message.""" + + role: Literal["assistant"] + """The role of the output message. Always `assistant`.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the message input. + + One of `in_progress`, `completed`, or `incomplete`. Populated when input items + are returned via API. + """ + + type: Literal["message"] + """The type of the output message. Always `message`.""" + + phase: Optional[Literal["commentary", "final_answer"]] = None + """ + Labels an `assistant` message as intermediate commentary (`commentary`) or the + final answer (`final_answer`). For models like `gpt-5.3-codex` and beyond, when + sending follow-up requests, preserve and resend phase on all assistant messages + — dropping it can degrade performance. Not used for user messages. + """ diff --git a/src/openai/types/responses/response_output_message_param.py b/src/openai/types/responses/response_output_message_param.py new file mode 100644 index 0000000000..09fec5bd58 --- /dev/null +++ b/src/openai/types/responses/response_output_message_param.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .response_output_text_param import ResponseOutputTextParam +from .response_output_refusal_param import ResponseOutputRefusalParam + +__all__ = ["ResponseOutputMessageParam", "Content"] + +Content: TypeAlias = Union[ResponseOutputTextParam, ResponseOutputRefusalParam] + + +class ResponseOutputMessageParam(TypedDict, total=False): + """An output message from the model.""" + + id: Required[str] + """The unique ID of the output message.""" + + content: Required[Iterable[Content]] + """The content of the output message.""" + + role: Required[Literal["assistant"]] + """The role of the output message. Always `assistant`.""" + + status: Required[Literal["in_progress", "completed", "incomplete"]] + """The status of the message input. + + One of `in_progress`, `completed`, or `incomplete`. Populated when input items + are returned via API. + """ + + type: Required[Literal["message"]] + """The type of the output message. Always `message`.""" + + phase: Optional[Literal["commentary", "final_answer"]] + """ + Labels an `assistant` message as intermediate commentary (`commentary`) or the + final answer (`final_answer`). For models like `gpt-5.3-codex` and beyond, when + sending follow-up requests, preserve and resend phase on all assistant messages + — dropping it can degrade performance. Not used for user messages. + """ diff --git a/src/openai/types/responses/response_output_refusal.py b/src/openai/types/responses/response_output_refusal.py new file mode 100644 index 0000000000..6bce26af74 --- /dev/null +++ b/src/openai/types/responses/response_output_refusal.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseOutputRefusal"] + + +class ResponseOutputRefusal(BaseModel): + """A refusal from the model.""" + + refusal: str + """The refusal explanation from the model.""" + + type: Literal["refusal"] + """The type of the refusal. Always `refusal`.""" diff --git a/src/openai/types/responses/response_output_refusal_param.py b/src/openai/types/responses/response_output_refusal_param.py new file mode 100644 index 0000000000..02bdfdcf4f --- /dev/null +++ b/src/openai/types/responses/response_output_refusal_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseOutputRefusalParam"] + + +class ResponseOutputRefusalParam(TypedDict, total=False): + """A refusal from the model.""" + + refusal: Required[str] + """The refusal explanation from the model.""" + + type: Required[Literal["refusal"]] + """The type of the refusal. Always `refusal`.""" diff --git a/src/openai/types/responses/response_output_text.py b/src/openai/types/responses/response_output_text.py new file mode 100644 index 0000000000..2386fcb3c0 --- /dev/null +++ b/src/openai/types/responses/response_output_text.py @@ -0,0 +1,131 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = [ + "ResponseOutputText", + "Annotation", + "AnnotationFileCitation", + "AnnotationURLCitation", + "AnnotationContainerFileCitation", + "AnnotationFilePath", + "Logprob", + "LogprobTopLogprob", +] + + +class AnnotationFileCitation(BaseModel): + """A citation to a file.""" + + file_id: str + """The ID of the file.""" + + filename: str + """The filename of the file cited.""" + + index: int + """The index of the file in the list of files.""" + + type: Literal["file_citation"] + """The type of the file citation. Always `file_citation`.""" + + +class AnnotationURLCitation(BaseModel): + """A citation for a web resource used to generate a model response.""" + + end_index: int + """The index of the last character of the URL citation in the message.""" + + start_index: int + """The index of the first character of the URL citation in the message.""" + + title: str + """The title of the web resource.""" + + type: Literal["url_citation"] + """The type of the URL citation. Always `url_citation`.""" + + url: str + """The URL of the web resource.""" + + +class AnnotationContainerFileCitation(BaseModel): + """A citation for a container file used to generate a model response.""" + + container_id: str + """The ID of the container file.""" + + end_index: int + """The index of the last character of the container file citation in the message.""" + + file_id: str + """The ID of the file.""" + + filename: str + """The filename of the container file cited.""" + + start_index: int + """The index of the first character of the container file citation in the message.""" + + type: Literal["container_file_citation"] + """The type of the container file citation. Always `container_file_citation`.""" + + +class AnnotationFilePath(BaseModel): + """A path to a file.""" + + file_id: str + """The ID of the file.""" + + index: int + """The index of the file in the list of files.""" + + type: Literal["file_path"] + """The type of the file path. Always `file_path`.""" + + +Annotation: TypeAlias = Annotated[ + Union[AnnotationFileCitation, AnnotationURLCitation, AnnotationContainerFileCitation, AnnotationFilePath], + PropertyInfo(discriminator="type"), +] + + +class LogprobTopLogprob(BaseModel): + """The top log probability of a token.""" + + token: str + + bytes: List[int] + + logprob: float + + +class Logprob(BaseModel): + """The log probability of a token.""" + + token: str + + bytes: List[int] + + logprob: float + + top_logprobs: List[LogprobTopLogprob] + + +class ResponseOutputText(BaseModel): + """A text output from the model.""" + + annotations: List[Annotation] + """The annotations of the text output.""" + + text: str + """The text output from the model.""" + + type: Literal["output_text"] + """The type of the output text. Always `output_text`.""" + + logprobs: Optional[List[Logprob]] = None diff --git a/src/openai/types/responses/response_output_text_annotation_added_event.py b/src/openai/types/responses/response_output_text_annotation_added_event.py new file mode 100644 index 0000000000..b9dc262150 --- /dev/null +++ b/src/openai/types/responses/response_output_text_annotation_added_event.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseOutputTextAnnotationAddedEvent"] + + +class ResponseOutputTextAnnotationAddedEvent(BaseModel): + """Emitted when an annotation is added to output text content.""" + + annotation: object + """The annotation object being added. (See annotation schema for details.)""" + + annotation_index: int + """The index of the annotation within the content part.""" + + content_index: int + """The index of the content part within the output item.""" + + item_id: str + """The unique identifier of the item to which the annotation is being added.""" + + output_index: int + """The index of the output item in the response's output array.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.output_text.annotation.added"] + """The type of the event. Always 'response.output_text.annotation.added'.""" diff --git a/src/openai/types/responses/response_output_text_param.py b/src/openai/types/responses/response_output_text_param.py new file mode 100644 index 0000000000..bc30fbcd8e --- /dev/null +++ b/src/openai/types/responses/response_output_text_param.py @@ -0,0 +1,129 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = [ + "ResponseOutputTextParam", + "Annotation", + "AnnotationFileCitation", + "AnnotationURLCitation", + "AnnotationContainerFileCitation", + "AnnotationFilePath", + "Logprob", + "LogprobTopLogprob", +] + + +class AnnotationFileCitation(TypedDict, total=False): + """A citation to a file.""" + + file_id: Required[str] + """The ID of the file.""" + + filename: Required[str] + """The filename of the file cited.""" + + index: Required[int] + """The index of the file in the list of files.""" + + type: Required[Literal["file_citation"]] + """The type of the file citation. Always `file_citation`.""" + + +class AnnotationURLCitation(TypedDict, total=False): + """A citation for a web resource used to generate a model response.""" + + end_index: Required[int] + """The index of the last character of the URL citation in the message.""" + + start_index: Required[int] + """The index of the first character of the URL citation in the message.""" + + title: Required[str] + """The title of the web resource.""" + + type: Required[Literal["url_citation"]] + """The type of the URL citation. Always `url_citation`.""" + + url: Required[str] + """The URL of the web resource.""" + + +class AnnotationContainerFileCitation(TypedDict, total=False): + """A citation for a container file used to generate a model response.""" + + container_id: Required[str] + """The ID of the container file.""" + + end_index: Required[int] + """The index of the last character of the container file citation in the message.""" + + file_id: Required[str] + """The ID of the file.""" + + filename: Required[str] + """The filename of the container file cited.""" + + start_index: Required[int] + """The index of the first character of the container file citation in the message.""" + + type: Required[Literal["container_file_citation"]] + """The type of the container file citation. Always `container_file_citation`.""" + + +class AnnotationFilePath(TypedDict, total=False): + """A path to a file.""" + + file_id: Required[str] + """The ID of the file.""" + + index: Required[int] + """The index of the file in the list of files.""" + + type: Required[Literal["file_path"]] + """The type of the file path. Always `file_path`.""" + + +Annotation: TypeAlias = Union[ + AnnotationFileCitation, AnnotationURLCitation, AnnotationContainerFileCitation, AnnotationFilePath +] + + +class LogprobTopLogprob(TypedDict, total=False): + """The top log probability of a token.""" + + token: Required[str] + + bytes: Required[Iterable[int]] + + logprob: Required[float] + + +class Logprob(TypedDict, total=False): + """The log probability of a token.""" + + token: Required[str] + + bytes: Required[Iterable[int]] + + logprob: Required[float] + + top_logprobs: Required[Iterable[LogprobTopLogprob]] + + +class ResponseOutputTextParam(TypedDict, total=False): + """A text output from the model.""" + + annotations: Required[Iterable[Annotation]] + """The annotations of the text output.""" + + text: Required[str] + """The text output from the model.""" + + type: Required[Literal["output_text"]] + """The type of the output text. Always `output_text`.""" + + logprobs: Iterable[Logprob] diff --git a/src/openai/types/responses/response_prompt.py b/src/openai/types/responses/response_prompt.py new file mode 100644 index 0000000000..e3acacf63a --- /dev/null +++ b/src/openai/types/responses/response_prompt.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Union, Optional +from typing_extensions import TypeAlias + +from ..._models import BaseModel +from .response_input_file import ResponseInputFile +from .response_input_text import ResponseInputText +from .response_input_image import ResponseInputImage + +__all__ = ["ResponsePrompt", "Variables"] + +Variables: TypeAlias = Union[str, ResponseInputText, ResponseInputImage, ResponseInputFile] + + +class ResponsePrompt(BaseModel): + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + id: str + """The unique identifier of the prompt template to use.""" + + variables: Optional[Dict[str, Variables]] = None + """Optional map of values to substitute in for variables in your prompt. + + The substitution values can either be strings, or other Response input types + like images or files. + """ + + version: Optional[str] = None + """Optional version of the prompt template.""" diff --git a/src/openai/types/responses/response_prompt_param.py b/src/openai/types/responses/response_prompt_param.py new file mode 100644 index 0000000000..f9a28b62a2 --- /dev/null +++ b/src/openai/types/responses/response_prompt_param.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from typing_extensions import Required, TypeAlias, TypedDict + +from .response_input_file_param import ResponseInputFileParam +from .response_input_text_param import ResponseInputTextParam +from .response_input_image_param import ResponseInputImageParam + +__all__ = ["ResponsePromptParam", "Variables"] + +Variables: TypeAlias = Union[str, ResponseInputTextParam, ResponseInputImageParam, ResponseInputFileParam] + + +class ResponsePromptParam(TypedDict, total=False): + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + id: Required[str] + """The unique identifier of the prompt template to use.""" + + variables: Optional[Dict[str, Variables]] + """Optional map of values to substitute in for variables in your prompt. + + The substitution values can either be strings, or other Response input types + like images or files. + """ + + version: Optional[str] + """Optional version of the prompt template.""" diff --git a/src/openai/types/responses/response_queued_event.py b/src/openai/types/responses/response_queued_event.py new file mode 100644 index 0000000000..a554215275 --- /dev/null +++ b/src/openai/types/responses/response_queued_event.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .response import Response +from ..._models import BaseModel + +__all__ = ["ResponseQueuedEvent"] + + +class ResponseQueuedEvent(BaseModel): + """Emitted when a response is queued and waiting to be processed.""" + + response: Response + """The full response object that is queued.""" + + sequence_number: int + """The sequence number for this event.""" + + type: Literal["response.queued"] + """The type of the event. Always 'response.queued'.""" diff --git a/src/openai/types/responses/response_reasoning_item.py b/src/openai/types/responses/response_reasoning_item.py new file mode 100644 index 0000000000..1a22eb60cc --- /dev/null +++ b/src/openai/types/responses/response_reasoning_item.py @@ -0,0 +1,62 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseReasoningItem", "Summary", "Content"] + + +class Summary(BaseModel): + """A summary text from the model.""" + + text: str + """A summary of the reasoning output from the model so far.""" + + type: Literal["summary_text"] + """The type of the object. Always `summary_text`.""" + + +class Content(BaseModel): + """Reasoning text from the model.""" + + text: str + """The reasoning text from the model.""" + + type: Literal["reasoning_text"] + """The type of the reasoning text. Always `reasoning_text`.""" + + +class ResponseReasoningItem(BaseModel): + """ + A description of the chain of thought used by a reasoning model while generating + a response. Be sure to include these items in your `input` to the Responses API + for subsequent turns of a conversation if you are manually + [managing context](https://platform.openai.com/docs/guides/conversation-state). + """ + + id: str + """The unique identifier of the reasoning content.""" + + summary: List[Summary] + """Reasoning summary content.""" + + type: Literal["reasoning"] + """The type of the object. Always `reasoning`.""" + + content: Optional[List[Content]] = None + """Reasoning text content.""" + + encrypted_content: Optional[str] = None + """ + The encrypted content of the reasoning item - populated when a response is + generated with `reasoning.encrypted_content` in the `include` parameter. + """ + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ diff --git a/src/openai/types/responses/response_reasoning_item_param.py b/src/openai/types/responses/response_reasoning_item_param.py new file mode 100644 index 0000000000..40320b72e1 --- /dev/null +++ b/src/openai/types/responses/response_reasoning_item_param.py @@ -0,0 +1,62 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ResponseReasoningItemParam", "Summary", "Content"] + + +class Summary(TypedDict, total=False): + """A summary text from the model.""" + + text: Required[str] + """A summary of the reasoning output from the model so far.""" + + type: Required[Literal["summary_text"]] + """The type of the object. Always `summary_text`.""" + + +class Content(TypedDict, total=False): + """Reasoning text from the model.""" + + text: Required[str] + """The reasoning text from the model.""" + + type: Required[Literal["reasoning_text"]] + """The type of the reasoning text. Always `reasoning_text`.""" + + +class ResponseReasoningItemParam(TypedDict, total=False): + """ + A description of the chain of thought used by a reasoning model while generating + a response. Be sure to include these items in your `input` to the Responses API + for subsequent turns of a conversation if you are manually + [managing context](https://platform.openai.com/docs/guides/conversation-state). + """ + + id: Required[str] + """The unique identifier of the reasoning content.""" + + summary: Required[Iterable[Summary]] + """Reasoning summary content.""" + + type: Required[Literal["reasoning"]] + """The type of the object. Always `reasoning`.""" + + content: Iterable[Content] + """Reasoning text content.""" + + encrypted_content: Optional[str] + """ + The encrypted content of the reasoning item - populated when a response is + generated with `reasoning.encrypted_content` in the `include` parameter. + """ + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the item. + + One of `in_progress`, `completed`, or `incomplete`. Populated when items are + returned via API. + """ diff --git a/src/openai/types/responses/response_reasoning_summary_part_added_event.py b/src/openai/types/responses/response_reasoning_summary_part_added_event.py new file mode 100644 index 0000000000..e4b0f34231 --- /dev/null +++ b/src/openai/types/responses/response_reasoning_summary_part_added_event.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseReasoningSummaryPartAddedEvent", "Part"] + + +class Part(BaseModel): + """The summary part that was added.""" + + text: str + """The text of the summary part.""" + + type: Literal["summary_text"] + """The type of the summary part. Always `summary_text`.""" + + +class ResponseReasoningSummaryPartAddedEvent(BaseModel): + """Emitted when a new reasoning summary part is added.""" + + item_id: str + """The ID of the item this summary part is associated with.""" + + output_index: int + """The index of the output item this summary part is associated with.""" + + part: Part + """The summary part that was added.""" + + sequence_number: int + """The sequence number of this event.""" + + summary_index: int + """The index of the summary part within the reasoning summary.""" + + type: Literal["response.reasoning_summary_part.added"] + """The type of the event. Always `response.reasoning_summary_part.added`.""" diff --git a/src/openai/types/responses/response_reasoning_summary_part_done_event.py b/src/openai/types/responses/response_reasoning_summary_part_done_event.py new file mode 100644 index 0000000000..48f3f684e8 --- /dev/null +++ b/src/openai/types/responses/response_reasoning_summary_part_done_event.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseReasoningSummaryPartDoneEvent", "Part"] + + +class Part(BaseModel): + """The completed summary part.""" + + text: str + """The text of the summary part.""" + + type: Literal["summary_text"] + """The type of the summary part. Always `summary_text`.""" + + +class ResponseReasoningSummaryPartDoneEvent(BaseModel): + """Emitted when a reasoning summary part is completed.""" + + item_id: str + """The ID of the item this summary part is associated with.""" + + output_index: int + """The index of the output item this summary part is associated with.""" + + part: Part + """The completed summary part.""" + + sequence_number: int + """The sequence number of this event.""" + + summary_index: int + """The index of the summary part within the reasoning summary.""" + + type: Literal["response.reasoning_summary_part.done"] + """The type of the event. Always `response.reasoning_summary_part.done`.""" diff --git a/src/openai/types/responses/response_reasoning_summary_text_delta_event.py b/src/openai/types/responses/response_reasoning_summary_text_delta_event.py new file mode 100644 index 0000000000..84bcf039c4 --- /dev/null +++ b/src/openai/types/responses/response_reasoning_summary_text_delta_event.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseReasoningSummaryTextDeltaEvent"] + + +class ResponseReasoningSummaryTextDeltaEvent(BaseModel): + """Emitted when a delta is added to a reasoning summary text.""" + + delta: str + """The text delta that was added to the summary.""" + + item_id: str + """The ID of the item this summary text delta is associated with.""" + + output_index: int + """The index of the output item this summary text delta is associated with.""" + + sequence_number: int + """The sequence number of this event.""" + + summary_index: int + """The index of the summary part within the reasoning summary.""" + + type: Literal["response.reasoning_summary_text.delta"] + """The type of the event. Always `response.reasoning_summary_text.delta`.""" diff --git a/src/openai/types/responses/response_reasoning_summary_text_done_event.py b/src/openai/types/responses/response_reasoning_summary_text_done_event.py new file mode 100644 index 0000000000..244d001b75 --- /dev/null +++ b/src/openai/types/responses/response_reasoning_summary_text_done_event.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseReasoningSummaryTextDoneEvent"] + + +class ResponseReasoningSummaryTextDoneEvent(BaseModel): + """Emitted when a reasoning summary text is completed.""" + + item_id: str + """The ID of the item this summary text is associated with.""" + + output_index: int + """The index of the output item this summary text is associated with.""" + + sequence_number: int + """The sequence number of this event.""" + + summary_index: int + """The index of the summary part within the reasoning summary.""" + + text: str + """The full text of the completed reasoning summary.""" + + type: Literal["response.reasoning_summary_text.done"] + """The type of the event. Always `response.reasoning_summary_text.done`.""" diff --git a/src/openai/types/responses/response_reasoning_text_delta_event.py b/src/openai/types/responses/response_reasoning_text_delta_event.py new file mode 100644 index 0000000000..0e05226c94 --- /dev/null +++ b/src/openai/types/responses/response_reasoning_text_delta_event.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseReasoningTextDeltaEvent"] + + +class ResponseReasoningTextDeltaEvent(BaseModel): + """Emitted when a delta is added to a reasoning text.""" + + content_index: int + """The index of the reasoning content part this delta is associated with.""" + + delta: str + """The text delta that was added to the reasoning content.""" + + item_id: str + """The ID of the item this reasoning text delta is associated with.""" + + output_index: int + """The index of the output item this reasoning text delta is associated with.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.reasoning_text.delta"] + """The type of the event. Always `response.reasoning_text.delta`.""" diff --git a/src/openai/types/responses/response_reasoning_text_done_event.py b/src/openai/types/responses/response_reasoning_text_done_event.py new file mode 100644 index 0000000000..40e3f4701c --- /dev/null +++ b/src/openai/types/responses/response_reasoning_text_done_event.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseReasoningTextDoneEvent"] + + +class ResponseReasoningTextDoneEvent(BaseModel): + """Emitted when a reasoning text is completed.""" + + content_index: int + """The index of the reasoning content part.""" + + item_id: str + """The ID of the item this reasoning text is associated with.""" + + output_index: int + """The index of the output item this reasoning text is associated with.""" + + sequence_number: int + """The sequence number of this event.""" + + text: str + """The full text of the completed reasoning content.""" + + type: Literal["response.reasoning_text.done"] + """The type of the event. Always `response.reasoning_text.done`.""" diff --git a/src/openai/types/responses/response_refusal_delta_event.py b/src/openai/types/responses/response_refusal_delta_event.py new file mode 100644 index 0000000000..e3933b7dda --- /dev/null +++ b/src/openai/types/responses/response_refusal_delta_event.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseRefusalDeltaEvent"] + + +class ResponseRefusalDeltaEvent(BaseModel): + """Emitted when there is a partial refusal text.""" + + content_index: int + """The index of the content part that the refusal text is added to.""" + + delta: str + """The refusal text that is added.""" + + item_id: str + """The ID of the output item that the refusal text is added to.""" + + output_index: int + """The index of the output item that the refusal text is added to.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.refusal.delta"] + """The type of the event. Always `response.refusal.delta`.""" diff --git a/src/openai/types/responses/response_refusal_done_event.py b/src/openai/types/responses/response_refusal_done_event.py new file mode 100644 index 0000000000..91adeb6331 --- /dev/null +++ b/src/openai/types/responses/response_refusal_done_event.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseRefusalDoneEvent"] + + +class ResponseRefusalDoneEvent(BaseModel): + """Emitted when refusal text is finalized.""" + + content_index: int + """The index of the content part that the refusal text is finalized.""" + + item_id: str + """The ID of the output item that the refusal text is finalized.""" + + output_index: int + """The index of the output item that the refusal text is finalized.""" + + refusal: str + """The refusal text that is finalized.""" + + sequence_number: int + """The sequence number of this event.""" + + type: Literal["response.refusal.done"] + """The type of the event. Always `response.refusal.done`.""" diff --git a/src/openai/types/responses/response_retrieve_params.py b/src/openai/types/responses/response_retrieve_params.py new file mode 100644 index 0000000000..4013db85ce --- /dev/null +++ b/src/openai/types/responses/response_retrieve_params.py @@ -0,0 +1,59 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union +from typing_extensions import Literal, Required, TypedDict + +from .response_includable import ResponseIncludable + +__all__ = ["ResponseRetrieveParamsBase", "ResponseRetrieveParamsNonStreaming", "ResponseRetrieveParamsStreaming"] + + +class ResponseRetrieveParamsBase(TypedDict, total=False): + include: List[ResponseIncludable] + """Additional fields to include in the response. + + See the `include` parameter for Response creation above for more information. + """ + + include_obfuscation: bool + """When true, stream obfuscation will be enabled. + + Stream obfuscation adds random characters to an `obfuscation` field on streaming + delta events to normalize payload sizes as a mitigation to certain side-channel + attacks. These obfuscation fields are included by default, but add a small + amount of overhead to the data stream. You can set `include_obfuscation` to + false to optimize for bandwidth if you trust the network links between your + application and the OpenAI API. + """ + + starting_after: int + """The sequence number of the event after which to start streaming.""" + + +class ResponseRetrieveParamsNonStreaming(ResponseRetrieveParamsBase, total=False): + stream: Literal[False] + """ + If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + """ + + +class ResponseRetrieveParamsStreaming(ResponseRetrieveParamsBase): + stream: Required[Literal[True]] + """ + If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + """ + + +ResponseRetrieveParams = Union[ResponseRetrieveParamsNonStreaming, ResponseRetrieveParamsStreaming] diff --git a/src/openai/types/responses/response_status.py b/src/openai/types/responses/response_status.py new file mode 100644 index 0000000000..a7887b92d2 --- /dev/null +++ b/src/openai/types/responses/response_status.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ResponseStatus"] + +ResponseStatus: TypeAlias = Literal["completed", "failed", "in_progress", "cancelled", "queued", "incomplete"] diff --git a/src/openai/types/responses/response_stream_event.py b/src/openai/types/responses/response_stream_event.py new file mode 100644 index 0000000000..c0a317cd9d --- /dev/null +++ b/src/openai/types/responses/response_stream_event.py @@ -0,0 +1,120 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from .response_error_event import ResponseErrorEvent +from .response_failed_event import ResponseFailedEvent +from .response_queued_event import ResponseQueuedEvent +from .response_created_event import ResponseCreatedEvent +from .response_completed_event import ResponseCompletedEvent +from .response_text_done_event import ResponseTextDoneEvent +from .response_audio_done_event import ResponseAudioDoneEvent +from .response_incomplete_event import ResponseIncompleteEvent +from .response_text_delta_event import ResponseTextDeltaEvent +from .response_audio_delta_event import ResponseAudioDeltaEvent +from .response_in_progress_event import ResponseInProgressEvent +from .response_refusal_done_event import ResponseRefusalDoneEvent +from .response_refusal_delta_event import ResponseRefusalDeltaEvent +from .response_mcp_call_failed_event import ResponseMcpCallFailedEvent +from .response_output_item_done_event import ResponseOutputItemDoneEvent +from .response_content_part_done_event import ResponseContentPartDoneEvent +from .response_output_item_added_event import ResponseOutputItemAddedEvent +from .response_content_part_added_event import ResponseContentPartAddedEvent +from .response_mcp_call_completed_event import ResponseMcpCallCompletedEvent +from .response_reasoning_text_done_event import ResponseReasoningTextDoneEvent +from .response_mcp_call_in_progress_event import ResponseMcpCallInProgressEvent +from .response_reasoning_text_delta_event import ResponseReasoningTextDeltaEvent +from .response_audio_transcript_done_event import ResponseAudioTranscriptDoneEvent +from .response_mcp_list_tools_failed_event import ResponseMcpListToolsFailedEvent +from .response_audio_transcript_delta_event import ResponseAudioTranscriptDeltaEvent +from .response_mcp_call_arguments_done_event import ResponseMcpCallArgumentsDoneEvent +from .response_image_gen_call_completed_event import ResponseImageGenCallCompletedEvent +from .response_mcp_call_arguments_delta_event import ResponseMcpCallArgumentsDeltaEvent +from .response_mcp_list_tools_completed_event import ResponseMcpListToolsCompletedEvent +from .response_image_gen_call_generating_event import ResponseImageGenCallGeneratingEvent +from .response_web_search_call_completed_event import ResponseWebSearchCallCompletedEvent +from .response_web_search_call_searching_event import ResponseWebSearchCallSearchingEvent +from .response_file_search_call_completed_event import ResponseFileSearchCallCompletedEvent +from .response_file_search_call_searching_event import ResponseFileSearchCallSearchingEvent +from .response_image_gen_call_in_progress_event import ResponseImageGenCallInProgressEvent +from .response_mcp_list_tools_in_progress_event import ResponseMcpListToolsInProgressEvent +from .response_custom_tool_call_input_done_event import ResponseCustomToolCallInputDoneEvent +from .response_reasoning_summary_part_done_event import ResponseReasoningSummaryPartDoneEvent +from .response_reasoning_summary_text_done_event import ResponseReasoningSummaryTextDoneEvent +from .response_web_search_call_in_progress_event import ResponseWebSearchCallInProgressEvent +from .response_custom_tool_call_input_delta_event import ResponseCustomToolCallInputDeltaEvent +from .response_file_search_call_in_progress_event import ResponseFileSearchCallInProgressEvent +from .response_function_call_arguments_done_event import ResponseFunctionCallArgumentsDoneEvent +from .response_image_gen_call_partial_image_event import ResponseImageGenCallPartialImageEvent +from .response_output_text_annotation_added_event import ResponseOutputTextAnnotationAddedEvent +from .response_reasoning_summary_part_added_event import ResponseReasoningSummaryPartAddedEvent +from .response_reasoning_summary_text_delta_event import ResponseReasoningSummaryTextDeltaEvent +from .response_function_call_arguments_delta_event import ResponseFunctionCallArgumentsDeltaEvent +from .response_code_interpreter_call_code_done_event import ResponseCodeInterpreterCallCodeDoneEvent +from .response_code_interpreter_call_completed_event import ResponseCodeInterpreterCallCompletedEvent +from .response_code_interpreter_call_code_delta_event import ResponseCodeInterpreterCallCodeDeltaEvent +from .response_code_interpreter_call_in_progress_event import ResponseCodeInterpreterCallInProgressEvent +from .response_code_interpreter_call_interpreting_event import ResponseCodeInterpreterCallInterpretingEvent + +__all__ = ["ResponseStreamEvent"] + +ResponseStreamEvent: TypeAlias = Annotated[ + Union[ + ResponseAudioDeltaEvent, + ResponseAudioDoneEvent, + ResponseAudioTranscriptDeltaEvent, + ResponseAudioTranscriptDoneEvent, + ResponseCodeInterpreterCallCodeDeltaEvent, + ResponseCodeInterpreterCallCodeDoneEvent, + ResponseCodeInterpreterCallCompletedEvent, + ResponseCodeInterpreterCallInProgressEvent, + ResponseCodeInterpreterCallInterpretingEvent, + ResponseCompletedEvent, + ResponseContentPartAddedEvent, + ResponseContentPartDoneEvent, + ResponseCreatedEvent, + ResponseErrorEvent, + ResponseFileSearchCallCompletedEvent, + ResponseFileSearchCallInProgressEvent, + ResponseFileSearchCallSearchingEvent, + ResponseFunctionCallArgumentsDeltaEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseInProgressEvent, + ResponseFailedEvent, + ResponseIncompleteEvent, + ResponseOutputItemAddedEvent, + ResponseOutputItemDoneEvent, + ResponseReasoningSummaryPartAddedEvent, + ResponseReasoningSummaryPartDoneEvent, + ResponseReasoningSummaryTextDeltaEvent, + ResponseReasoningSummaryTextDoneEvent, + ResponseReasoningTextDeltaEvent, + ResponseReasoningTextDoneEvent, + ResponseRefusalDeltaEvent, + ResponseRefusalDoneEvent, + ResponseTextDeltaEvent, + ResponseTextDoneEvent, + ResponseWebSearchCallCompletedEvent, + ResponseWebSearchCallInProgressEvent, + ResponseWebSearchCallSearchingEvent, + ResponseImageGenCallCompletedEvent, + ResponseImageGenCallGeneratingEvent, + ResponseImageGenCallInProgressEvent, + ResponseImageGenCallPartialImageEvent, + ResponseMcpCallArgumentsDeltaEvent, + ResponseMcpCallArgumentsDoneEvent, + ResponseMcpCallCompletedEvent, + ResponseMcpCallFailedEvent, + ResponseMcpCallInProgressEvent, + ResponseMcpListToolsCompletedEvent, + ResponseMcpListToolsFailedEvent, + ResponseMcpListToolsInProgressEvent, + ResponseOutputTextAnnotationAddedEvent, + ResponseQueuedEvent, + ResponseCustomToolCallInputDeltaEvent, + ResponseCustomToolCallInputDoneEvent, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/responses/response_text_config.py b/src/openai/types/responses/response_text_config.py new file mode 100644 index 0000000000..fbf4da0b03 --- /dev/null +++ b/src/openai/types/responses/response_text_config.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .response_format_text_config import ResponseFormatTextConfig + +__all__ = ["ResponseTextConfig"] + + +class ResponseTextConfig(BaseModel): + """Configuration options for a text response from the model. + + Can be plain + text or structured JSON data. Learn more: + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + format: Optional[ResponseFormatTextConfig] = None + """An object specifying the format that the model must output. + + Configuring `{ "type": "json_schema" }` enables Structured Outputs, which + ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + The default format is `{ "type": "text" }` with no additional options. + + **Not recommended for gpt-4o and newer models:** + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ + + verbosity: Optional[Literal["low", "medium", "high"]] = None + """Constrains the verbosity of the model's response. + + Lower values will result in more concise responses, while higher values will + result in more verbose responses. Currently supported values are `low`, + `medium`, and `high`. + """ diff --git a/src/openai/types/responses/response_text_config_param.py b/src/openai/types/responses/response_text_config_param.py new file mode 100644 index 0000000000..9cd54765b0 --- /dev/null +++ b/src/openai/types/responses/response_text_config_param.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +from .response_format_text_config_param import ResponseFormatTextConfigParam + +__all__ = ["ResponseTextConfigParam"] + + +class ResponseTextConfigParam(TypedDict, total=False): + """Configuration options for a text response from the model. + + Can be plain + text or structured JSON data. Learn more: + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + format: ResponseFormatTextConfigParam + """An object specifying the format that the model must output. + + Configuring `{ "type": "json_schema" }` enables Structured Outputs, which + ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + + The default format is `{ "type": "text" }` with no additional options. + + **Not recommended for gpt-4o and newer models:** + + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. + """ + + verbosity: Optional[Literal["low", "medium", "high"]] + """Constrains the verbosity of the model's response. + + Lower values will result in more concise responses, while higher values will + result in more verbose responses. Currently supported values are `low`, + `medium`, and `high`. + """ diff --git a/src/openai/types/responses/response_text_delta_event.py b/src/openai/types/responses/response_text_delta_event.py new file mode 100644 index 0000000000..9b0b83de59 --- /dev/null +++ b/src/openai/types/responses/response_text_delta_event.py @@ -0,0 +1,58 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseTextDeltaEvent", "Logprob", "LogprobTopLogprob"] + + +class LogprobTopLogprob(BaseModel): + token: Optional[str] = None + """A possible text token.""" + + logprob: Optional[float] = None + """The log probability of this token.""" + + +class Logprob(BaseModel): + """ + A logprob is the logarithmic probability that the model assigns to producing + a particular token at a given position in the sequence. Less-negative (higher) + logprob values indicate greater model confidence in that token choice. + """ + + token: str + """A possible text token.""" + + logprob: float + """The log probability of this token.""" + + top_logprobs: Optional[List[LogprobTopLogprob]] = None + """The log probabilities of up to 20 of the most likely tokens.""" + + +class ResponseTextDeltaEvent(BaseModel): + """Emitted when there is an additional text delta.""" + + content_index: int + """The index of the content part that the text delta was added to.""" + + delta: str + """The text delta that was added.""" + + item_id: str + """The ID of the output item that the text delta was added to.""" + + logprobs: List[Logprob] + """The log probabilities of the tokens in the delta.""" + + output_index: int + """The index of the output item that the text delta was added to.""" + + sequence_number: int + """The sequence number for this event.""" + + type: Literal["response.output_text.delta"] + """The type of the event. Always `response.output_text.delta`.""" diff --git a/src/openai/types/responses/response_text_done_event.py b/src/openai/types/responses/response_text_done_event.py new file mode 100644 index 0000000000..3a202af67a --- /dev/null +++ b/src/openai/types/responses/response_text_done_event.py @@ -0,0 +1,58 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseTextDoneEvent", "Logprob", "LogprobTopLogprob"] + + +class LogprobTopLogprob(BaseModel): + token: Optional[str] = None + """A possible text token.""" + + logprob: Optional[float] = None + """The log probability of this token.""" + + +class Logprob(BaseModel): + """ + A logprob is the logarithmic probability that the model assigns to producing + a particular token at a given position in the sequence. Less-negative (higher) + logprob values indicate greater model confidence in that token choice. + """ + + token: str + """A possible text token.""" + + logprob: float + """The log probability of this token.""" + + top_logprobs: Optional[List[LogprobTopLogprob]] = None + """The log probabilities of up to 20 of the most likely tokens.""" + + +class ResponseTextDoneEvent(BaseModel): + """Emitted when text content is finalized.""" + + content_index: int + """The index of the content part that the text content is finalized.""" + + item_id: str + """The ID of the output item that the text content is finalized.""" + + logprobs: List[Logprob] + """The log probabilities of the tokens in the delta.""" + + output_index: int + """The index of the output item that the text content is finalized.""" + + sequence_number: int + """The sequence number for this event.""" + + text: str + """The text content that is finalized.""" + + type: Literal["response.output_text.done"] + """The type of the event. Always `response.output_text.done`.""" diff --git a/src/openai/types/responses/response_tool_search_call.py b/src/openai/types/responses/response_tool_search_call.py new file mode 100644 index 0000000000..495bf17119 --- /dev/null +++ b/src/openai/types/responses/response_tool_search_call.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseToolSearchCall"] + + +class ResponseToolSearchCall(BaseModel): + id: str + """The unique ID of the tool search call item.""" + + arguments: object + """Arguments used for the tool search call.""" + + call_id: Optional[str] = None + """The unique ID of the tool search call generated by the model.""" + + execution: Literal["server", "client"] + """Whether tool search was executed by the server or by the client.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the tool search call item that was recorded.""" + + type: Literal["tool_search_call"] + """The type of the item. Always `tool_search_call`.""" + + created_by: Optional[str] = None + """The identifier of the actor that created the item.""" diff --git a/src/openai/types/responses/response_tool_search_output_item.py b/src/openai/types/responses/response_tool_search_output_item.py new file mode 100644 index 0000000000..f8911dd491 --- /dev/null +++ b/src/openai/types/responses/response_tool_search_output_item.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .tool import Tool +from ..._models import BaseModel + +__all__ = ["ResponseToolSearchOutputItem"] + + +class ResponseToolSearchOutputItem(BaseModel): + id: str + """The unique ID of the tool search output item.""" + + call_id: Optional[str] = None + """The unique ID of the tool search call generated by the model.""" + + execution: Literal["server", "client"] + """Whether tool search was executed by the server or by the client.""" + + status: Literal["in_progress", "completed", "incomplete"] + """The status of the tool search output item that was recorded.""" + + tools: List[Tool] + """The loaded tool definitions returned by tool search.""" + + type: Literal["tool_search_output"] + """The type of the item. Always `tool_search_output`.""" + + created_by: Optional[str] = None + """The identifier of the actor that created the item.""" diff --git a/src/openai/types/responses/response_tool_search_output_item_param.py b/src/openai/types/responses/response_tool_search_output_item_param.py new file mode 100644 index 0000000000..f4d4ef34c6 --- /dev/null +++ b/src/openai/types/responses/response_tool_search_output_item_param.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .tool import Tool +from ..._models import BaseModel + +__all__ = ["ResponseToolSearchOutputItemParam"] + + +class ResponseToolSearchOutputItemParam(BaseModel): + tools: List[Tool] + """The loaded tool definitions returned by the tool search output.""" + + type: Literal["tool_search_output"] + """The item type. Always `tool_search_output`.""" + + id: Optional[str] = None + """The unique ID of this tool search output.""" + + call_id: Optional[str] = None + """The unique ID of the tool search call generated by the model.""" + + execution: Optional[Literal["server", "client"]] = None + """Whether tool search was executed by the server or by the client.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] = None + """The status of the tool search output.""" diff --git a/src/openai/types/responses/response_tool_search_output_item_param_param.py b/src/openai/types/responses/response_tool_search_output_item_param_param.py new file mode 100644 index 0000000000..28e9b1e186 --- /dev/null +++ b/src/openai/types/responses/response_tool_search_output_item_param_param.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .tool_param import ToolParam + +__all__ = ["ResponseToolSearchOutputItemParamParam"] + + +class ResponseToolSearchOutputItemParamParam(TypedDict, total=False): + tools: Required[Iterable[ToolParam]] + """The loaded tool definitions returned by the tool search output.""" + + type: Required[Literal["tool_search_output"]] + """The item type. Always `tool_search_output`.""" + + id: Optional[str] + """The unique ID of this tool search output.""" + + call_id: Optional[str] + """The unique ID of the tool search call generated by the model.""" + + execution: Literal["server", "client"] + """Whether tool search was executed by the server or by the client.""" + + status: Optional[Literal["in_progress", "completed", "incomplete"]] + """The status of the tool search output.""" diff --git a/src/openai/types/responses/response_usage.py b/src/openai/types/responses/response_usage.py new file mode 100644 index 0000000000..d4b739c598 --- /dev/null +++ b/src/openai/types/responses/response_usage.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["ResponseUsage", "InputTokensDetails", "OutputTokensDetails"] + + +class InputTokensDetails(BaseModel): + """A detailed breakdown of the input tokens.""" + + cached_tokens: int + """The number of tokens that were retrieved from the cache. + + [More on prompt caching](https://platform.openai.com/docs/guides/prompt-caching). + """ + + +class OutputTokensDetails(BaseModel): + """A detailed breakdown of the output tokens.""" + + reasoning_tokens: int + """The number of reasoning tokens.""" + + +class ResponseUsage(BaseModel): + """ + Represents token usage details including input tokens, output tokens, + a breakdown of output tokens, and the total tokens used. + """ + + input_tokens: int + """The number of input tokens.""" + + input_tokens_details: InputTokensDetails + """A detailed breakdown of the input tokens.""" + + output_tokens: int + """The number of output tokens.""" + + output_tokens_details: OutputTokensDetails + """A detailed breakdown of the output tokens.""" + + total_tokens: int + """The total number of tokens used.""" diff --git a/src/openai/types/responses/response_web_search_call_completed_event.py b/src/openai/types/responses/response_web_search_call_completed_event.py new file mode 100644 index 0000000000..5aa7afe609 --- /dev/null +++ b/src/openai/types/responses/response_web_search_call_completed_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseWebSearchCallCompletedEvent"] + + +class ResponseWebSearchCallCompletedEvent(BaseModel): + """Emitted when a web search call is completed.""" + + item_id: str + """Unique ID for the output item associated with the web search call.""" + + output_index: int + """The index of the output item that the web search call is associated with.""" + + sequence_number: int + """The sequence number of the web search call being processed.""" + + type: Literal["response.web_search_call.completed"] + """The type of the event. Always `response.web_search_call.completed`.""" diff --git a/src/openai/types/responses/response_web_search_call_in_progress_event.py b/src/openai/types/responses/response_web_search_call_in_progress_event.py new file mode 100644 index 0000000000..73b30ff5c0 --- /dev/null +++ b/src/openai/types/responses/response_web_search_call_in_progress_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseWebSearchCallInProgressEvent"] + + +class ResponseWebSearchCallInProgressEvent(BaseModel): + """Emitted when a web search call is initiated.""" + + item_id: str + """Unique ID for the output item associated with the web search call.""" + + output_index: int + """The index of the output item that the web search call is associated with.""" + + sequence_number: int + """The sequence number of the web search call being processed.""" + + type: Literal["response.web_search_call.in_progress"] + """The type of the event. Always `response.web_search_call.in_progress`.""" diff --git a/src/openai/types/responses/response_web_search_call_searching_event.py b/src/openai/types/responses/response_web_search_call_searching_event.py new file mode 100644 index 0000000000..959c095187 --- /dev/null +++ b/src/openai/types/responses/response_web_search_call_searching_event.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseWebSearchCallSearchingEvent"] + + +class ResponseWebSearchCallSearchingEvent(BaseModel): + """Emitted when a web search call is executing.""" + + item_id: str + """Unique ID for the output item associated with the web search call.""" + + output_index: int + """The index of the output item that the web search call is associated with.""" + + sequence_number: int + """The sequence number of the web search call being processed.""" + + type: Literal["response.web_search_call.searching"] + """The type of the event. Always `response.web_search_call.searching`.""" diff --git a/src/openai/types/responses/responses_client_event.py b/src/openai/types/responses/responses_client_event.py new file mode 100644 index 0000000000..df33c019f1 --- /dev/null +++ b/src/openai/types/responses/responses_client_event.py @@ -0,0 +1,348 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from .tool import Tool +from ..._models import BaseModel +from .response_input import ResponseInput +from .response_prompt import ResponsePrompt +from .tool_choice_mcp import ToolChoiceMcp +from ..shared.metadata import Metadata +from ..shared.reasoning import Reasoning +from .tool_choice_shell import ToolChoiceShell +from .tool_choice_types import ToolChoiceTypes +from .tool_choice_custom import ToolChoiceCustom +from .response_includable import ResponseIncludable +from .tool_choice_allowed import ToolChoiceAllowed +from .tool_choice_options import ToolChoiceOptions +from .response_text_config import ResponseTextConfig +from .tool_choice_function import ToolChoiceFunction +from ..shared.responses_model import ResponsesModel +from .tool_choice_apply_patch import ToolChoiceApplyPatch +from .response_conversation_param import ResponseConversationParam + +__all__ = ["ResponsesClientEvent", "ContextManagement", "Conversation", "Moderation", "StreamOptions", "ToolChoice"] + + +class ContextManagement(BaseModel): + type: str + """The context management entry type. Currently only 'compaction' is supported.""" + + compact_threshold: Optional[int] = None + """Token threshold at which compaction should be triggered for this entry.""" + + +Conversation: TypeAlias = Union[str, ResponseConversationParam, None] + + +class Moderation(BaseModel): + """Configuration for running moderation on the input and output of this response.""" + + model: str + """The moderation model to use for moderated completions, e.g. + + 'omni-moderation-latest'. + """ + + +class StreamOptions(BaseModel): + """Options for streaming responses. Only set this when you set `stream: true`.""" + + include_obfuscation: Optional[bool] = None + """When true, stream obfuscation will be enabled. + + Stream obfuscation adds random characters to an `obfuscation` field on streaming + delta events to normalize payload sizes as a mitigation to certain side-channel + attacks. These obfuscation fields are included by default, but add a small + amount of overhead to the data stream. You can set `include_obfuscation` to + false to optimize for bandwidth if you trust the network links between your + application and the OpenAI API. + """ + + +ToolChoice: TypeAlias = Union[ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMcp, + ToolChoiceCustom, + ToolChoiceApplyPatch, + ToolChoiceShell, +] + + +class ResponsesClientEvent(BaseModel): + type: Literal["response.create"] + """The type of the client event. Always `response.create`.""" + + background: Optional[bool] = None + """ + Whether to run the model response in the background. + [Learn more](https://platform.openai.com/docs/guides/background). + """ + + context_management: Optional[List[ContextManagement]] = None + """Context management configuration for this request.""" + + conversation: Optional[Conversation] = None + """The conversation that this response belongs to. + + Items from this conversation are prepended to `input_items` for this response + request. Input items and output items from this response are automatically added + to this conversation after this response completes. + """ + + include: Optional[List[ResponseIncludable]] = None + """Specify additional output data to include in the model response. + + Currently supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + """ + + input: Union[str, ResponseInput, None] = None + """Text, image, or file inputs to the model, used to generate a response. + + Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Image inputs](https://platform.openai.com/docs/guides/images) + - [File inputs](https://platform.openai.com/docs/guides/pdf-files) + - [Conversation state](https://platform.openai.com/docs/guides/conversation-state) + - [Function calling](https://platform.openai.com/docs/guides/function-calling) + """ + + instructions: Optional[str] = None + """A system (or developer) message inserted into the model's context. + + When using along with `previous_response_id`, the instructions from a previous + response will not be carried over to the next response. This makes it simple to + swap out system (or developer) messages in new responses. + """ + + max_output_tokens: Optional[int] = None + """ + An upper bound for the number of tokens that can be generated for a response, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + """ + + max_tool_calls: Optional[int] = None + """ + The maximum number of total calls to built-in tools that can be processed in a + response. This maximum number applies across all built-in tool calls, not per + individual tool. Any further attempts to call a tool by the model will be + ignored. + """ + + metadata: Optional[Metadata] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: Optional[ResponsesModel] = None + """Model ID used to generate the response, like `gpt-4o` or `o3`. + + OpenAI offers a wide range of models with different capabilities, performance + characteristics, and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + """ + + moderation: Optional[Moderation] = None + """Configuration for running moderation on the input and output of this response.""" + + parallel_tool_calls: Optional[bool] = None + """Whether to allow the model to run tool calls in parallel.""" + + previous_response_id: Optional[str] = None + """The unique ID of the previous response to the model. + + Use this to create multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + """ + + prompt: Optional[ResponsePrompt] = None + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + prompt_cache_key: Optional[str] = None + """ + Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + """ + + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] = None + """The retention policy for the prompt cache. + + Set to `24h` to enable extended prompt caching, which keeps cached prefixes + active for longer, up to a maximum of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + """ + + reasoning: Optional[Reasoning] = None + """**gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + safety_identifier: Optional[str] = None + """ + A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + """ + + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] = None + """Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + """ + + store: Optional[bool] = None + """Whether to store the generated model response for later retrieval via API.""" + + stream: Optional[bool] = None + """ + If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + """ + + stream_options: Optional[StreamOptions] = None + """Options for streaming responses. Only set this when you set `stream: true`.""" + + temperature: Optional[float] = None + """What sampling temperature to use, between 0 and 2. + + Higher values like 0.8 will make the output more random, while lower values like + 0.2 will make it more focused and deterministic. We generally recommend altering + this or `top_p` but not both. + """ + + text: Optional[ResponseTextConfig] = None + """Configuration options for a text response from the model. + + Can be plain text or structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + tool_choice: Optional[ToolChoice] = None + """ + How the model should select which tool (or tools) to use when generating a + response. See the `tools` parameter to see how to specify which tools the model + can call. + """ + + tools: Optional[List[Tool]] = None + """An array of tools the model may call while generating a response. + + You can specify which tool to use by setting the `tool_choice` parameter. + + We support the following categories of tools: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **MCP Tools**: Integrations with third-party systems via custom MCP servers or + predefined connectors such as Google Drive and SharePoint. Learn more about + [MCP Tools](https://platform.openai.com/docs/guides/tools-connectors-mcp). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code with strongly typed arguments and outputs. + Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + You can also use custom tools to call your own code. + """ + + top_logprobs: Optional[int] = None + """ + An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + """ + + top_p: Optional[float] = None + """ + An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + """ + + truncation: Optional[Literal["auto", "disabled"]] = None + """The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. + - `disabled` (default): If the input size will exceed the context window size + for a model, the request will fail with a 400 error. + """ + + user: Optional[str] = None + """This field is being replaced by `safety_identifier` and `prompt_cache_key`. + + Use `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + """ diff --git a/src/openai/types/responses/responses_client_event_param.py b/src/openai/types/responses/responses_client_event_param.py new file mode 100644 index 0000000000..55aeafc33d --- /dev/null +++ b/src/openai/types/responses/responses_client_event_param.py @@ -0,0 +1,356 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .tool_param import ToolParam +from .response_includable import ResponseIncludable +from .tool_choice_options import ToolChoiceOptions +from .response_input_param import ResponseInputParam +from .response_prompt_param import ResponsePromptParam +from .tool_choice_mcp_param import ToolChoiceMcpParam +from ..shared_params.metadata import Metadata +from .tool_choice_shell_param import ToolChoiceShellParam +from .tool_choice_types_param import ToolChoiceTypesParam +from ..shared_params.reasoning import Reasoning +from .tool_choice_custom_param import ToolChoiceCustomParam +from .tool_choice_allowed_param import ToolChoiceAllowedParam +from .response_text_config_param import ResponseTextConfigParam +from .tool_choice_function_param import ToolChoiceFunctionParam +from .tool_choice_apply_patch_param import ToolChoiceApplyPatchParam +from ..shared_params.responses_model import ResponsesModel +from .response_conversation_param_param import ResponseConversationParamParam + +__all__ = [ + "ResponsesClientEventParam", + "ContextManagement", + "Conversation", + "Moderation", + "StreamOptions", + "ToolChoice", +] + + +class ContextManagement(TypedDict, total=False): + type: Required[str] + """The context management entry type. Currently only 'compaction' is supported.""" + + compact_threshold: Optional[int] + """Token threshold at which compaction should be triggered for this entry.""" + + +Conversation: TypeAlias = Union[str, ResponseConversationParamParam] + + +class Moderation(TypedDict, total=False): + """Configuration for running moderation on the input and output of this response.""" + + model: Required[str] + """The moderation model to use for moderated completions, e.g. + + 'omni-moderation-latest'. + """ + + +class StreamOptions(TypedDict, total=False): + """Options for streaming responses. Only set this when you set `stream: true`.""" + + include_obfuscation: bool + """When true, stream obfuscation will be enabled. + + Stream obfuscation adds random characters to an `obfuscation` field on streaming + delta events to normalize payload sizes as a mitigation to certain side-channel + attacks. These obfuscation fields are included by default, but add a small + amount of overhead to the data stream. You can set `include_obfuscation` to + false to optimize for bandwidth if you trust the network links between your + application and the OpenAI API. + """ + + +ToolChoice: TypeAlias = Union[ + ToolChoiceOptions, + ToolChoiceAllowedParam, + ToolChoiceTypesParam, + ToolChoiceFunctionParam, + ToolChoiceMcpParam, + ToolChoiceCustomParam, + ToolChoiceApplyPatchParam, + ToolChoiceShellParam, +] + + +class ResponsesClientEventParam(TypedDict, total=False): + type: Required[Literal["response.create"]] + """The type of the client event. Always `response.create`.""" + + background: Optional[bool] + """ + Whether to run the model response in the background. + [Learn more](https://platform.openai.com/docs/guides/background). + """ + + context_management: Optional[Iterable[ContextManagement]] + """Context management configuration for this request.""" + + conversation: Optional[Conversation] + """The conversation that this response belongs to. + + Items from this conversation are prepended to `input_items` for this response + request. Input items and output items from this response are automatically added + to this conversation after this response completes. + """ + + include: Optional[List[ResponseIncludable]] + """Specify additional output data to include in the model response. + + Currently supported values are: + + - `web_search_call.action.sources`: Include the sources of the web search tool + call. + - `code_interpreter_call.outputs`: Includes the outputs of python code execution + in code interpreter tool call items. + - `computer_call_output.output.image_url`: Include image urls from the computer + call output. + - `file_search_call.results`: Include the search results of the file search tool + call. + - `message.input_image.image_url`: Include image urls from the input message. + - `message.output_text.logprobs`: Include logprobs with assistant messages. + - `reasoning.encrypted_content`: Includes an encrypted version of reasoning + tokens in reasoning item outputs. This enables reasoning items to be used in + multi-turn conversations when using the Responses API statelessly (like when + the `store` parameter is set to `false`, or when an organization is enrolled + in the zero data retention program). + """ + + input: Union[str, ResponseInputParam] + """Text, image, or file inputs to the model, used to generate a response. + + Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Image inputs](https://platform.openai.com/docs/guides/images) + - [File inputs](https://platform.openai.com/docs/guides/pdf-files) + - [Conversation state](https://platform.openai.com/docs/guides/conversation-state) + - [Function calling](https://platform.openai.com/docs/guides/function-calling) + """ + + instructions: Optional[str] + """A system (or developer) message inserted into the model's context. + + When using along with `previous_response_id`, the instructions from a previous + response will not be carried over to the next response. This makes it simple to + swap out system (or developer) messages in new responses. + """ + + max_output_tokens: Optional[int] + """ + An upper bound for the number of tokens that can be generated for a response, + including visible output tokens and + [reasoning tokens](https://platform.openai.com/docs/guides/reasoning). + """ + + max_tool_calls: Optional[int] + """ + The maximum number of total calls to built-in tools that can be processed in a + response. This maximum number applies across all built-in tool calls, not per + individual tool. Any further attempts to call a tool by the model will be + ignored. + """ + + metadata: Optional[Metadata] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. + """ + + model: ResponsesModel + """Model ID used to generate the response, like `gpt-4o` or `o3`. + + OpenAI offers a wide range of models with different capabilities, performance + characteristics, and price points. Refer to the + [model guide](https://platform.openai.com/docs/models) to browse and compare + available models. + """ + + moderation: Optional[Moderation] + """Configuration for running moderation on the input and output of this response.""" + + parallel_tool_calls: Optional[bool] + """Whether to allow the model to run tool calls in parallel.""" + + previous_response_id: Optional[str] + """The unique ID of the previous response to the model. + + Use this to create multi-turn conversations. Learn more about + [conversation state](https://platform.openai.com/docs/guides/conversation-state). + Cannot be used in conjunction with `conversation`. + """ + + prompt: Optional[ResponsePromptParam] + """ + Reference to a prompt template and its variables. + [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + """ + + prompt_cache_key: str + """ + Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + """ + + prompt_cache_retention: Optional[Literal["in_memory", "24h"]] + """The retention policy for the prompt cache. + + Set to `24h` to enable extended prompt caching, which keeps cached prefixes + active for longer, up to a maximum of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + For `gpt-5.5`, `gpt-5.5-pro`, and future models, only `24h` is supported. + + For older models that support both `in_memory` and `24h`, the default depends on + your organization's data retention policy: + + - Organizations without ZDR enabled default to `24h`. + - Organizations with ZDR enabled default to `in_memory` when + `prompt_cache_retention` is not specified. + """ + + reasoning: Optional[Reasoning] + """**gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + safety_identifier: str + """ + A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user, with a maximum length of 64 characters. We recommend + hashing their username or email address, in order to avoid sending us any + identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + """ + + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] + """Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + """ + + store: Optional[bool] + """Whether to store the generated model response for later retrieval via API.""" + + stream: Optional[bool] + """ + If set to true, the model response data will be streamed to the client as it is + generated using + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + See the + [Streaming section below](https://platform.openai.com/docs/api-reference/responses-streaming) + for more information. + """ + + stream_options: Optional[StreamOptions] + """Options for streaming responses. Only set this when you set `stream: true`.""" + + temperature: Optional[float] + """What sampling temperature to use, between 0 and 2. + + Higher values like 0.8 will make the output more random, while lower values like + 0.2 will make it more focused and deterministic. We generally recommend altering + this or `top_p` but not both. + """ + + text: ResponseTextConfigParam + """Configuration options for a text response from the model. + + Can be plain text or structured JSON data. Learn more: + + - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) + - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + """ + + tool_choice: ToolChoice + """ + How the model should select which tool (or tools) to use when generating a + response. See the `tools` parameter to see how to specify which tools the model + can call. + """ + + tools: Iterable[ToolParam] + """An array of tools the model may call while generating a response. + + You can specify which tool to use by setting the `tool_choice` parameter. + + We support the following categories of tools: + + - **Built-in tools**: Tools that are provided by OpenAI that extend the model's + capabilities, like + [web search](https://platform.openai.com/docs/guides/tools-web-search) or + [file search](https://platform.openai.com/docs/guides/tools-file-search). + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + - **MCP Tools**: Integrations with third-party systems via custom MCP servers or + predefined connectors such as Google Drive and SharePoint. Learn more about + [MCP Tools](https://platform.openai.com/docs/guides/tools-connectors-mcp). + - **Function calls (custom tools)**: Functions that are defined by you, enabling + the model to call your own code with strongly typed arguments and outputs. + Learn more about + [function calling](https://platform.openai.com/docs/guides/function-calling). + You can also use custom tools to call your own code. + """ + + top_logprobs: Optional[int] + """ + An integer between 0 and 20 specifying the maximum number of most likely tokens + to return at each token position, each with an associated log probability. In + some cases, the number of returned tokens may be fewer than requested. + """ + + top_p: Optional[float] + """ + An alternative to sampling with temperature, called nucleus sampling, where the + model considers the results of the tokens with top_p probability mass. So 0.1 + means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + """ + + truncation: Optional[Literal["auto", "disabled"]] + """The truncation strategy to use for the model response. + + - `auto`: If the input to this Response exceeds the model's context window size, + the model will truncate the response to fit the context window by dropping + items from the beginning of the conversation. + - `disabled` (default): If the input size will exceed the context window size + for a model, the request will fail with a 400 error. + """ + + user: str + """This field is being replaced by `safety_identifier` and `prompt_cache_key`. + + Use `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). + """ diff --git a/src/openai/types/responses/responses_server_event.py b/src/openai/types/responses/responses_server_event.py new file mode 100644 index 0000000000..c543587ad0 --- /dev/null +++ b/src/openai/types/responses/responses_server_event.py @@ -0,0 +1,120 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from .response_error_event import ResponseErrorEvent +from .response_failed_event import ResponseFailedEvent +from .response_queued_event import ResponseQueuedEvent +from .response_created_event import ResponseCreatedEvent +from .response_completed_event import ResponseCompletedEvent +from .response_text_done_event import ResponseTextDoneEvent +from .response_audio_done_event import ResponseAudioDoneEvent +from .response_incomplete_event import ResponseIncompleteEvent +from .response_text_delta_event import ResponseTextDeltaEvent +from .response_audio_delta_event import ResponseAudioDeltaEvent +from .response_in_progress_event import ResponseInProgressEvent +from .response_refusal_done_event import ResponseRefusalDoneEvent +from .response_refusal_delta_event import ResponseRefusalDeltaEvent +from .response_mcp_call_failed_event import ResponseMcpCallFailedEvent +from .response_output_item_done_event import ResponseOutputItemDoneEvent +from .response_content_part_done_event import ResponseContentPartDoneEvent +from .response_output_item_added_event import ResponseOutputItemAddedEvent +from .response_content_part_added_event import ResponseContentPartAddedEvent +from .response_mcp_call_completed_event import ResponseMcpCallCompletedEvent +from .response_reasoning_text_done_event import ResponseReasoningTextDoneEvent +from .response_mcp_call_in_progress_event import ResponseMcpCallInProgressEvent +from .response_reasoning_text_delta_event import ResponseReasoningTextDeltaEvent +from .response_audio_transcript_done_event import ResponseAudioTranscriptDoneEvent +from .response_mcp_list_tools_failed_event import ResponseMcpListToolsFailedEvent +from .response_audio_transcript_delta_event import ResponseAudioTranscriptDeltaEvent +from .response_mcp_call_arguments_done_event import ResponseMcpCallArgumentsDoneEvent +from .response_image_gen_call_completed_event import ResponseImageGenCallCompletedEvent +from .response_mcp_call_arguments_delta_event import ResponseMcpCallArgumentsDeltaEvent +from .response_mcp_list_tools_completed_event import ResponseMcpListToolsCompletedEvent +from .response_image_gen_call_generating_event import ResponseImageGenCallGeneratingEvent +from .response_web_search_call_completed_event import ResponseWebSearchCallCompletedEvent +from .response_web_search_call_searching_event import ResponseWebSearchCallSearchingEvent +from .response_file_search_call_completed_event import ResponseFileSearchCallCompletedEvent +from .response_file_search_call_searching_event import ResponseFileSearchCallSearchingEvent +from .response_image_gen_call_in_progress_event import ResponseImageGenCallInProgressEvent +from .response_mcp_list_tools_in_progress_event import ResponseMcpListToolsInProgressEvent +from .response_custom_tool_call_input_done_event import ResponseCustomToolCallInputDoneEvent +from .response_reasoning_summary_part_done_event import ResponseReasoningSummaryPartDoneEvent +from .response_reasoning_summary_text_done_event import ResponseReasoningSummaryTextDoneEvent +from .response_web_search_call_in_progress_event import ResponseWebSearchCallInProgressEvent +from .response_custom_tool_call_input_delta_event import ResponseCustomToolCallInputDeltaEvent +from .response_file_search_call_in_progress_event import ResponseFileSearchCallInProgressEvent +from .response_function_call_arguments_done_event import ResponseFunctionCallArgumentsDoneEvent +from .response_image_gen_call_partial_image_event import ResponseImageGenCallPartialImageEvent +from .response_output_text_annotation_added_event import ResponseOutputTextAnnotationAddedEvent +from .response_reasoning_summary_part_added_event import ResponseReasoningSummaryPartAddedEvent +from .response_reasoning_summary_text_delta_event import ResponseReasoningSummaryTextDeltaEvent +from .response_function_call_arguments_delta_event import ResponseFunctionCallArgumentsDeltaEvent +from .response_code_interpreter_call_code_done_event import ResponseCodeInterpreterCallCodeDoneEvent +from .response_code_interpreter_call_completed_event import ResponseCodeInterpreterCallCompletedEvent +from .response_code_interpreter_call_code_delta_event import ResponseCodeInterpreterCallCodeDeltaEvent +from .response_code_interpreter_call_in_progress_event import ResponseCodeInterpreterCallInProgressEvent +from .response_code_interpreter_call_interpreting_event import ResponseCodeInterpreterCallInterpretingEvent + +__all__ = ["ResponsesServerEvent"] + +ResponsesServerEvent: TypeAlias = Annotated[ + Union[ + ResponseAudioDeltaEvent, + ResponseAudioDoneEvent, + ResponseAudioTranscriptDeltaEvent, + ResponseAudioTranscriptDoneEvent, + ResponseCodeInterpreterCallCodeDeltaEvent, + ResponseCodeInterpreterCallCodeDoneEvent, + ResponseCodeInterpreterCallCompletedEvent, + ResponseCodeInterpreterCallInProgressEvent, + ResponseCodeInterpreterCallInterpretingEvent, + ResponseCompletedEvent, + ResponseContentPartAddedEvent, + ResponseContentPartDoneEvent, + ResponseCreatedEvent, + ResponseErrorEvent, + ResponseFileSearchCallCompletedEvent, + ResponseFileSearchCallInProgressEvent, + ResponseFileSearchCallSearchingEvent, + ResponseFunctionCallArgumentsDeltaEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseInProgressEvent, + ResponseFailedEvent, + ResponseIncompleteEvent, + ResponseOutputItemAddedEvent, + ResponseOutputItemDoneEvent, + ResponseReasoningSummaryPartAddedEvent, + ResponseReasoningSummaryPartDoneEvent, + ResponseReasoningSummaryTextDeltaEvent, + ResponseReasoningSummaryTextDoneEvent, + ResponseReasoningTextDeltaEvent, + ResponseReasoningTextDoneEvent, + ResponseRefusalDeltaEvent, + ResponseRefusalDoneEvent, + ResponseTextDeltaEvent, + ResponseTextDoneEvent, + ResponseWebSearchCallCompletedEvent, + ResponseWebSearchCallInProgressEvent, + ResponseWebSearchCallSearchingEvent, + ResponseImageGenCallCompletedEvent, + ResponseImageGenCallGeneratingEvent, + ResponseImageGenCallInProgressEvent, + ResponseImageGenCallPartialImageEvent, + ResponseMcpCallArgumentsDeltaEvent, + ResponseMcpCallArgumentsDoneEvent, + ResponseMcpCallCompletedEvent, + ResponseMcpCallFailedEvent, + ResponseMcpCallInProgressEvent, + ResponseMcpListToolsCompletedEvent, + ResponseMcpListToolsFailedEvent, + ResponseMcpListToolsInProgressEvent, + ResponseOutputTextAnnotationAddedEvent, + ResponseQueuedEvent, + ResponseCustomToolCallInputDeltaEvent, + ResponseCustomToolCallInputDoneEvent, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/responses/skill_reference.py b/src/openai/types/responses/skill_reference.py new file mode 100644 index 0000000000..76e614ab6c --- /dev/null +++ b/src/openai/types/responses/skill_reference.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["SkillReference"] + + +class SkillReference(BaseModel): + skill_id: str + """The ID of the referenced skill.""" + + type: Literal["skill_reference"] + """References a skill created with the /v1/skills endpoint.""" + + version: Optional[str] = None + """Optional skill version. Use a positive integer or 'latest'. Omit for default.""" diff --git a/src/openai/types/responses/skill_reference_param.py b/src/openai/types/responses/skill_reference_param.py new file mode 100644 index 0000000000..33b572854d --- /dev/null +++ b/src/openai/types/responses/skill_reference_param.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["SkillReferenceParam"] + + +class SkillReferenceParam(TypedDict, total=False): + skill_id: Required[str] + """The ID of the referenced skill.""" + + type: Required[Literal["skill_reference"]] + """References a skill created with the /v1/skills endpoint.""" + + version: str + """Optional skill version. Use a positive integer or 'latest'. Omit for default.""" diff --git a/src/openai/types/responses/tool.py b/src/openai/types/responses/tool.py new file mode 100644 index 0000000000..92e6fb29a7 --- /dev/null +++ b/src/openai/types/responses/tool.py @@ -0,0 +1,360 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from . import web_search_tool +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .custom_tool import CustomTool +from .computer_tool import ComputerTool +from .function_tool import FunctionTool +from .namespace_tool import NamespaceTool +from .web_search_tool import WebSearchTool +from .apply_patch_tool import ApplyPatchTool +from .file_search_tool import FileSearchTool +from .tool_search_tool import ToolSearchTool +from .function_shell_tool import FunctionShellTool +from .web_search_preview_tool import WebSearchPreviewTool +from .computer_use_preview_tool import ComputerUsePreviewTool +from .container_network_policy_disabled import ContainerNetworkPolicyDisabled +from .container_network_policy_allowlist import ContainerNetworkPolicyAllowlist + +__all__ = [ + "Tool", + "WebSearchTool", + "Mcp", + "McpAllowedTools", + "McpAllowedToolsMcpToolFilter", + "McpRequireApproval", + "McpRequireApprovalMcpToolApprovalFilter", + "McpRequireApprovalMcpToolApprovalFilterAlways", + "McpRequireApprovalMcpToolApprovalFilterNever", + "CodeInterpreter", + "CodeInterpreterContainer", + "CodeInterpreterContainerCodeInterpreterToolAuto", + "CodeInterpreterContainerCodeInterpreterToolAutoNetworkPolicy", + "ImageGeneration", + "ImageGenerationInputImageMask", + "LocalShell", +] + +WebSearchToolFilters = web_search_tool.Filters +WebSearchToolUserLocation = web_search_tool.UserLocation + + +class McpAllowedToolsMcpToolFilter(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +McpAllowedTools: TypeAlias = Union[List[str], McpAllowedToolsMcpToolFilter, None] + + +class McpRequireApprovalMcpToolApprovalFilterAlways(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +class McpRequireApprovalMcpToolApprovalFilterNever(BaseModel): + """A filter object to specify which tools are allowed.""" + + read_only: Optional[bool] = None + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: Optional[List[str]] = None + """List of allowed tool names.""" + + +class McpRequireApprovalMcpToolApprovalFilter(BaseModel): + """Specify which of the MCP server's tools require approval. + + Can be + `always`, `never`, or a filter object associated with tools + that require approval. + """ + + always: Optional[McpRequireApprovalMcpToolApprovalFilterAlways] = None + """A filter object to specify which tools are allowed.""" + + never: Optional[McpRequireApprovalMcpToolApprovalFilterNever] = None + """A filter object to specify which tools are allowed.""" + + +McpRequireApproval: TypeAlias = Union[McpRequireApprovalMcpToolApprovalFilter, Literal["always", "never"], None] + + +class Mcp(BaseModel): + """ + Give the model access to additional tools via remote Model Context Protocol + (MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp). + """ + + server_label: str + """A label for this MCP server, used to identify it in tool calls.""" + + type: Literal["mcp"] + """The type of the MCP tool. Always `mcp`.""" + + allowed_tools: Optional[McpAllowedTools] = None + """List of allowed tool names or a filter object.""" + + authorization: Optional[str] = None + """ + An OAuth access token that can be used with a remote MCP server, either with a + custom MCP server URL or a service connector. Your application must handle the + OAuth authorization flow and provide the token here. + """ + + connector_id: Optional[ + Literal[ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint", + ] + ] = None + """Identifier for service connectors, like those available in ChatGPT. + + One of `server_url` or `connector_id` must be provided. Learn more about service + connectors + [here](https://platform.openai.com/docs/guides/tools-remote-mcp#connectors). + + Currently supported `connector_id` values are: + + - Dropbox: `connector_dropbox` + - Gmail: `connector_gmail` + - Google Calendar: `connector_googlecalendar` + - Google Drive: `connector_googledrive` + - Microsoft Teams: `connector_microsoftteams` + - Outlook Calendar: `connector_outlookcalendar` + - Outlook Email: `connector_outlookemail` + - SharePoint: `connector_sharepoint` + """ + + defer_loading: Optional[bool] = None + """Whether this MCP tool is deferred and discovered via tool search.""" + + headers: Optional[Dict[str, str]] = None + """Optional HTTP headers to send to the MCP server. + + Use for authentication or other purposes. + """ + + require_approval: Optional[McpRequireApproval] = None + """Specify which of the MCP server's tools require approval.""" + + server_description: Optional[str] = None + """Optional description of the MCP server, used to provide more context.""" + + server_url: Optional[str] = None + """The URL for the MCP server. + + One of `server_url` or `connector_id` must be provided. + """ + + +CodeInterpreterContainerCodeInterpreterToolAutoNetworkPolicy: TypeAlias = Annotated[ + Union[ContainerNetworkPolicyDisabled, ContainerNetworkPolicyAllowlist], PropertyInfo(discriminator="type") +] + + +class CodeInterpreterContainerCodeInterpreterToolAuto(BaseModel): + """Configuration for a code interpreter container. + + Optionally specify the IDs of the files to run the code on. + """ + + type: Literal["auto"] + """Always `auto`.""" + + file_ids: Optional[List[str]] = None + """An optional list of uploaded files to make available to your code.""" + + memory_limit: Optional[Literal["1g", "4g", "16g", "64g"]] = None + """The memory limit for the code interpreter container.""" + + network_policy: Optional[CodeInterpreterContainerCodeInterpreterToolAutoNetworkPolicy] = None + """Network access policy for the container.""" + + +CodeInterpreterContainer: TypeAlias = Union[str, CodeInterpreterContainerCodeInterpreterToolAuto] + + +class CodeInterpreter(BaseModel): + """A tool that runs Python code to help generate a response to a prompt.""" + + container: CodeInterpreterContainer + """The code interpreter container. + + Can be a container ID or an object that specifies uploaded file IDs to make + available to your code, along with an optional `memory_limit` setting. + """ + + type: Literal["code_interpreter"] + """The type of the code interpreter tool. Always `code_interpreter`.""" + + +class ImageGenerationInputImageMask(BaseModel): + """Optional mask for inpainting. + + Contains `image_url` + (string, optional) and `file_id` (string, optional). + """ + + file_id: Optional[str] = None + """File ID for the mask image.""" + + image_url: Optional[str] = None + """Base64-encoded mask image.""" + + +class ImageGeneration(BaseModel): + """A tool that generates images using the GPT image models.""" + + type: Literal["image_generation"] + """The type of the image generation tool. Always `image_generation`.""" + + action: Optional[Literal["generate", "edit", "auto"]] = None + """Whether to generate a new image or edit an existing image. Default: `auto`.""" + + background: Optional[Literal["transparent", "opaque", "auto"]] = None + """ + Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + """ + + input_fidelity: Optional[Literal["high", "low"]] = None + """ + Control how much effort the model will exert to match the style and features, + especially facial features, of input images. This parameter is only supported + for `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for + `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`. + """ + + input_image_mask: Optional[ImageGenerationInputImageMask] = None + """Optional mask for inpainting. + + Contains `image_url` (string, optional) and `file_id` (string, optional). + """ + + model: Union[ + str, + Literal[ + "gpt-image-1", + "gpt-image-1-mini", + "gpt-image-2", + "gpt-image-2-2026-04-21", + "gpt-image-1.5", + "chatgpt-image-latest", + ], + None, + ] = None + """The image generation model to use. Default: `gpt-image-1`.""" + + moderation: Optional[Literal["auto", "low"]] = None + """Moderation level for the generated image. Default: `auto`.""" + + output_compression: Optional[int] = None + """Compression level for the output image. Default: 100.""" + + output_format: Optional[Literal["png", "webp", "jpeg"]] = None + """The output format of the generated image. + + One of `png`, `webp`, or `jpeg`. Default: `png`. + """ + + partial_images: Optional[int] = None + """ + Number of partial images to generate in streaming mode, from 0 (default value) + to 3. + """ + + quality: Optional[Literal["low", "medium", "high", "auto"]] = None + """The quality of the generated image. + + One of `low`, `medium`, `high`, or `auto`. Default: `auto`. + """ + + size: Union[str, Literal["1024x1024", "1024x1536", "1536x1024", "auto"], None] = None + """The size of the generated images. + + For `gpt-image-2` and `gpt-image-2-2026-04-21`, arbitrary resolutions are + supported as `WIDTHxHEIGHT` strings, for example `1536x864`. Width and height + must both be divisible by 16 and the requested aspect ratio must be between 1:3 + and 3:1. Resolutions above `2560x1440` are experimental, and the maximum + supported resolution is `3840x2160`. The requested size must also satisfy the + model's current pixel and edge limits. The standard sizes `1024x1024`, + `1536x1024`, and `1024x1536` are supported by the GPT image models; `auto` is + supported for models that allow automatic sizing. For `dall-e-2`, use one of + `256x256`, `512x512`, or `1024x1024`. For `dall-e-3`, use one of `1024x1024`, + `1792x1024`, or `1024x1792`. + """ + + +class LocalShell(BaseModel): + """A tool that allows the model to execute shell commands in a local environment.""" + + type: Literal["local_shell"] + """The type of the local shell tool. Always `local_shell`.""" + + +Tool: TypeAlias = Annotated[ + Union[ + FunctionTool, + FileSearchTool, + ComputerTool, + ComputerUsePreviewTool, + WebSearchTool, + Mcp, + CodeInterpreter, + ImageGeneration, + LocalShell, + FunctionShellTool, + CustomTool, + NamespaceTool, + ToolSearchTool, + WebSearchPreviewTool, + ApplyPatchTool, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/responses/tool_choice_allowed.py b/src/openai/types/responses/tool_choice_allowed.py new file mode 100644 index 0000000000..400e170a57 --- /dev/null +++ b/src/openai/types/responses/tool_choice_allowed.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ToolChoiceAllowed"] + + +class ToolChoiceAllowed(BaseModel): + """Constrains the tools available to the model to a pre-defined set.""" + + mode: Literal["auto", "required"] + """Constrains the tools available to the model to a pre-defined set. + + `auto` allows the model to pick from among the allowed tools and generate a + message. + + `required` requires the model to call one or more of the allowed tools. + """ + + tools: List[Dict[str, object]] + """A list of tool definitions that the model should be allowed to call. + + For the Responses API, the list of tool definitions might look like: + + ```json + [ + { "type": "function", "name": "get_weather" }, + { "type": "mcp", "server_label": "deepwiki" }, + { "type": "image_generation" } + ] + ``` + """ + + type: Literal["allowed_tools"] + """Allowed tool configuration type. Always `allowed_tools`.""" diff --git a/src/openai/types/responses/tool_choice_allowed_param.py b/src/openai/types/responses/tool_choice_allowed_param.py new file mode 100644 index 0000000000..cb316c1560 --- /dev/null +++ b/src/openai/types/responses/tool_choice_allowed_param.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Iterable +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceAllowedParam"] + + +class ToolChoiceAllowedParam(TypedDict, total=False): + """Constrains the tools available to the model to a pre-defined set.""" + + mode: Required[Literal["auto", "required"]] + """Constrains the tools available to the model to a pre-defined set. + + `auto` allows the model to pick from among the allowed tools and generate a + message. + + `required` requires the model to call one or more of the allowed tools. + """ + + tools: Required[Iterable[Dict[str, object]]] + """A list of tool definitions that the model should be allowed to call. + + For the Responses API, the list of tool definitions might look like: + + ```json + [ + { "type": "function", "name": "get_weather" }, + { "type": "mcp", "server_label": "deepwiki" }, + { "type": "image_generation" } + ] + ``` + """ + + type: Required[Literal["allowed_tools"]] + """Allowed tool configuration type. Always `allowed_tools`.""" diff --git a/src/openai/types/responses/tool_choice_apply_patch.py b/src/openai/types/responses/tool_choice_apply_patch.py new file mode 100644 index 0000000000..ef5a5e8bfa --- /dev/null +++ b/src/openai/types/responses/tool_choice_apply_patch.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ToolChoiceApplyPatch"] + + +class ToolChoiceApplyPatch(BaseModel): + """Forces the model to call the apply_patch tool when executing a tool call.""" + + type: Literal["apply_patch"] + """The tool to call. Always `apply_patch`.""" diff --git a/src/openai/types/responses/tool_choice_apply_patch_param.py b/src/openai/types/responses/tool_choice_apply_patch_param.py new file mode 100644 index 0000000000..193c99328a --- /dev/null +++ b/src/openai/types/responses/tool_choice_apply_patch_param.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceApplyPatchParam"] + + +class ToolChoiceApplyPatchParam(TypedDict, total=False): + """Forces the model to call the apply_patch tool when executing a tool call.""" + + type: Required[Literal["apply_patch"]] + """The tool to call. Always `apply_patch`.""" diff --git a/src/openai/types/responses/tool_choice_custom.py b/src/openai/types/responses/tool_choice_custom.py new file mode 100644 index 0000000000..dec85ef78c --- /dev/null +++ b/src/openai/types/responses/tool_choice_custom.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ToolChoiceCustom"] + + +class ToolChoiceCustom(BaseModel): + """Use this option to force the model to call a specific custom tool.""" + + name: str + """The name of the custom tool to call.""" + + type: Literal["custom"] + """For custom tool calling, the type is always `custom`.""" diff --git a/src/openai/types/responses/tool_choice_custom_param.py b/src/openai/types/responses/tool_choice_custom_param.py new file mode 100644 index 0000000000..ccdbab568a --- /dev/null +++ b/src/openai/types/responses/tool_choice_custom_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceCustomParam"] + + +class ToolChoiceCustomParam(TypedDict, total=False): + """Use this option to force the model to call a specific custom tool.""" + + name: Required[str] + """The name of the custom tool to call.""" + + type: Required[Literal["custom"]] + """For custom tool calling, the type is always `custom`.""" diff --git a/src/openai/types/responses/tool_choice_function.py b/src/openai/types/responses/tool_choice_function.py new file mode 100644 index 0000000000..b2aab24aca --- /dev/null +++ b/src/openai/types/responses/tool_choice_function.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ToolChoiceFunction"] + + +class ToolChoiceFunction(BaseModel): + """Use this option to force the model to call a specific function.""" + + name: str + """The name of the function to call.""" + + type: Literal["function"] + """For function calling, the type is always `function`.""" diff --git a/src/openai/types/responses/tool_choice_function_param.py b/src/openai/types/responses/tool_choice_function_param.py new file mode 100644 index 0000000000..837465ebd7 --- /dev/null +++ b/src/openai/types/responses/tool_choice_function_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceFunctionParam"] + + +class ToolChoiceFunctionParam(TypedDict, total=False): + """Use this option to force the model to call a specific function.""" + + name: Required[str] + """The name of the function to call.""" + + type: Required[Literal["function"]] + """For function calling, the type is always `function`.""" diff --git a/src/openai/types/responses/tool_choice_mcp.py b/src/openai/types/responses/tool_choice_mcp.py new file mode 100644 index 0000000000..a2c8049c2d --- /dev/null +++ b/src/openai/types/responses/tool_choice_mcp.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ToolChoiceMcp"] + + +class ToolChoiceMcp(BaseModel): + """ + Use this option to force the model to call a specific tool on a remote MCP server. + """ + + server_label: str + """The label of the MCP server to use.""" + + type: Literal["mcp"] + """For MCP tools, the type is always `mcp`.""" + + name: Optional[str] = None + """The name of the tool to call on the server.""" diff --git a/src/openai/types/responses/tool_choice_mcp_param.py b/src/openai/types/responses/tool_choice_mcp_param.py new file mode 100644 index 0000000000..9726e47a47 --- /dev/null +++ b/src/openai/types/responses/tool_choice_mcp_param.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceMcpParam"] + + +class ToolChoiceMcpParam(TypedDict, total=False): + """ + Use this option to force the model to call a specific tool on a remote MCP server. + """ + + server_label: Required[str] + """The label of the MCP server to use.""" + + type: Required[Literal["mcp"]] + """For MCP tools, the type is always `mcp`.""" + + name: Optional[str] + """The name of the tool to call on the server.""" diff --git a/src/openai/types/responses/tool_choice_options.py b/src/openai/types/responses/tool_choice_options.py new file mode 100644 index 0000000000..c200db54e1 --- /dev/null +++ b/src/openai/types/responses/tool_choice_options.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ToolChoiceOptions"] + +ToolChoiceOptions: TypeAlias = Literal["none", "auto", "required"] diff --git a/src/openai/types/responses/tool_choice_shell.py b/src/openai/types/responses/tool_choice_shell.py new file mode 100644 index 0000000000..a78eccc387 --- /dev/null +++ b/src/openai/types/responses/tool_choice_shell.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ToolChoiceShell"] + + +class ToolChoiceShell(BaseModel): + """Forces the model to call the shell tool when a tool call is required.""" + + type: Literal["shell"] + """The tool to call. Always `shell`.""" diff --git a/src/openai/types/responses/tool_choice_shell_param.py b/src/openai/types/responses/tool_choice_shell_param.py new file mode 100644 index 0000000000..0dbcc90f39 --- /dev/null +++ b/src/openai/types/responses/tool_choice_shell_param.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceShellParam"] + + +class ToolChoiceShellParam(TypedDict, total=False): + """Forces the model to call the shell tool when a tool call is required.""" + + type: Required[Literal["shell"]] + """The tool to call. Always `shell`.""" diff --git a/src/openai/types/responses/tool_choice_types.py b/src/openai/types/responses/tool_choice_types.py new file mode 100644 index 0000000000..3cd1d3f64c --- /dev/null +++ b/src/openai/types/responses/tool_choice_types.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ToolChoiceTypes"] + + +class ToolChoiceTypes(BaseModel): + """ + Indicates that the model should use a built-in tool to generate a response. + [Learn more about built-in tools](https://platform.openai.com/docs/guides/tools). + """ + + type: Literal[ + "file_search", + "web_search_preview", + "computer", + "computer_use_preview", + "computer_use", + "web_search_preview_2025_03_11", + "image_generation", + "code_interpreter", + ] + """The type of hosted tool the model should to use. + + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + + Allowed values are: + + - `file_search` + - `web_search_preview` + - `computer` + - `computer_use_preview` + - `computer_use` + - `code_interpreter` + - `image_generation` + """ diff --git a/src/openai/types/responses/tool_choice_types_param.py b/src/openai/types/responses/tool_choice_types_param.py new file mode 100644 index 0000000000..5d08380a22 --- /dev/null +++ b/src/openai/types/responses/tool_choice_types_param.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceTypesParam"] + + +class ToolChoiceTypesParam(TypedDict, total=False): + """ + Indicates that the model should use a built-in tool to generate a response. + [Learn more about built-in tools](https://platform.openai.com/docs/guides/tools). + """ + + type: Required[ + Literal[ + "file_search", + "web_search_preview", + "computer", + "computer_use_preview", + "computer_use", + "web_search_preview_2025_03_11", + "image_generation", + "code_interpreter", + ] + ] + """The type of hosted tool the model should to use. + + Learn more about + [built-in tools](https://platform.openai.com/docs/guides/tools). + + Allowed values are: + + - `file_search` + - `web_search_preview` + - `computer` + - `computer_use_preview` + - `computer_use` + - `code_interpreter` + - `image_generation` + """ diff --git a/src/openai/types/responses/tool_param.py b/src/openai/types/responses/tool_param.py new file mode 100644 index 0000000000..7a9a566b16 --- /dev/null +++ b/src/openai/types/responses/tool_param.py @@ -0,0 +1,359 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from . import web_search_tool_param +from ..chat import ChatCompletionFunctionToolParam +from ..._types import SequenceNotStr +from .custom_tool_param import CustomToolParam +from .computer_tool_param import ComputerToolParam +from .function_tool_param import FunctionToolParam +from .namespace_tool_param import NamespaceToolParam +from .web_search_tool_param import WebSearchToolParam +from .apply_patch_tool_param import ApplyPatchToolParam +from .file_search_tool_param import FileSearchToolParam +from .tool_search_tool_param import ToolSearchToolParam +from .function_shell_tool_param import FunctionShellToolParam +from .web_search_preview_tool_param import WebSearchPreviewToolParam +from .computer_use_preview_tool_param import ComputerUsePreviewToolParam +from .container_network_policy_disabled_param import ContainerNetworkPolicyDisabledParam +from .container_network_policy_allowlist_param import ContainerNetworkPolicyAllowlistParam + +__all__ = [ + "ToolParam", + "Mcp", + "McpAllowedTools", + "McpAllowedToolsMcpToolFilter", + "McpRequireApproval", + "McpRequireApprovalMcpToolApprovalFilter", + "McpRequireApprovalMcpToolApprovalFilterAlways", + "McpRequireApprovalMcpToolApprovalFilterNever", + "CodeInterpreter", + "CodeInterpreterContainer", + "CodeInterpreterContainerCodeInterpreterToolAuto", + "CodeInterpreterContainerCodeInterpreterToolAutoNetworkPolicy", + "ImageGeneration", + "ImageGenerationInputImageMask", + "LocalShell", +] + +WebSearchTool = web_search_tool_param.WebSearchToolParam +WebSearchToolFilters = web_search_tool_param.Filters +WebSearchToolUserLocation = web_search_tool_param.UserLocation + + +class McpAllowedToolsMcpToolFilter(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +McpAllowedTools: TypeAlias = Union[SequenceNotStr[str], McpAllowedToolsMcpToolFilter] + + +class McpRequireApprovalMcpToolApprovalFilterAlways(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +class McpRequireApprovalMcpToolApprovalFilterNever(TypedDict, total=False): + """A filter object to specify which tools are allowed.""" + + read_only: bool + """Indicates whether or not a tool modifies data or is read-only. + + If an MCP server is + [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint), + it will match this filter. + """ + + tool_names: SequenceNotStr[str] + """List of allowed tool names.""" + + +class McpRequireApprovalMcpToolApprovalFilter(TypedDict, total=False): + """Specify which of the MCP server's tools require approval. + + Can be + `always`, `never`, or a filter object associated with tools + that require approval. + """ + + always: McpRequireApprovalMcpToolApprovalFilterAlways + """A filter object to specify which tools are allowed.""" + + never: McpRequireApprovalMcpToolApprovalFilterNever + """A filter object to specify which tools are allowed.""" + + +McpRequireApproval: TypeAlias = Union[McpRequireApprovalMcpToolApprovalFilter, Literal["always", "never"]] + + +class Mcp(TypedDict, total=False): + """ + Give the model access to additional tools via remote Model Context Protocol + (MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp). + """ + + server_label: Required[str] + """A label for this MCP server, used to identify it in tool calls.""" + + type: Required[Literal["mcp"]] + """The type of the MCP tool. Always `mcp`.""" + + allowed_tools: Optional[McpAllowedTools] + """List of allowed tool names or a filter object.""" + + authorization: str + """ + An OAuth access token that can be used with a remote MCP server, either with a + custom MCP server URL or a service connector. Your application must handle the + OAuth authorization flow and provide the token here. + """ + + connector_id: Literal[ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint", + ] + """Identifier for service connectors, like those available in ChatGPT. + + One of `server_url` or `connector_id` must be provided. Learn more about service + connectors + [here](https://platform.openai.com/docs/guides/tools-remote-mcp#connectors). + + Currently supported `connector_id` values are: + + - Dropbox: `connector_dropbox` + - Gmail: `connector_gmail` + - Google Calendar: `connector_googlecalendar` + - Google Drive: `connector_googledrive` + - Microsoft Teams: `connector_microsoftteams` + - Outlook Calendar: `connector_outlookcalendar` + - Outlook Email: `connector_outlookemail` + - SharePoint: `connector_sharepoint` + """ + + defer_loading: bool + """Whether this MCP tool is deferred and discovered via tool search.""" + + headers: Optional[Dict[str, str]] + """Optional HTTP headers to send to the MCP server. + + Use for authentication or other purposes. + """ + + require_approval: Optional[McpRequireApproval] + """Specify which of the MCP server's tools require approval.""" + + server_description: str + """Optional description of the MCP server, used to provide more context.""" + + server_url: str + """The URL for the MCP server. + + One of `server_url` or `connector_id` must be provided. + """ + + +CodeInterpreterContainerCodeInterpreterToolAutoNetworkPolicy: TypeAlias = Union[ + ContainerNetworkPolicyDisabledParam, ContainerNetworkPolicyAllowlistParam +] + + +class CodeInterpreterContainerCodeInterpreterToolAuto(TypedDict, total=False): + """Configuration for a code interpreter container. + + Optionally specify the IDs of the files to run the code on. + """ + + type: Required[Literal["auto"]] + """Always `auto`.""" + + file_ids: SequenceNotStr[str] + """An optional list of uploaded files to make available to your code.""" + + memory_limit: Optional[Literal["1g", "4g", "16g", "64g"]] + """The memory limit for the code interpreter container.""" + + network_policy: CodeInterpreterContainerCodeInterpreterToolAutoNetworkPolicy + """Network access policy for the container.""" + + +CodeInterpreterContainer: TypeAlias = Union[str, CodeInterpreterContainerCodeInterpreterToolAuto] + + +class CodeInterpreter(TypedDict, total=False): + """A tool that runs Python code to help generate a response to a prompt.""" + + container: Required[CodeInterpreterContainer] + """The code interpreter container. + + Can be a container ID or an object that specifies uploaded file IDs to make + available to your code, along with an optional `memory_limit` setting. + """ + + type: Required[Literal["code_interpreter"]] + """The type of the code interpreter tool. Always `code_interpreter`.""" + + +class ImageGenerationInputImageMask(TypedDict, total=False): + """Optional mask for inpainting. + + Contains `image_url` + (string, optional) and `file_id` (string, optional). + """ + + file_id: str + """File ID for the mask image.""" + + image_url: str + """Base64-encoded mask image.""" + + +class ImageGeneration(TypedDict, total=False): + """A tool that generates images using the GPT image models.""" + + type: Required[Literal["image_generation"]] + """The type of the image generation tool. Always `image_generation`.""" + + action: Literal["generate", "edit", "auto"] + """Whether to generate a new image or edit an existing image. Default: `auto`.""" + + background: Literal["transparent", "opaque", "auto"] + """ + Allows to set transparency for the background of the generated image(s). This + parameter is only supported for GPT image models that support transparent + backgrounds. Must be one of `transparent`, `opaque`, or `auto` (default value). + When `auto` is used, the model will automatically determine the best background + for the image. + + `gpt-image-2` and `gpt-image-2-2026-04-21` do not support transparent + backgrounds. Requests with `background` set to `transparent` will return an + error for these models; use `opaque` or `auto` instead. + + If `transparent`, the output format needs to support transparency, so it should + be set to either `png` (default value) or `webp`. + """ + + input_fidelity: Optional[Literal["high", "low"]] + """ + Control how much effort the model will exert to match the style and features, + especially facial features, of input images. This parameter is only supported + for `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for + `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`. + """ + + input_image_mask: ImageGenerationInputImageMask + """Optional mask for inpainting. + + Contains `image_url` (string, optional) and `file_id` (string, optional). + """ + + model: Union[ + str, + Literal[ + "gpt-image-1", + "gpt-image-1-mini", + "gpt-image-2", + "gpt-image-2-2026-04-21", + "gpt-image-1.5", + "chatgpt-image-latest", + ], + ] + """The image generation model to use. Default: `gpt-image-1`.""" + + moderation: Literal["auto", "low"] + """Moderation level for the generated image. Default: `auto`.""" + + output_compression: int + """Compression level for the output image. Default: 100.""" + + output_format: Literal["png", "webp", "jpeg"] + """The output format of the generated image. + + One of `png`, `webp`, or `jpeg`. Default: `png`. + """ + + partial_images: int + """ + Number of partial images to generate in streaming mode, from 0 (default value) + to 3. + """ + + quality: Literal["low", "medium", "high", "auto"] + """The quality of the generated image. + + One of `low`, `medium`, `high`, or `auto`. Default: `auto`. + """ + + size: Union[str, Literal["1024x1024", "1024x1536", "1536x1024", "auto"]] + """The size of the generated images. + + For `gpt-image-2` and `gpt-image-2-2026-04-21`, arbitrary resolutions are + supported as `WIDTHxHEIGHT` strings, for example `1536x864`. Width and height + must both be divisible by 16 and the requested aspect ratio must be between 1:3 + and 3:1. Resolutions above `2560x1440` are experimental, and the maximum + supported resolution is `3840x2160`. The requested size must also satisfy the + model's current pixel and edge limits. The standard sizes `1024x1024`, + `1536x1024`, and `1024x1536` are supported by the GPT image models; `auto` is + supported for models that allow automatic sizing. For `dall-e-2`, use one of + `256x256`, `512x512`, or `1024x1024`. For `dall-e-3`, use one of `1024x1024`, + `1792x1024`, or `1024x1792`. + """ + + +class LocalShell(TypedDict, total=False): + """A tool that allows the model to execute shell commands in a local environment.""" + + type: Required[Literal["local_shell"]] + """The type of the local shell tool. Always `local_shell`.""" + + +ToolParam: TypeAlias = Union[ + FunctionToolParam, + FileSearchToolParam, + ComputerToolParam, + ComputerUsePreviewToolParam, + WebSearchToolParam, + Mcp, + CodeInterpreter, + ImageGeneration, + LocalShell, + FunctionShellToolParam, + CustomToolParam, + NamespaceToolParam, + ToolSearchToolParam, + WebSearchPreviewToolParam, + ApplyPatchToolParam, +] + + +ParseableToolParam: TypeAlias = Union[ToolParam, ChatCompletionFunctionToolParam] diff --git a/src/openai/types/responses/tool_search_tool.py b/src/openai/types/responses/tool_search_tool.py new file mode 100644 index 0000000000..44a741a1c4 --- /dev/null +++ b/src/openai/types/responses/tool_search_tool.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ToolSearchTool"] + + +class ToolSearchTool(BaseModel): + """Hosted or BYOT tool search configuration for deferred tools.""" + + type: Literal["tool_search"] + """The type of the tool. Always `tool_search`.""" + + description: Optional[str] = None + """Description shown to the model for a client-executed tool search tool.""" + + execution: Optional[Literal["server", "client"]] = None + """Whether tool search is executed by the server or by the client.""" + + parameters: Optional[object] = None + """Parameter schema for a client-executed tool search tool.""" diff --git a/src/openai/types/responses/tool_search_tool_param.py b/src/openai/types/responses/tool_search_tool_param.py new file mode 100644 index 0000000000..3063da276c --- /dev/null +++ b/src/openai/types/responses/tool_search_tool_param.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolSearchToolParam"] + + +class ToolSearchToolParam(TypedDict, total=False): + """Hosted or BYOT tool search configuration for deferred tools.""" + + type: Required[Literal["tool_search"]] + """The type of the tool. Always `tool_search`.""" + + description: Optional[str] + """Description shown to the model for a client-executed tool search tool.""" + + execution: Literal["server", "client"] + """Whether tool search is executed by the server or by the client.""" + + parameters: Optional[object] + """Parameter schema for a client-executed tool search tool.""" diff --git a/src/openai/types/responses/web_search_preview_tool.py b/src/openai/types/responses/web_search_preview_tool.py new file mode 100644 index 0000000000..bdf092f196 --- /dev/null +++ b/src/openai/types/responses/web_search_preview_tool.py @@ -0,0 +1,58 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["WebSearchPreviewTool", "UserLocation"] + + +class UserLocation(BaseModel): + """The user's location.""" + + type: Literal["approximate"] + """The type of location approximation. Always `approximate`.""" + + city: Optional[str] = None + """Free text input for the city of the user, e.g. `San Francisco`.""" + + country: Optional[str] = None + """ + The two-letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1) of + the user, e.g. `US`. + """ + + region: Optional[str] = None + """Free text input for the region of the user, e.g. `California`.""" + + timezone: Optional[str] = None + """ + The [IANA timezone](https://timeapi.io/documentation/iana-timezones) of the + user, e.g. `America/Los_Angeles`. + """ + + +class WebSearchPreviewTool(BaseModel): + """This tool searches the web for relevant results to use in a response. + + Learn more about the [web search tool](https://platform.openai.com/docs/guides/tools-web-search). + """ + + type: Literal["web_search_preview", "web_search_preview_2025_03_11"] + """The type of the web search tool. + + One of `web_search_preview` or `web_search_preview_2025_03_11`. + """ + + search_content_types: Optional[List[Literal["text", "image"]]] = None + + search_context_size: Optional[Literal["low", "medium", "high"]] = None + """High level guidance for the amount of context window space to use for the + search. + + One of `low`, `medium`, or `high`. `medium` is the default. + """ + + user_location: Optional[UserLocation] = None + """The user's location.""" diff --git a/src/openai/types/responses/web_search_preview_tool_param.py b/src/openai/types/responses/web_search_preview_tool_param.py new file mode 100644 index 0000000000..b81f95e308 --- /dev/null +++ b/src/openai/types/responses/web_search_preview_tool_param.py @@ -0,0 +1,58 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["WebSearchPreviewToolParam", "UserLocation"] + + +class UserLocation(TypedDict, total=False): + """The user's location.""" + + type: Required[Literal["approximate"]] + """The type of location approximation. Always `approximate`.""" + + city: Optional[str] + """Free text input for the city of the user, e.g. `San Francisco`.""" + + country: Optional[str] + """ + The two-letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1) of + the user, e.g. `US`. + """ + + region: Optional[str] + """Free text input for the region of the user, e.g. `California`.""" + + timezone: Optional[str] + """ + The [IANA timezone](https://timeapi.io/documentation/iana-timezones) of the + user, e.g. `America/Los_Angeles`. + """ + + +class WebSearchPreviewToolParam(TypedDict, total=False): + """This tool searches the web for relevant results to use in a response. + + Learn more about the [web search tool](https://platform.openai.com/docs/guides/tools-web-search). + """ + + type: Required[Literal["web_search_preview", "web_search_preview_2025_03_11"]] + """The type of the web search tool. + + One of `web_search_preview` or `web_search_preview_2025_03_11`. + """ + + search_content_types: List[Literal["text", "image"]] + + search_context_size: Literal["low", "medium", "high"] + """High level guidance for the amount of context window space to use for the + search. + + One of `low`, `medium`, or `high`. `medium` is the default. + """ + + user_location: Optional[UserLocation] + """The user's location.""" diff --git a/src/openai/types/responses/web_search_tool.py b/src/openai/types/responses/web_search_tool.py new file mode 100644 index 0000000000..769f5c93a4 --- /dev/null +++ b/src/openai/types/responses/web_search_tool.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["WebSearchTool", "Filters", "UserLocation"] + + +class Filters(BaseModel): + """Filters for the search.""" + + allowed_domains: Optional[List[str]] = None + """Allowed domains for the search. + + If not provided, all domains are allowed. Subdomains of the provided domains are + allowed as well. + + Example: `["pubmed.ncbi.nlm.nih.gov"]` + """ + + +class UserLocation(BaseModel): + """The approximate location of the user.""" + + city: Optional[str] = None + """Free text input for the city of the user, e.g. `San Francisco`.""" + + country: Optional[str] = None + """ + The two-letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1) of + the user, e.g. `US`. + """ + + region: Optional[str] = None + """Free text input for the region of the user, e.g. `California`.""" + + timezone: Optional[str] = None + """ + The [IANA timezone](https://timeapi.io/documentation/iana-timezones) of the + user, e.g. `America/Los_Angeles`. + """ + + type: Optional[Literal["approximate"]] = None + """The type of location approximation. Always `approximate`.""" + + +class WebSearchTool(BaseModel): + """Search the Internet for sources related to the prompt. + + Learn more about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search). + """ + + type: Literal["web_search", "web_search_2025_08_26"] + """The type of the web search tool. + + One of `web_search` or `web_search_2025_08_26`. + """ + + filters: Optional[Filters] = None + """Filters for the search.""" + + search_context_size: Optional[Literal["low", "medium", "high"]] = None + """High level guidance for the amount of context window space to use for the + search. + + One of `low`, `medium`, or `high`. `medium` is the default. + """ + + user_location: Optional[UserLocation] = None + """The approximate location of the user.""" diff --git a/src/openai/types/responses/web_search_tool_param.py b/src/openai/types/responses/web_search_tool_param.py new file mode 100644 index 0000000000..a4531a9304 --- /dev/null +++ b/src/openai/types/responses/web_search_tool_param.py @@ -0,0 +1,75 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["WebSearchToolParam", "Filters", "UserLocation"] + + +class Filters(TypedDict, total=False): + """Filters for the search.""" + + allowed_domains: Optional[SequenceNotStr[str]] + """Allowed domains for the search. + + If not provided, all domains are allowed. Subdomains of the provided domains are + allowed as well. + + Example: `["pubmed.ncbi.nlm.nih.gov"]` + """ + + +class UserLocation(TypedDict, total=False): + """The approximate location of the user.""" + + city: Optional[str] + """Free text input for the city of the user, e.g. `San Francisco`.""" + + country: Optional[str] + """ + The two-letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1) of + the user, e.g. `US`. + """ + + region: Optional[str] + """Free text input for the region of the user, e.g. `California`.""" + + timezone: Optional[str] + """ + The [IANA timezone](https://timeapi.io/documentation/iana-timezones) of the + user, e.g. `America/Los_Angeles`. + """ + + type: Literal["approximate"] + """The type of location approximation. Always `approximate`.""" + + +class WebSearchToolParam(TypedDict, total=False): + """Search the Internet for sources related to the prompt. + + Learn more about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search). + """ + + type: Required[Literal["web_search", "web_search_2025_08_26"]] + """The type of the web search tool. + + One of `web_search` or `web_search_2025_08_26`. + """ + + filters: Optional[Filters] + """Filters for the search.""" + + search_context_size: Literal["low", "medium", "high"] + """High level guidance for the amount of context window space to use for the + search. + + One of `low`, `medium`, or `high`. `medium` is the default. + """ + + user_location: Optional[UserLocation] + """The approximate location of the user.""" diff --git a/src/openai/types/shared/__init__.py b/src/openai/types/shared/__init__.py index c8776bca0e..55d3e86371 100644 --- a/src/openai/types/shared/__init__.py +++ b/src/openai/types/shared/__init__.py @@ -1,8 +1,20 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .metadata import Metadata as Metadata +from .reasoning import Reasoning as Reasoning +from .all_models import AllModels as AllModels +from .chat_model import ChatModel as ChatModel from .error_object import ErrorObject as ErrorObject +from .compound_filter import CompoundFilter as CompoundFilter +from .responses_model import ResponsesModel as ResponsesModel +from .oauth_error_code import OAuthErrorCode as OAuthErrorCode +from .reasoning_effort import ReasoningEffort as ReasoningEffort +from .comparison_filter import ComparisonFilter as ComparisonFilter from .function_definition import FunctionDefinition as FunctionDefinition from .function_parameters import FunctionParameters as FunctionParameters from .response_format_text import ResponseFormatText as ResponseFormatText +from .custom_tool_input_format import CustomToolInputFormat as CustomToolInputFormat from .response_format_json_object import ResponseFormatJSONObject as ResponseFormatJSONObject from .response_format_json_schema import ResponseFormatJSONSchema as ResponseFormatJSONSchema +from .response_format_text_python import ResponseFormatTextPython as ResponseFormatTextPython +from .response_format_text_grammar import ResponseFormatTextGrammar as ResponseFormatTextGrammar diff --git a/src/openai/types/shared/all_models.py b/src/openai/types/shared/all_models.py new file mode 100644 index 0000000000..ba8e1d82cf --- /dev/null +++ b/src/openai/types/shared/all_models.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, TypeAlias + +from .chat_model import ChatModel + +__all__ = ["AllModels"] + +AllModels: TypeAlias = Union[ + str, + ChatModel, + Literal[ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max", + ], +] diff --git a/src/openai/types/shared/chat_model.py b/src/openai/types/shared/chat_model.py new file mode 100644 index 0000000000..501a22a8bd --- /dev/null +++ b/src/openai/types/shared/chat_model.py @@ -0,0 +1,86 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ChatModel"] + +ChatModel: TypeAlias = Literal[ + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gpt-5.4-mini-2026-03-17", + "gpt-5.4-nano-2026-03-17", + "gpt-5.3-chat-latest", + "gpt-5.2", + "gpt-5.2-2025-12-11", + "gpt-5.2-chat-latest", + "gpt-5.2-pro", + "gpt-5.2-pro-2025-12-11", + "gpt-5.1", + "gpt-5.1-2025-11-13", + "gpt-5.1-codex", + "gpt-5.1-mini", + "gpt-5.1-chat-latest", + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-5-chat-latest", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o4-mini", + "o4-mini-2025-04-16", + "o3", + "o3-2025-04-16", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "o1-preview", + "o1-preview-2024-09-12", + "o1-mini", + "o1-mini-2024-09-12", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-audio-preview", + "gpt-4o-audio-preview-2024-10-01", + "gpt-4o-audio-preview-2024-12-17", + "gpt-4o-audio-preview-2025-06-03", + "gpt-4o-mini-audio-preview", + "gpt-4o-mini-audio-preview-2024-12-17", + "gpt-4o-search-preview", + "gpt-4o-mini-search-preview", + "gpt-4o-search-preview-2025-03-11", + "gpt-4o-mini-search-preview-2025-03-11", + "chatgpt-4o-latest", + "codex-mini-latest", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0301", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613", +] diff --git a/src/openai/types/shared/comparison_filter.py b/src/openai/types/shared/comparison_filter.py new file mode 100644 index 0000000000..57c26cd016 --- /dev/null +++ b/src/openai/types/shared/comparison_filter.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ComparisonFilter"] + + +class ComparisonFilter(BaseModel): + """ + A filter used to compare a specified attribute key to a given value using a defined comparison operation. + """ + + key: str + """The key to compare against the value.""" + + type: Literal["eq", "ne", "gt", "gte", "lt", "lte", "in", "nin"] + """ + Specifies the comparison operator: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, + `nin`. + + - `eq`: equals + - `ne`: not equal + - `gt`: greater than + - `gte`: greater than or equal + - `lt`: less than + - `lte`: less than or equal + - `in`: in + - `nin`: not in + """ + + value: Union[str, float, bool, List[Union[str, float]]] + """ + The value to compare against the attribute key; supports string, number, or + boolean types. + """ diff --git a/src/openai/types/shared/compound_filter.py b/src/openai/types/shared/compound_filter.py new file mode 100644 index 0000000000..4801aaac1a --- /dev/null +++ b/src/openai/types/shared/compound_filter.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .comparison_filter import ComparisonFilter + +__all__ = ["CompoundFilter", "Filter"] + +Filter: TypeAlias = Union[ComparisonFilter, object] + + +class CompoundFilter(BaseModel): + """Combine multiple filters using `and` or `or`.""" + + filters: List[Filter] + """Array of filters to combine. + + Items can be `ComparisonFilter` or `CompoundFilter`. + """ + + type: Literal["and", "or"] + """Type of operation: `and` or `or`.""" diff --git a/src/openai/types/shared/custom_tool_input_format.py b/src/openai/types/shared/custom_tool_input_format.py new file mode 100644 index 0000000000..9391692b7b --- /dev/null +++ b/src/openai/types/shared/custom_tool_input_format.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = ["CustomToolInputFormat", "Text", "Grammar"] + + +class Text(BaseModel): + """Unconstrained free-form text.""" + + type: Literal["text"] + """Unconstrained text format. Always `text`.""" + + +class Grammar(BaseModel): + """A grammar defined by the user.""" + + definition: str + """The grammar definition.""" + + syntax: Literal["lark", "regex"] + """The syntax of the grammar definition. One of `lark` or `regex`.""" + + type: Literal["grammar"] + """Grammar format. Always `grammar`.""" + + +CustomToolInputFormat: TypeAlias = Annotated[Union[Text, Grammar], PropertyInfo(discriminator="type")] diff --git a/src/openai/types/shared/function_definition.py b/src/openai/types/shared/function_definition.py index 06baa23170..33ebb9ad3e 100644 --- a/src/openai/types/shared/function_definition.py +++ b/src/openai/types/shared/function_definition.py @@ -39,5 +39,5 @@ class FunctionDefinition(BaseModel): If set to true, the model will follow the exact schema defined in the `parameters` field. Only a subset of JSON Schema is supported when `strict` is `true`. Learn more about Structured Outputs in the - [function calling guide](docs/guides/function-calling). + [function calling guide](https://platform.openai.com/docs/guides/function-calling). """ diff --git a/src/openai/types/shared/metadata.py b/src/openai/types/shared/metadata.py new file mode 100644 index 0000000000..0da88c679c --- /dev/null +++ b/src/openai/types/shared/metadata.py @@ -0,0 +1,8 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict +from typing_extensions import TypeAlias + +__all__ = ["Metadata"] + +Metadata: TypeAlias = Dict[str, str] diff --git a/src/openai/types/shared/oauth_error_code.py b/src/openai/types/shared/oauth_error_code.py new file mode 100644 index 0000000000..4ef3924293 --- /dev/null +++ b/src/openai/types/shared/oauth_error_code.py @@ -0,0 +1,8 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, TypeAlias + +__all__ = ["OAuthErrorCode"] + +OAuthErrorCode: TypeAlias = Union[Literal["invalid_grant", "invalid_subject_token"], str] diff --git a/src/openai/types/shared/reasoning.py b/src/openai/types/shared/reasoning.py new file mode 100644 index 0000000000..14f56a04cd --- /dev/null +++ b/src/openai/types/shared/reasoning.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .reasoning_effort import ReasoningEffort + +__all__ = ["Reasoning"] + + +class Reasoning(BaseModel): + """**gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + effort: Optional[ReasoningEffort] = None + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + generate_summary: Optional[Literal["auto", "concise", "detailed"]] = None + """**Deprecated:** use `summary` instead. + + A summary of the reasoning performed by the model. This can be useful for + debugging and understanding the model's reasoning process. One of `auto`, + `concise`, or `detailed`. + """ + + summary: Optional[Literal["auto", "concise", "detailed"]] = None + """A summary of the reasoning performed by the model. + + This can be useful for debugging and understanding the model's reasoning + process. One of `auto`, `concise`, or `detailed`. + + `concise` is supported for `computer-use-preview` models and all reasoning + models after `gpt-5`. + """ diff --git a/src/openai/types/shared/reasoning_effort.py b/src/openai/types/shared/reasoning_effort.py new file mode 100644 index 0000000000..24d8516424 --- /dev/null +++ b/src/openai/types/shared/reasoning_effort.py @@ -0,0 +1,8 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal, TypeAlias + +__all__ = ["ReasoningEffort"] + +ReasoningEffort: TypeAlias = Optional[Literal["none", "minimal", "low", "medium", "high", "xhigh"]] diff --git a/src/openai/types/shared/response_format_json_object.py b/src/openai/types/shared/response_format_json_object.py index 107728dd2e..98e0da6a2c 100644 --- a/src/openai/types/shared/response_format_json_object.py +++ b/src/openai/types/shared/response_format_json_object.py @@ -8,5 +8,13 @@ class ResponseFormatJSONObject(BaseModel): + """JSON object response format. + + An older method of generating JSON responses. + Using `json_schema` is recommended for models that support it. Note that the + model will not generate JSON without a system or user message instructing it + to do so. + """ + type: Literal["json_object"] - """The type of response format being defined: `json_object`""" + """The type of response format being defined. Always `json_object`.""" diff --git a/src/openai/types/shared/response_format_json_schema.py b/src/openai/types/shared/response_format_json_schema.py index 3194a4fe91..9b2adb66cd 100644 --- a/src/openai/types/shared/response_format_json_schema.py +++ b/src/openai/types/shared/response_format_json_schema.py @@ -11,6 +11,8 @@ class JSONSchema(BaseModel): + """Structured Outputs configuration options, including a JSON Schema.""" + name: str """The name of the response format. @@ -25,20 +27,30 @@ class JSONSchema(BaseModel): """ schema_: Optional[Dict[str, object]] = FieldInfo(alias="schema", default=None) - """The schema for the response format, described as a JSON Schema object.""" + """ + The schema for the response format, described as a JSON Schema object. Learn how + to build JSON schemas [here](https://json-schema.org/). + """ strict: Optional[bool] = None - """Whether to enable strict schema adherence when generating the output. - - If set to true, the model will always follow the exact schema defined in the - `schema` field. Only a subset of JSON Schema is supported when `strict` is - `true`. To learn more, read the + """ + Whether to enable strict schema adherence when generating the output. If set to + true, the model will always follow the exact schema defined in the `schema` + field. Only a subset of JSON Schema is supported when `strict` is `true`. To + learn more, read the [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). """ class ResponseFormatJSONSchema(BaseModel): + """JSON Schema response format. + + Used to generate structured JSON responses. + Learn more about [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs). + """ + json_schema: JSONSchema + """Structured Outputs configuration options, including a JSON Schema.""" type: Literal["json_schema"] - """The type of response format being defined: `json_schema`""" + """The type of response format being defined. Always `json_schema`.""" diff --git a/src/openai/types/shared/response_format_text.py b/src/openai/types/shared/response_format_text.py index 6721fe0973..9f4bc0d13e 100644 --- a/src/openai/types/shared/response_format_text.py +++ b/src/openai/types/shared/response_format_text.py @@ -8,5 +8,7 @@ class ResponseFormatText(BaseModel): + """Default response format. Used to generate text responses.""" + type: Literal["text"] - """The type of response format being defined: `text`""" + """The type of response format being defined. Always `text`.""" diff --git a/src/openai/types/shared/response_format_text_grammar.py b/src/openai/types/shared/response_format_text_grammar.py new file mode 100644 index 0000000000..84cd141278 --- /dev/null +++ b/src/openai/types/shared/response_format_text_grammar.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFormatTextGrammar"] + + +class ResponseFormatTextGrammar(BaseModel): + """ + A custom grammar for the model to follow when generating text. + Learn more in the [custom grammars guide](https://platform.openai.com/docs/guides/custom-grammars). + """ + + grammar: str + """The custom grammar for the model to follow.""" + + type: Literal["grammar"] + """The type of response format being defined. Always `grammar`.""" diff --git a/src/openai/types/shared/response_format_text_python.py b/src/openai/types/shared/response_format_text_python.py new file mode 100644 index 0000000000..1b04cb62ba --- /dev/null +++ b/src/openai/types/shared/response_format_text_python.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFormatTextPython"] + + +class ResponseFormatTextPython(BaseModel): + """Configure the model to generate valid Python code. + + See the + [custom grammars guide](https://platform.openai.com/docs/guides/custom-grammars) for more details. + """ + + type: Literal["python"] + """The type of response format being defined. Always `python`.""" diff --git a/src/openai/types/shared/responses_model.py b/src/openai/types/shared/responses_model.py new file mode 100644 index 0000000000..38cdea9a94 --- /dev/null +++ b/src/openai/types/shared/responses_model.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, TypeAlias + +from .chat_model import ChatModel + +__all__ = ["ResponsesModel"] + +ResponsesModel: TypeAlias = Union[ + str, + ChatModel, + Literal[ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max", + ], +] diff --git a/src/openai/types/shared_params/__init__.py b/src/openai/types/shared_params/__init__.py index ab4057d59f..0ed5fbaa80 100644 --- a/src/openai/types/shared_params/__init__.py +++ b/src/openai/types/shared_params/__init__.py @@ -1,7 +1,16 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .metadata import Metadata as Metadata +from .reasoning import Reasoning as Reasoning +from .chat_model import ChatModel as ChatModel +from .compound_filter import CompoundFilter as CompoundFilter +from .responses_model import ResponsesModel as ResponsesModel +from .oauth_error_code import OAuthErrorCode as OAuthErrorCode +from .reasoning_effort import ReasoningEffort as ReasoningEffort +from .comparison_filter import ComparisonFilter as ComparisonFilter from .function_definition import FunctionDefinition as FunctionDefinition from .function_parameters import FunctionParameters as FunctionParameters from .response_format_text import ResponseFormatText as ResponseFormatText +from .custom_tool_input_format import CustomToolInputFormat as CustomToolInputFormat from .response_format_json_object import ResponseFormatJSONObject as ResponseFormatJSONObject from .response_format_json_schema import ResponseFormatJSONSchema as ResponseFormatJSONSchema diff --git a/src/openai/types/shared_params/chat_model.py b/src/openai/types/shared_params/chat_model.py new file mode 100644 index 0000000000..17eaacd905 --- /dev/null +++ b/src/openai/types/shared_params/chat_model.py @@ -0,0 +1,88 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ChatModel"] + +ChatModel: TypeAlias = Literal[ + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gpt-5.4-mini-2026-03-17", + "gpt-5.4-nano-2026-03-17", + "gpt-5.3-chat-latest", + "gpt-5.2", + "gpt-5.2-2025-12-11", + "gpt-5.2-chat-latest", + "gpt-5.2-pro", + "gpt-5.2-pro-2025-12-11", + "gpt-5.1", + "gpt-5.1-2025-11-13", + "gpt-5.1-codex", + "gpt-5.1-mini", + "gpt-5.1-chat-latest", + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-5-chat-latest", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o4-mini", + "o4-mini-2025-04-16", + "o3", + "o3-2025-04-16", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "o1-preview", + "o1-preview-2024-09-12", + "o1-mini", + "o1-mini-2024-09-12", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-audio-preview", + "gpt-4o-audio-preview-2024-10-01", + "gpt-4o-audio-preview-2024-12-17", + "gpt-4o-audio-preview-2025-06-03", + "gpt-4o-mini-audio-preview", + "gpt-4o-mini-audio-preview-2024-12-17", + "gpt-4o-search-preview", + "gpt-4o-mini-search-preview", + "gpt-4o-search-preview-2025-03-11", + "gpt-4o-mini-search-preview-2025-03-11", + "chatgpt-4o-latest", + "codex-mini-latest", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0301", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613", +] diff --git a/src/openai/types/shared_params/comparison_filter.py b/src/openai/types/shared_params/comparison_filter.py new file mode 100644 index 0000000000..005f4d1f0d --- /dev/null +++ b/src/openai/types/shared_params/comparison_filter.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["ComparisonFilter"] + + +class ComparisonFilter(TypedDict, total=False): + """ + A filter used to compare a specified attribute key to a given value using a defined comparison operation. + """ + + key: Required[str] + """The key to compare against the value.""" + + type: Required[Literal["eq", "ne", "gt", "gte", "lt", "lte", "in", "nin"]] + """ + Specifies the comparison operator: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, + `nin`. + + - `eq`: equals + - `ne`: not equal + - `gt`: greater than + - `gte`: greater than or equal + - `lt`: less than + - `lte`: less than or equal + - `in`: in + - `nin`: not in + """ + + value: Required[Union[str, float, bool, SequenceNotStr[Union[str, float]]]] + """ + The value to compare against the attribute key; supports string, number, or + boolean types. + """ diff --git a/src/openai/types/shared_params/compound_filter.py b/src/openai/types/shared_params/compound_filter.py new file mode 100644 index 0000000000..9358e46083 --- /dev/null +++ b/src/openai/types/shared_params/compound_filter.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .comparison_filter import ComparisonFilter + +__all__ = ["CompoundFilter", "Filter"] + +Filter: TypeAlias = Union[ComparisonFilter, object] + + +class CompoundFilter(TypedDict, total=False): + """Combine multiple filters using `and` or `or`.""" + + filters: Required[Iterable[Filter]] + """Array of filters to combine. + + Items can be `ComparisonFilter` or `CompoundFilter`. + """ + + type: Required[Literal["and", "or"]] + """Type of operation: `and` or `or`.""" diff --git a/src/openai/types/shared_params/custom_tool_input_format.py b/src/openai/types/shared_params/custom_tool_input_format.py new file mode 100644 index 0000000000..ddc71cacb4 --- /dev/null +++ b/src/openai/types/shared_params/custom_tool_input_format.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = ["CustomToolInputFormat", "Text", "Grammar"] + + +class Text(TypedDict, total=False): + """Unconstrained free-form text.""" + + type: Required[Literal["text"]] + """Unconstrained text format. Always `text`.""" + + +class Grammar(TypedDict, total=False): + """A grammar defined by the user.""" + + definition: Required[str] + """The grammar definition.""" + + syntax: Required[Literal["lark", "regex"]] + """The syntax of the grammar definition. One of `lark` or `regex`.""" + + type: Required[Literal["grammar"]] + """Grammar format. Always `grammar`.""" + + +CustomToolInputFormat: TypeAlias = Union[Text, Grammar] diff --git a/src/openai/types/shared_params/function_definition.py b/src/openai/types/shared_params/function_definition.py index d45ec13f1e..b3fdaf86ff 100644 --- a/src/openai/types/shared_params/function_definition.py +++ b/src/openai/types/shared_params/function_definition.py @@ -41,5 +41,5 @@ class FunctionDefinition(TypedDict, total=False): If set to true, the model will follow the exact schema defined in the `parameters` field. Only a subset of JSON Schema is supported when `strict` is `true`. Learn more about Structured Outputs in the - [function calling guide](docs/guides/function-calling). + [function calling guide](https://platform.openai.com/docs/guides/function-calling). """ diff --git a/src/openai/types/shared_params/metadata.py b/src/openai/types/shared_params/metadata.py new file mode 100644 index 0000000000..821650b48b --- /dev/null +++ b/src/openai/types/shared_params/metadata.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict +from typing_extensions import TypeAlias + +__all__ = ["Metadata"] + +Metadata: TypeAlias = Dict[str, str] diff --git a/src/openai/types/shared_params/oauth_error_code.py b/src/openai/types/shared_params/oauth_error_code.py new file mode 100644 index 0000000000..cb3c981881 --- /dev/null +++ b/src/openai/types/shared_params/oauth_error_code.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypeAlias + +__all__ = ["OAuthErrorCode"] + +OAuthErrorCode: TypeAlias = Union[Literal["invalid_grant", "invalid_subject_token"], str] diff --git a/src/openai/types/shared_params/reasoning.py b/src/openai/types/shared_params/reasoning.py new file mode 100644 index 0000000000..2bd7ce7268 --- /dev/null +++ b/src/openai/types/shared_params/reasoning.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +from ..shared.reasoning_effort import ReasoningEffort + +__all__ = ["Reasoning"] + + +class Reasoning(TypedDict, total=False): + """**gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + effort: Optional[ReasoningEffort] + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. + Reducing reasoning effort can result in faster responses and fewer tokens used + on reasoning in a response. + + - `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported + reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool + calls are supported for all reasoning values in gpt-5.1. + - All models before `gpt-5.1` default to `medium` reasoning effort, and do not + support `none`. + - The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort. + - `xhigh` is supported for all models after `gpt-5.1-codex-max`. + """ + + generate_summary: Optional[Literal["auto", "concise", "detailed"]] + """**Deprecated:** use `summary` instead. + + A summary of the reasoning performed by the model. This can be useful for + debugging and understanding the model's reasoning process. One of `auto`, + `concise`, or `detailed`. + """ + + summary: Optional[Literal["auto", "concise", "detailed"]] + """A summary of the reasoning performed by the model. + + This can be useful for debugging and understanding the model's reasoning + process. One of `auto`, `concise`, or `detailed`. + + `concise` is supported for `computer-use-preview` models and all reasoning + models after `gpt-5`. + """ diff --git a/src/openai/types/shared_params/reasoning_effort.py b/src/openai/types/shared_params/reasoning_effort.py new file mode 100644 index 0000000000..8518c2b141 --- /dev/null +++ b/src/openai/types/shared_params/reasoning_effort.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypeAlias + +__all__ = ["ReasoningEffort"] + +ReasoningEffort: TypeAlias = Optional[Literal["none", "minimal", "low", "medium", "high", "xhigh"]] diff --git a/src/openai/types/shared_params/response_format_json_object.py b/src/openai/types/shared_params/response_format_json_object.py index 8419c6cb56..ef5d43be2e 100644 --- a/src/openai/types/shared_params/response_format_json_object.py +++ b/src/openai/types/shared_params/response_format_json_object.py @@ -8,5 +8,13 @@ class ResponseFormatJSONObject(TypedDict, total=False): + """JSON object response format. + + An older method of generating JSON responses. + Using `json_schema` is recommended for models that support it. Note that the + model will not generate JSON without a system or user message instructing it + to do so. + """ + type: Required[Literal["json_object"]] - """The type of response format being defined: `json_object`""" + """The type of response format being defined. Always `json_object`.""" diff --git a/src/openai/types/shared_params/response_format_json_schema.py b/src/openai/types/shared_params/response_format_json_schema.py index 4b60fae8ee..0a0e846873 100644 --- a/src/openai/types/shared_params/response_format_json_schema.py +++ b/src/openai/types/shared_params/response_format_json_schema.py @@ -9,6 +9,8 @@ class JSONSchema(TypedDict, total=False): + """Structured Outputs configuration options, including a JSON Schema.""" + name: Required[str] """The name of the response format. @@ -23,20 +25,30 @@ class JSONSchema(TypedDict, total=False): """ schema: Dict[str, object] - """The schema for the response format, described as a JSON Schema object.""" + """ + The schema for the response format, described as a JSON Schema object. Learn how + to build JSON schemas [here](https://json-schema.org/). + """ strict: Optional[bool] - """Whether to enable strict schema adherence when generating the output. - - If set to true, the model will always follow the exact schema defined in the - `schema` field. Only a subset of JSON Schema is supported when `strict` is - `true`. To learn more, read the + """ + Whether to enable strict schema adherence when generating the output. If set to + true, the model will always follow the exact schema defined in the `schema` + field. Only a subset of JSON Schema is supported when `strict` is `true`. To + learn more, read the [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). """ class ResponseFormatJSONSchema(TypedDict, total=False): + """JSON Schema response format. + + Used to generate structured JSON responses. + Learn more about [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs). + """ + json_schema: Required[JSONSchema] + """Structured Outputs configuration options, including a JSON Schema.""" type: Required[Literal["json_schema"]] - """The type of response format being defined: `json_schema`""" + """The type of response format being defined. Always `json_schema`.""" diff --git a/src/openai/types/shared_params/response_format_text.py b/src/openai/types/shared_params/response_format_text.py index 5bec7fc503..c195036f95 100644 --- a/src/openai/types/shared_params/response_format_text.py +++ b/src/openai/types/shared_params/response_format_text.py @@ -8,5 +8,7 @@ class ResponseFormatText(TypedDict, total=False): + """Default response format. Used to generate text responses.""" + type: Required[Literal["text"]] - """The type of response format being defined: `text`""" + """The type of response format being defined. Always `text`.""" diff --git a/src/openai/types/shared_params/responses_model.py b/src/openai/types/shared_params/responses_model.py new file mode 100644 index 0000000000..ad44dd6bf7 --- /dev/null +++ b/src/openai/types/shared_params/responses_model.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypeAlias + +from ..shared.chat_model import ChatModel + +__all__ = ["ResponsesModel"] + +ResponsesModel: TypeAlias = Union[ + str, + ChatModel, + Literal[ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max", + ], +] diff --git a/src/openai/types/skill.py b/src/openai/types/skill.py new file mode 100644 index 0000000000..25d74978d8 --- /dev/null +++ b/src/openai/types/skill.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["Skill"] + + +class Skill(BaseModel): + id: str + """Unique identifier for the skill.""" + + created_at: int + """Unix timestamp (seconds) for when the skill was created.""" + + default_version: str + """Default version for the skill.""" + + description: str + """Description of the skill.""" + + latest_version: str + """Latest version for the skill.""" + + name: str + """Name of the skill.""" + + object: Literal["skill"] + """The object type, which is `skill`.""" diff --git a/src/openai/types/skill_create_params.py b/src/openai/types/skill_create_params.py new file mode 100644 index 0000000000..5a709286a1 --- /dev/null +++ b/src/openai/types/skill_create_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypedDict + +from .._types import FileTypes, SequenceNotStr + +__all__ = ["SkillCreateParams"] + + +class SkillCreateParams(TypedDict, total=False): + files: Union[SequenceNotStr[FileTypes], FileTypes] + """Skill files to upload (directory upload) or a single zip file.""" diff --git a/src/openai/types/skill_list.py b/src/openai/types/skill_list.py new file mode 100644 index 0000000000..fc5b57bc26 --- /dev/null +++ b/src/openai/types/skill_list.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .skill import Skill +from .._models import BaseModel + +__all__ = ["SkillList"] + + +class SkillList(BaseModel): + data: List[Skill] + """A list of items""" + + first_id: Optional[str] = None + """The ID of the first item in the list.""" + + has_more: bool + """Whether there are more items available.""" + + last_id: Optional[str] = None + """The ID of the last item in the list.""" + + object: Literal["list"] + """The type of object returned, must be `list`.""" diff --git a/src/openai/types/skill_list_params.py b/src/openai/types/skill_list_params.py new file mode 100644 index 0000000000..ffe7967915 --- /dev/null +++ b/src/openai/types/skill_list_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["SkillListParams"] + + +class SkillListParams(TypedDict, total=False): + after: str + """Identifier for the last item from the previous pagination request""" + + limit: int + """Number of items to retrieve""" + + order: Literal["asc", "desc"] + """Sort order of results by timestamp. + + Use `asc` for ascending order or `desc` for descending order. + """ diff --git a/src/openai/types/skill_update_params.py b/src/openai/types/skill_update_params.py new file mode 100644 index 0000000000..48a790e1fc --- /dev/null +++ b/src/openai/types/skill_update_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["SkillUpdateParams"] + + +class SkillUpdateParams(TypedDict, total=False): + default_version: Required[str] + """The skill version number to set as default.""" diff --git a/src/openai/types/skills/__init__.py b/src/openai/types/skills/__init__.py new file mode 100644 index 0000000000..b0605fb85b --- /dev/null +++ b/src/openai/types/skills/__init__.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .skill_version import SkillVersion as SkillVersion +from .skill_version_list import SkillVersionList as SkillVersionList +from .version_list_params import VersionListParams as VersionListParams +from .deleted_skill_version import DeletedSkillVersion as DeletedSkillVersion +from .version_create_params import VersionCreateParams as VersionCreateParams diff --git a/src/openai/types/skills/deleted_skill_version.py b/src/openai/types/skills/deleted_skill_version.py new file mode 100644 index 0000000000..e1fd8c4985 --- /dev/null +++ b/src/openai/types/skills/deleted_skill_version.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["DeletedSkillVersion"] + + +class DeletedSkillVersion(BaseModel): + id: str + + deleted: bool + + object: Literal["skill.version.deleted"] + + version: str + """The deleted skill version.""" diff --git a/src/openai/types/skills/skill_version.py b/src/openai/types/skills/skill_version.py new file mode 100644 index 0000000000..74b47000a5 --- /dev/null +++ b/src/openai/types/skills/skill_version.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["SkillVersion"] + + +class SkillVersion(BaseModel): + id: str + """Unique identifier for the skill version.""" + + created_at: int + """Unix timestamp (seconds) for when the version was created.""" + + description: str + """Description of the skill version.""" + + name: str + """Name of the skill version.""" + + object: Literal["skill.version"] + """The object type, which is `skill.version`.""" + + skill_id: str + """Identifier of the skill for this version.""" + + version: str + """Version number for this skill.""" diff --git a/src/openai/types/skills/skill_version_list.py b/src/openai/types/skills/skill_version_list.py new file mode 100644 index 0000000000..7d10082f1c --- /dev/null +++ b/src/openai/types/skills/skill_version_list.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .skill_version import SkillVersion + +__all__ = ["SkillVersionList"] + + +class SkillVersionList(BaseModel): + data: List[SkillVersion] + """A list of items""" + + first_id: Optional[str] = None + """The ID of the first item in the list.""" + + has_more: bool + """Whether there are more items available.""" + + last_id: Optional[str] = None + """The ID of the last item in the list.""" + + object: Literal["list"] + """The type of object returned, must be `list`.""" diff --git a/src/openai/types/skills/version_create_params.py b/src/openai/types/skills/version_create_params.py new file mode 100644 index 0000000000..043b43a030 --- /dev/null +++ b/src/openai/types/skills/version_create_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import TypedDict + +from ..._types import FileTypes, SequenceNotStr + +__all__ = ["VersionCreateParams"] + + +class VersionCreateParams(TypedDict, total=False): + default: bool + """Whether to set this version as the default.""" + + files: Union[SequenceNotStr[FileTypes], FileTypes] + """Skill files to upload (directory upload) or a single zip file.""" diff --git a/src/openai/types/skills/version_list_params.py b/src/openai/types/skills/version_list_params.py new file mode 100644 index 0000000000..0638f40086 --- /dev/null +++ b/src/openai/types/skills/version_list_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["VersionListParams"] + + +class VersionListParams(TypedDict, total=False): + after: str + """The skill version ID to start after.""" + + limit: int + """Number of versions to retrieve.""" + + order: Literal["asc", "desc"] + """Sort order of results by version number.""" diff --git a/src/openai/types/skills/versions/__init__.py b/src/openai/types/skills/versions/__init__.py new file mode 100644 index 0000000000..f8ee8b14b1 --- /dev/null +++ b/src/openai/types/skills/versions/__init__.py @@ -0,0 +1,3 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations diff --git a/src/openai/types/beta/static_file_chunking_strategy.py b/src/openai/types/static_file_chunking_strategy.py similarity index 94% rename from src/openai/types/beta/static_file_chunking_strategy.py rename to src/openai/types/static_file_chunking_strategy.py index ba80e1a2b9..cb842442c1 100644 --- a/src/openai/types/beta/static_file_chunking_strategy.py +++ b/src/openai/types/static_file_chunking_strategy.py @@ -1,8 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - - -from ..._models import BaseModel +from .._models import BaseModel __all__ = ["StaticFileChunkingStrategy"] diff --git a/src/openai/types/beta/static_file_chunking_strategy_object.py b/src/openai/types/static_file_chunking_strategy_object.py similarity index 92% rename from src/openai/types/beta/static_file_chunking_strategy_object.py rename to src/openai/types/static_file_chunking_strategy_object.py index 896c4b8320..2a95dce5b3 100644 --- a/src/openai/types/beta/static_file_chunking_strategy_object.py +++ b/src/openai/types/static_file_chunking_strategy_object.py @@ -2,7 +2,7 @@ from typing_extensions import Literal -from ..._models import BaseModel +from .._models import BaseModel from .static_file_chunking_strategy import StaticFileChunkingStrategy __all__ = ["StaticFileChunkingStrategyObject"] diff --git a/src/openai/types/static_file_chunking_strategy_object_param.py b/src/openai/types/static_file_chunking_strategy_object_param.py new file mode 100644 index 0000000000..40188a41d5 --- /dev/null +++ b/src/openai/types/static_file_chunking_strategy_object_param.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .static_file_chunking_strategy_param import StaticFileChunkingStrategyParam + +__all__ = ["StaticFileChunkingStrategyObjectParam"] + + +class StaticFileChunkingStrategyObjectParam(TypedDict, total=False): + """Customize your own chunking strategy by setting chunk size and chunk overlap.""" + + static: Required[StaticFileChunkingStrategyParam] + + type: Required[Literal["static"]] + """Always `static`.""" diff --git a/src/openai/types/beta/static_file_chunking_strategy_param.py b/src/openai/types/static_file_chunking_strategy_param.py similarity index 100% rename from src/openai/types/beta/static_file_chunking_strategy_param.py rename to src/openai/types/static_file_chunking_strategy_param.py diff --git a/src/openai/types/upload.py b/src/openai/types/upload.py index 1cf8ee97f8..d248da6ee3 100644 --- a/src/openai/types/upload.py +++ b/src/openai/types/upload.py @@ -10,6 +10,8 @@ class Upload(BaseModel): + """The Upload object can accept byte chunks in the form of Parts.""" + id: str """The Upload unique identifier, which can be referenced in API endpoints.""" @@ -20,7 +22,7 @@ class Upload(BaseModel): """The Unix timestamp (in seconds) for when the Upload was created.""" expires_at: int - """The Unix timestamp (in seconds) for when the Upload was created.""" + """The Unix timestamp (in seconds) for when the Upload will expire.""" filename: str """The name of the file to be uploaded.""" @@ -39,4 +41,4 @@ class Upload(BaseModel): """The status of the Upload.""" file: Optional[FileObject] = None - """The ready File object after the Upload is completed.""" + """The `File` object represents a document that has been uploaded to OpenAI.""" diff --git a/src/openai/types/upload_complete_params.py b/src/openai/types/upload_complete_params.py index cce568d5c6..846a241dc7 100644 --- a/src/openai/types/upload_complete_params.py +++ b/src/openai/types/upload_complete_params.py @@ -2,14 +2,15 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr + __all__ = ["UploadCompleteParams"] class UploadCompleteParams(TypedDict, total=False): - part_ids: Required[List[str]] + part_ids: Required[SequenceNotStr[str]] """The ordered list of Part IDs.""" md5: str diff --git a/src/openai/types/upload_create_params.py b/src/openai/types/upload_create_params.py index 2ebabe6c66..c25d65bedd 100644 --- a/src/openai/types/upload_create_params.py +++ b/src/openai/types/upload_create_params.py @@ -2,11 +2,11 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import Literal, Required, TypedDict from .file_purpose import FilePurpose -__all__ = ["UploadCreateParams"] +__all__ = ["UploadCreateParams", "ExpiresAfter"] class UploadCreateParams(TypedDict, total=False): @@ -29,3 +29,29 @@ class UploadCreateParams(TypedDict, total=False): See the [documentation on File purposes](https://platform.openai.com/docs/api-reference/files/create#files-create-purpose). """ + + expires_after: ExpiresAfter + """The expiration policy for a file. + + By default, files with `purpose=batch` expire after 30 days and all other files + are persisted until they are manually deleted. + """ + + +class ExpiresAfter(TypedDict, total=False): + """The expiration policy for a file. + + By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted. + """ + + anchor: Required[Literal["created_at"]] + """Anchor timestamp after which the expiration policy applies. + + Supported anchors: `created_at`. + """ + + seconds: Required[int] + """The number of seconds after the anchor time that the file will expire. + + Must be between 3600 (1 hour) and 2592000 (30 days). + """ diff --git a/src/openai/types/uploads/upload_part.py b/src/openai/types/uploads/upload_part.py index e09621d8f9..e585b1a227 100644 --- a/src/openai/types/uploads/upload_part.py +++ b/src/openai/types/uploads/upload_part.py @@ -8,6 +8,8 @@ class UploadPart(BaseModel): + """The upload Part represents a chunk of bytes we can add to an Upload object.""" + id: str """The upload Part unique identifier, which can be referenced in API endpoints.""" diff --git a/src/openai/types/beta/vector_store.py b/src/openai/types/vector_store.py similarity index 82% rename from src/openai/types/beta/vector_store.py rename to src/openai/types/vector_store.py index 488961b444..82899ecd1b 100644 --- a/src/openai/types/beta/vector_store.py +++ b/src/openai/types/vector_store.py @@ -3,7 +3,8 @@ from typing import Optional from typing_extensions import Literal -from ..._models import BaseModel +from .._models import BaseModel +from .shared.metadata import Metadata __all__ = ["VectorStore", "FileCounts", "ExpiresAfter"] @@ -26,6 +27,8 @@ class FileCounts(BaseModel): class ExpiresAfter(BaseModel): + """The expiration policy for a vector store.""" + anchor: Literal["last_active_at"] """Anchor timestamp after which the expiration policy applies. @@ -37,6 +40,10 @@ class ExpiresAfter(BaseModel): class VectorStore(BaseModel): + """ + A vector store is a collection of processed files can be used by the `file_search` tool. + """ + id: str """The identifier, which can be referenced in API endpoints.""" @@ -48,12 +55,14 @@ class VectorStore(BaseModel): last_active_at: Optional[int] = None """The Unix timestamp (in seconds) for when the vector store was last active.""" - metadata: Optional[object] = None + metadata: Optional[Metadata] = None """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ name: str diff --git a/src/openai/types/beta/vector_store_create_params.py b/src/openai/types/vector_store_create_params.py similarity index 71% rename from src/openai/types/beta/vector_store_create_params.py rename to src/openai/types/vector_store_create_params.py index a8f03a89b9..2b72562984 100644 --- a/src/openai/types/beta/vector_store_create_params.py +++ b/src/openai/types/vector_store_create_params.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import List, Optional +from typing import Optional from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr +from .shared_params.metadata import Metadata from .file_chunking_strategy_param import FileChunkingStrategyParam __all__ = ["VectorStoreCreateParams", "ExpiresAfter"] @@ -18,22 +20,30 @@ class VectorStoreCreateParams(TypedDict, total=False): non-empty. """ + description: str + """A description for the vector store. + + Can be used to describe the vector store's purpose. + """ + expires_after: ExpiresAfter """The expiration policy for a vector store.""" - file_ids: List[str] + file_ids: SequenceNotStr[str] """ A list of [File](https://platform.openai.com/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files. """ - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ name: str @@ -41,6 +51,8 @@ class VectorStoreCreateParams(TypedDict, total=False): class ExpiresAfter(TypedDict, total=False): + """The expiration policy for a vector store.""" + anchor: Required[Literal["last_active_at"]] """Anchor timestamp after which the expiration policy applies. diff --git a/src/openai/types/beta/vector_store_deleted.py b/src/openai/types/vector_store_deleted.py similarity index 89% rename from src/openai/types/beta/vector_store_deleted.py rename to src/openai/types/vector_store_deleted.py index 21ccda1db5..dfac9ce8bd 100644 --- a/src/openai/types/beta/vector_store_deleted.py +++ b/src/openai/types/vector_store_deleted.py @@ -2,7 +2,7 @@ from typing_extensions import Literal -from ..._models import BaseModel +from .._models import BaseModel __all__ = ["VectorStoreDeleted"] diff --git a/src/openai/types/beta/vector_store_list_params.py b/src/openai/types/vector_store_list_params.py similarity index 93% rename from src/openai/types/beta/vector_store_list_params.py rename to src/openai/types/vector_store_list_params.py index f39f67266d..e26ff90a85 100644 --- a/src/openai/types/beta/vector_store_list_params.py +++ b/src/openai/types/vector_store_list_params.py @@ -21,7 +21,7 @@ class VectorStoreListParams(TypedDict, total=False): """A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if - you make a list request and receive 100 objects, ending with obj_foo, your + you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list. """ diff --git a/src/openai/types/vector_store_search_params.py b/src/openai/types/vector_store_search_params.py new file mode 100644 index 0000000000..851d63c5d1 --- /dev/null +++ b/src/openai/types/vector_store_search_params.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .._types import SequenceNotStr +from .shared_params.compound_filter import CompoundFilter +from .shared_params.comparison_filter import ComparisonFilter + +__all__ = ["VectorStoreSearchParams", "Filters", "RankingOptions"] + + +class VectorStoreSearchParams(TypedDict, total=False): + query: Required[Union[str, SequenceNotStr[str]]] + """A query string for a search""" + + filters: Filters + """A filter to apply based on file attributes.""" + + max_num_results: int + """The maximum number of results to return. + + This number should be between 1 and 50 inclusive. + """ + + ranking_options: RankingOptions + """Ranking options for search.""" + + rewrite_query: bool + """Whether to rewrite the natural language query for vector search.""" + + +Filters: TypeAlias = Union[ComparisonFilter, CompoundFilter] + + +class RankingOptions(TypedDict, total=False): + """Ranking options for search.""" + + ranker: Literal["none", "auto", "default-2024-11-15"] + """Enable re-ranking; set to `none` to disable, which can help reduce latency.""" + + score_threshold: float diff --git a/src/openai/types/vector_store_search_response.py b/src/openai/types/vector_store_search_response.py new file mode 100644 index 0000000000..d78b71bfba --- /dev/null +++ b/src/openai/types/vector_store_search_response.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["VectorStoreSearchResponse", "Content"] + + +class Content(BaseModel): + text: str + """The text content returned from search.""" + + type: Literal["text"] + """The type of content.""" + + +class VectorStoreSearchResponse(BaseModel): + attributes: Optional[Dict[str, Union[str, float, bool]]] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. Keys are + strings with a maximum length of 64 characters. Values are strings with a + maximum length of 512 characters, booleans, or numbers. + """ + + content: List[Content] + """Content chunks from the file.""" + + file_id: str + """The ID of the vector store file.""" + + filename: str + """The name of the vector store file.""" + + score: float + """The similarity score for the result.""" diff --git a/src/openai/types/beta/vector_store_update_params.py b/src/openai/types/vector_store_update_params.py similarity index 74% rename from src/openai/types/beta/vector_store_update_params.py rename to src/openai/types/vector_store_update_params.py index 0f9593e476..7c6f891170 100644 --- a/src/openai/types/beta/vector_store_update_params.py +++ b/src/openai/types/vector_store_update_params.py @@ -5,6 +5,8 @@ from typing import Optional from typing_extensions import Literal, Required, TypedDict +from .shared_params.metadata import Metadata + __all__ = ["VectorStoreUpdateParams", "ExpiresAfter"] @@ -12,12 +14,14 @@ class VectorStoreUpdateParams(TypedDict, total=False): expires_after: Optional[ExpiresAfter] """The expiration policy for a vector store.""" - metadata: Optional[object] + metadata: Optional[Metadata] """Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a - structured format. Keys can be a maximum of 64 characters long and values can be - a maxium of 512 characters long. + structured format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings with + a maximum length of 512 characters. """ name: Optional[str] @@ -25,6 +29,8 @@ class VectorStoreUpdateParams(TypedDict, total=False): class ExpiresAfter(TypedDict, total=False): + """The expiration policy for a vector store.""" + anchor: Required[Literal["last_active_at"]] """Anchor timestamp after which the expiration policy applies. diff --git a/src/openai/types/beta/vector_stores/__init__.py b/src/openai/types/vector_stores/__init__.py similarity index 82% rename from src/openai/types/beta/vector_stores/__init__.py rename to src/openai/types/vector_stores/__init__.py index ff05dd63d8..96ce301481 100644 --- a/src/openai/types/beta/vector_stores/__init__.py +++ b/src/openai/types/vector_stores/__init__.py @@ -5,6 +5,8 @@ from .file_list_params import FileListParams as FileListParams from .vector_store_file import VectorStoreFile as VectorStoreFile from .file_create_params import FileCreateParams as FileCreateParams +from .file_update_params import FileUpdateParams as FileUpdateParams +from .file_content_response import FileContentResponse as FileContentResponse from .vector_store_file_batch import VectorStoreFileBatch as VectorStoreFileBatch from .file_batch_create_params import FileBatchCreateParams as FileBatchCreateParams from .vector_store_file_deleted import VectorStoreFileDeleted as VectorStoreFileDeleted diff --git a/src/openai/types/vector_stores/file_batch_create_params.py b/src/openai/types/vector_stores/file_batch_create_params.py new file mode 100644 index 0000000000..1e578888c5 --- /dev/null +++ b/src/openai/types/vector_stores/file_batch_create_params.py @@ -0,0 +1,76 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Required, TypedDict + +from ..._types import SequenceNotStr +from ..file_chunking_strategy_param import FileChunkingStrategyParam + +__all__ = ["FileBatchCreateParams", "File"] + + +class FileBatchCreateParams(TypedDict, total=False): + attributes: Optional[Dict[str, Union[str, float, bool]]] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. Keys are + strings with a maximum length of 64 characters. Values are strings with a + maximum length of 512 characters, booleans, or numbers. + """ + + chunking_strategy: FileChunkingStrategyParam + """The chunking strategy used to chunk the file(s). + + If not set, will use the `auto` strategy. Only applicable if `file_ids` is + non-empty. + """ + + file_ids: SequenceNotStr[str] + """ + A list of [File](https://platform.openai.com/docs/api-reference/files) IDs that + the vector store should use. Useful for tools like `file_search` that can access + files. If `attributes` or `chunking_strategy` are provided, they will be applied + to all files in the batch. The maximum batch size is 2000 files. This endpoint + is recommended for multi-file ingestion and helps reduce per-vector-store write + request pressure. Mutually exclusive with `files`. + """ + + files: Iterable[File] + """ + A list of objects that each include a `file_id` plus optional `attributes` or + `chunking_strategy`. Use this when you need to override metadata for specific + files. The global `attributes` or `chunking_strategy` will be ignored and must + be specified for each file. The maximum batch size is 2000 files. This endpoint + is recommended for multi-file ingestion and helps reduce per-vector-store write + request pressure. Mutually exclusive with `file_ids`. + """ + + +class File(TypedDict, total=False): + file_id: Required[str] + """ + A [File](https://platform.openai.com/docs/api-reference/files) ID that the + vector store should use. Useful for tools like `file_search` that can access + files. For multi-file ingestion, we recommend + [`file_batches`](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/createBatch) + to minimize per-vector-store write requests. + """ + + attributes: Optional[Dict[str, Union[str, float, bool]]] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. Keys are + strings with a maximum length of 64 characters. Values are strings with a + maximum length of 512 characters, booleans, or numbers. + """ + + chunking_strategy: FileChunkingStrategyParam + """The chunking strategy used to chunk the file(s). + + If not set, will use the `auto` strategy. Only applicable if `file_ids` is + non-empty. + """ diff --git a/src/openai/types/beta/vector_stores/file_batch_list_files_params.py b/src/openai/types/vector_stores/file_batch_list_files_params.py similarity index 94% rename from src/openai/types/beta/vector_stores/file_batch_list_files_params.py rename to src/openai/types/vector_stores/file_batch_list_files_params.py index 24dee7d5a5..2a0a6c6aa7 100644 --- a/src/openai/types/beta/vector_stores/file_batch_list_files_params.py +++ b/src/openai/types/vector_stores/file_batch_list_files_params.py @@ -23,7 +23,7 @@ class FileBatchListFilesParams(TypedDict, total=False): """A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if - you make a list request and receive 100 objects, ending with obj_foo, your + you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list. """ diff --git a/src/openai/types/vector_stores/file_content_response.py b/src/openai/types/vector_stores/file_content_response.py new file mode 100644 index 0000000000..32db2f2ce9 --- /dev/null +++ b/src/openai/types/vector_stores/file_content_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["FileContentResponse"] + + +class FileContentResponse(BaseModel): + text: Optional[str] = None + """The text content""" + + type: Optional[str] = None + """The content type (currently only `"text"`)""" diff --git a/src/openai/types/beta/vector_stores/file_create_params.py b/src/openai/types/vector_stores/file_create_params.py similarity index 51% rename from src/openai/types/beta/vector_stores/file_create_params.py rename to src/openai/types/vector_stores/file_create_params.py index d074d766e6..530adee8f6 100644 --- a/src/openai/types/beta/vector_stores/file_create_params.py +++ b/src/openai/types/vector_stores/file_create_params.py @@ -2,6 +2,7 @@ from __future__ import annotations +from typing import Dict, Union, Optional from typing_extensions import Required, TypedDict from ..file_chunking_strategy_param import FileChunkingStrategyParam @@ -14,7 +15,18 @@ class FileCreateParams(TypedDict, total=False): """ A [File](https://platform.openai.com/docs/api-reference/files) ID that the vector store should use. Useful for tools like `file_search` that can access - files. + files. For multi-file ingestion, we recommend + [`file_batches`](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/createBatch) + to minimize per-vector-store write requests. + """ + + attributes: Optional[Dict[str, Union[str, float, bool]]] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. Keys are + strings with a maximum length of 64 characters. Values are strings with a + maximum length of 512 characters, booleans, or numbers. """ chunking_strategy: FileChunkingStrategyParam diff --git a/src/openai/types/beta/vector_stores/file_list_params.py b/src/openai/types/vector_stores/file_list_params.py similarity index 94% rename from src/openai/types/beta/vector_stores/file_list_params.py rename to src/openai/types/vector_stores/file_list_params.py index 23dd7f0d94..867b5fb3bb 100644 --- a/src/openai/types/beta/vector_stores/file_list_params.py +++ b/src/openai/types/vector_stores/file_list_params.py @@ -21,7 +21,7 @@ class FileListParams(TypedDict, total=False): """A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if - you make a list request and receive 100 objects, ending with obj_foo, your + you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list. """ diff --git a/src/openai/types/vector_stores/file_update_params.py b/src/openai/types/vector_stores/file_update_params.py new file mode 100644 index 0000000000..ebf540d046 --- /dev/null +++ b/src/openai/types/vector_stores/file_update_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from typing_extensions import Required, TypedDict + +__all__ = ["FileUpdateParams"] + + +class FileUpdateParams(TypedDict, total=False): + vector_store_id: Required[str] + + attributes: Required[Optional[Dict[str, Union[str, float, bool]]]] + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. Keys are + strings with a maximum length of 64 characters. Values are strings with a + maximum length of 512 characters, booleans, or numbers. + """ diff --git a/src/openai/types/beta/vector_stores/vector_store_file.py b/src/openai/types/vector_stores/vector_store_file.py similarity index 68% rename from src/openai/types/beta/vector_stores/vector_store_file.py rename to src/openai/types/vector_stores/vector_store_file.py index e4608e159c..c1ea02227f 100644 --- a/src/openai/types/beta/vector_stores/vector_store_file.py +++ b/src/openai/types/vector_stores/vector_store_file.py @@ -1,23 +1,30 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import Dict, Union, Optional from typing_extensions import Literal -from ...._models import BaseModel +from ..._models import BaseModel from ..file_chunking_strategy import FileChunkingStrategy __all__ = ["VectorStoreFile", "LastError"] class LastError(BaseModel): + """The last error associated with this vector store file. + + Will be `null` if there are no errors. + """ + code: Literal["server_error", "unsupported_file", "invalid_file"] - """One of `server_error` or `rate_limit_exceeded`.""" + """One of `server_error`, `unsupported_file`, or `invalid_file`.""" message: str """A human-readable description of the error.""" class VectorStoreFile(BaseModel): + """A list of files attached to a vector store.""" + id: str """The identifier, which can be referenced in API endpoints.""" @@ -54,5 +61,14 @@ class VectorStoreFile(BaseModel): attached to. """ + attributes: Optional[Dict[str, Union[str, float, bool]]] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. Keys are + strings with a maximum length of 64 characters. Values are strings with a + maximum length of 512 characters, booleans, or numbers. + """ + chunking_strategy: Optional[FileChunkingStrategy] = None """The strategy used to chunk the file.""" diff --git a/src/openai/types/beta/vector_stores/vector_store_file_batch.py b/src/openai/types/vector_stores/vector_store_file_batch.py similarity index 94% rename from src/openai/types/beta/vector_stores/vector_store_file_batch.py rename to src/openai/types/vector_stores/vector_store_file_batch.py index df130a58de..b07eb25da5 100644 --- a/src/openai/types/beta/vector_stores/vector_store_file_batch.py +++ b/src/openai/types/vector_stores/vector_store_file_batch.py @@ -2,7 +2,7 @@ from typing_extensions import Literal -from ...._models import BaseModel +from ..._models import BaseModel __all__ = ["VectorStoreFileBatch", "FileCounts"] @@ -25,6 +25,8 @@ class FileCounts(BaseModel): class VectorStoreFileBatch(BaseModel): + """A batch of files attached to a vector store.""" + id: str """The identifier, which can be referenced in API endpoints.""" diff --git a/src/openai/types/beta/vector_stores/vector_store_file_deleted.py b/src/openai/types/vector_stores/vector_store_file_deleted.py similarity index 89% rename from src/openai/types/beta/vector_stores/vector_store_file_deleted.py rename to src/openai/types/vector_stores/vector_store_file_deleted.py index ae37f84364..5c856f26cd 100644 --- a/src/openai/types/beta/vector_stores/vector_store_file_deleted.py +++ b/src/openai/types/vector_stores/vector_store_file_deleted.py @@ -2,7 +2,7 @@ from typing_extensions import Literal -from ...._models import BaseModel +from ..._models import BaseModel __all__ = ["VectorStoreFileDeleted"] diff --git a/src/openai/types/video.py b/src/openai/types/video.py new file mode 100644 index 0000000000..051b951ede --- /dev/null +++ b/src/openai/types/video.py @@ -0,0 +1,58 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .video_size import VideoSize +from .video_model import VideoModel +from .video_seconds import VideoSeconds +from .video_create_error import VideoCreateError + +__all__ = ["Video"] + + +class Video(BaseModel): + """Structured information describing a generated video job.""" + + id: str + """Unique identifier for the video job.""" + + completed_at: Optional[int] = None + """Unix timestamp (seconds) for when the job completed, if finished.""" + + created_at: int + """Unix timestamp (seconds) for when the job was created.""" + + error: Optional[VideoCreateError] = None + """Error payload that explains why generation failed, if applicable.""" + + expires_at: Optional[int] = None + """Unix timestamp (seconds) for when the downloadable assets expire, if set.""" + + model: VideoModel + """The video generation model that produced the job.""" + + object: Literal["video"] + """The object type, which is always `video`.""" + + progress: int + """Approximate completion percentage for the generation task.""" + + prompt: Optional[str] = None + """The prompt that was used to generate the video.""" + + remixed_from_video_id: Optional[str] = None + """Identifier of the source video if this video is a remix.""" + + seconds: Union[str, VideoSeconds] + """Duration of the generated clip in seconds. + + For extensions, this is the stitched total duration. + """ + + size: VideoSize + """The resolution of the generated video.""" + + status: Literal["queued", "in_progress", "completed", "failed"] + """Current lifecycle status of the video job.""" diff --git a/src/openai/types/video_create_character_params.py b/src/openai/types/video_create_character_params.py new file mode 100644 index 0000000000..ef671e598f --- /dev/null +++ b/src/openai/types/video_create_character_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from .._types import FileTypes + +__all__ = ["VideoCreateCharacterParams"] + + +class VideoCreateCharacterParams(TypedDict, total=False): + name: Required[str] + """Display name for this API character.""" + + video: Required[FileTypes] + """Video file used to create a character.""" diff --git a/src/openai/types/video_create_character_response.py b/src/openai/types/video_create_character_response.py new file mode 100644 index 0000000000..e3a65a0200 --- /dev/null +++ b/src/openai/types/video_create_character_response.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VideoCreateCharacterResponse"] + + +class VideoCreateCharacterResponse(BaseModel): + id: Optional[str] = None + """Identifier for the character creation cameo.""" + + created_at: int + """Unix timestamp (in seconds) when the character was created.""" + + name: Optional[str] = None + """Display name for the character.""" diff --git a/src/openai/types/video_create_error.py b/src/openai/types/video_create_error.py new file mode 100644 index 0000000000..7f520220c8 --- /dev/null +++ b/src/openai/types/video_create_error.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["VideoCreateError"] + + +class VideoCreateError(BaseModel): + """An error that occurred while generating the response.""" + + code: str + """A machine-readable error code that was returned.""" + + message: str + """A human-readable description of the error that was returned.""" diff --git a/src/openai/types/video_create_params.py b/src/openai/types/video_create_params.py new file mode 100644 index 0000000000..641ac7db45 --- /dev/null +++ b/src/openai/types/video_create_params.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Required, TypeAlias, TypedDict + +from .._types import FileTypes +from .video_size import VideoSize +from .video_seconds import VideoSeconds +from .video_model_param import VideoModelParam +from .image_input_reference_param import ImageInputReferenceParam + +__all__ = ["VideoCreateParams", "InputReference"] + + +class VideoCreateParams(TypedDict, total=False): + prompt: Required[str] + """Text prompt that describes the video to generate.""" + + input_reference: InputReference + """Optional reference asset upload or reference object that guides generation.""" + + model: VideoModelParam + """The video generation model to use (allowed values: sora-2, sora-2-pro). + + Defaults to `sora-2`. + """ + + seconds: VideoSeconds + """Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds.""" + + size: VideoSize + """ + Output resolution formatted as width x height (allowed values: 720x1280, + 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280. + """ + + +InputReference: TypeAlias = Union[FileTypes, ImageInputReferenceParam] diff --git a/src/openai/types/video_delete_response.py b/src/openai/types/video_delete_response.py new file mode 100644 index 0000000000..1ed543aec8 --- /dev/null +++ b/src/openai/types/video_delete_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["VideoDeleteResponse"] + + +class VideoDeleteResponse(BaseModel): + """Confirmation payload returned after deleting a video.""" + + id: str + """Identifier of the deleted video.""" + + deleted: bool + """Indicates that the video resource was deleted.""" + + object: Literal["video.deleted"] + """The object type that signals the deletion response.""" diff --git a/src/openai/types/video_download_content_params.py b/src/openai/types/video_download_content_params.py new file mode 100644 index 0000000000..8c113d6715 --- /dev/null +++ b/src/openai/types/video_download_content_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["VideoDownloadContentParams"] + + +class VideoDownloadContentParams(TypedDict, total=False): + variant: Literal["video", "thumbnail", "spritesheet"] + """Which downloadable asset to return. Defaults to the MP4 video.""" diff --git a/src/openai/types/video_edit_params.py b/src/openai/types/video_edit_params.py new file mode 100644 index 0000000000..8d3b15fc6f --- /dev/null +++ b/src/openai/types/video_edit_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Required, TypeAlias, TypedDict + +from .._types import FileTypes + +__all__ = ["VideoEditParams", "Video", "VideoVideoReferenceInputParam"] + + +class VideoEditParams(TypedDict, total=False): + prompt: Required[str] + """Text prompt that describes how to edit the source video.""" + + video: Required[Video] + """Reference to the completed video to edit.""" + + +class VideoVideoReferenceInputParam(TypedDict, total=False): + """Reference to the completed video.""" + + id: Required[str] + """The identifier of the completed video.""" + + +Video: TypeAlias = Union[FileTypes, VideoVideoReferenceInputParam] diff --git a/src/openai/types/video_extend_params.py b/src/openai/types/video_extend_params.py new file mode 100644 index 0000000000..65be4b5270 --- /dev/null +++ b/src/openai/types/video_extend_params.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Required, TypeAlias, TypedDict + +from .._types import FileTypes +from .video_seconds import VideoSeconds + +__all__ = ["VideoExtendParams", "Video", "VideoVideoReferenceInputParam"] + + +class VideoExtendParams(TypedDict, total=False): + prompt: Required[str] + """Updated text prompt that directs the extension generation.""" + + seconds: Required[VideoSeconds] + """ + Length of the newly generated extension segment in seconds (allowed values: 4, + 8, 12, 16, 20). + """ + + video: Required[Video] + """Reference to the completed video to extend.""" + + +class VideoVideoReferenceInputParam(TypedDict, total=False): + """Reference to the completed video.""" + + id: Required[str] + """The identifier of the completed video.""" + + +Video: TypeAlias = Union[FileTypes, VideoVideoReferenceInputParam] diff --git a/src/openai/types/video_get_character_response.py b/src/openai/types/video_get_character_response.py new file mode 100644 index 0000000000..df6202ed03 --- /dev/null +++ b/src/openai/types/video_get_character_response.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VideoGetCharacterResponse"] + + +class VideoGetCharacterResponse(BaseModel): + id: Optional[str] = None + """Identifier for the character creation cameo.""" + + created_at: int + """Unix timestamp (in seconds) when the character was created.""" + + name: Optional[str] = None + """Display name for the character.""" diff --git a/src/openai/types/video_list_params.py b/src/openai/types/video_list_params.py new file mode 100644 index 0000000000..bf55ba7fa2 --- /dev/null +++ b/src/openai/types/video_list_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["VideoListParams"] + + +class VideoListParams(TypedDict, total=False): + after: str + """Identifier for the last item from the previous pagination request""" + + limit: int + """Number of items to retrieve""" + + order: Literal["asc", "desc"] + """Sort order of results by timestamp. + + Use `asc` for ascending order or `desc` for descending order. + """ diff --git a/src/openai/types/video_model.py b/src/openai/types/video_model.py new file mode 100644 index 0000000000..29d8cb16eb --- /dev/null +++ b/src/openai/types/video_model.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Literal, TypeAlias + +__all__ = ["VideoModel"] + +VideoModel: TypeAlias = Union[ + str, Literal["sora-2", "sora-2-pro", "sora-2-2025-10-06", "sora-2-pro-2025-10-06", "sora-2-2025-12-08"] +] diff --git a/src/openai/types/video_model_param.py b/src/openai/types/video_model_param.py new file mode 100644 index 0000000000..4310b8d057 --- /dev/null +++ b/src/openai/types/video_model_param.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, TypeAlias + +__all__ = ["VideoModelParam"] + +VideoModelParam: TypeAlias = Union[ + str, Literal["sora-2", "sora-2-pro", "sora-2-2025-10-06", "sora-2-pro-2025-10-06", "sora-2-2025-12-08"] +] diff --git a/src/openai/types/video_remix_params.py b/src/openai/types/video_remix_params.py new file mode 100644 index 0000000000..15388d6172 --- /dev/null +++ b/src/openai/types/video_remix_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["VideoRemixParams"] + + +class VideoRemixParams(TypedDict, total=False): + prompt: Required[str] + """Updated text prompt that directs the remix generation.""" diff --git a/src/openai/types/video_seconds.py b/src/openai/types/video_seconds.py new file mode 100644 index 0000000000..e50d37dc51 --- /dev/null +++ b/src/openai/types/video_seconds.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["VideoSeconds"] + +VideoSeconds: TypeAlias = Literal["4", "8", "12"] diff --git a/src/openai/types/video_size.py b/src/openai/types/video_size.py new file mode 100644 index 0000000000..215ac8815a --- /dev/null +++ b/src/openai/types/video_size.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["VideoSize"] + +VideoSize: TypeAlias = Literal["720x1280", "1280x720", "1024x1792", "1792x1024"] diff --git a/src/openai/types/webhooks/__init__.py b/src/openai/types/webhooks/__init__.py new file mode 100644 index 0000000000..8b9e55653b --- /dev/null +++ b/src/openai/types/webhooks/__init__.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent +from .batch_failed_webhook_event import BatchFailedWebhookEvent as BatchFailedWebhookEvent +from .batch_expired_webhook_event import BatchExpiredWebhookEvent as BatchExpiredWebhookEvent +from .batch_cancelled_webhook_event import BatchCancelledWebhookEvent as BatchCancelledWebhookEvent +from .batch_completed_webhook_event import BatchCompletedWebhookEvent as BatchCompletedWebhookEvent +from .eval_run_failed_webhook_event import EvalRunFailedWebhookEvent as EvalRunFailedWebhookEvent +from .response_failed_webhook_event import ResponseFailedWebhookEvent as ResponseFailedWebhookEvent +from .eval_run_canceled_webhook_event import EvalRunCanceledWebhookEvent as EvalRunCanceledWebhookEvent +from .eval_run_succeeded_webhook_event import EvalRunSucceededWebhookEvent as EvalRunSucceededWebhookEvent +from .response_cancelled_webhook_event import ResponseCancelledWebhookEvent as ResponseCancelledWebhookEvent +from .response_completed_webhook_event import ResponseCompletedWebhookEvent as ResponseCompletedWebhookEvent +from .response_incomplete_webhook_event import ResponseIncompleteWebhookEvent as ResponseIncompleteWebhookEvent +from .fine_tuning_job_failed_webhook_event import FineTuningJobFailedWebhookEvent as FineTuningJobFailedWebhookEvent +from .realtime_call_incoming_webhook_event import RealtimeCallIncomingWebhookEvent as RealtimeCallIncomingWebhookEvent +from .fine_tuning_job_cancelled_webhook_event import ( + FineTuningJobCancelledWebhookEvent as FineTuningJobCancelledWebhookEvent, +) +from .fine_tuning_job_succeeded_webhook_event import ( + FineTuningJobSucceededWebhookEvent as FineTuningJobSucceededWebhookEvent, +) diff --git a/src/openai/types/webhooks/batch_cancelled_webhook_event.py b/src/openai/types/webhooks/batch_cancelled_webhook_event.py new file mode 100644 index 0000000000..9d1c485f5e --- /dev/null +++ b/src/openai/types/webhooks/batch_cancelled_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["BatchCancelledWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the batch API request.""" + + +class BatchCancelledWebhookEvent(BaseModel): + """Sent when a batch API request has been cancelled.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the batch API request was cancelled.""" + + data: Data + """Event data payload.""" + + type: Literal["batch.cancelled"] + """The type of the event. Always `batch.cancelled`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/batch_completed_webhook_event.py b/src/openai/types/webhooks/batch_completed_webhook_event.py new file mode 100644 index 0000000000..5ae8191789 --- /dev/null +++ b/src/openai/types/webhooks/batch_completed_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["BatchCompletedWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the batch API request.""" + + +class BatchCompletedWebhookEvent(BaseModel): + """Sent when a batch API request has been completed.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the batch API request was completed.""" + + data: Data + """Event data payload.""" + + type: Literal["batch.completed"] + """The type of the event. Always `batch.completed`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/batch_expired_webhook_event.py b/src/openai/types/webhooks/batch_expired_webhook_event.py new file mode 100644 index 0000000000..2f08a7f579 --- /dev/null +++ b/src/openai/types/webhooks/batch_expired_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["BatchExpiredWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the batch API request.""" + + +class BatchExpiredWebhookEvent(BaseModel): + """Sent when a batch API request has expired.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the batch API request expired.""" + + data: Data + """Event data payload.""" + + type: Literal["batch.expired"] + """The type of the event. Always `batch.expired`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/batch_failed_webhook_event.py b/src/openai/types/webhooks/batch_failed_webhook_event.py new file mode 100644 index 0000000000..7166616588 --- /dev/null +++ b/src/openai/types/webhooks/batch_failed_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["BatchFailedWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the batch API request.""" + + +class BatchFailedWebhookEvent(BaseModel): + """Sent when a batch API request has failed.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the batch API request failed.""" + + data: Data + """Event data payload.""" + + type: Literal["batch.failed"] + """The type of the event. Always `batch.failed`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/eval_run_canceled_webhook_event.py b/src/openai/types/webhooks/eval_run_canceled_webhook_event.py new file mode 100644 index 0000000000..1948f8933b --- /dev/null +++ b/src/openai/types/webhooks/eval_run_canceled_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["EvalRunCanceledWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the eval run.""" + + +class EvalRunCanceledWebhookEvent(BaseModel): + """Sent when an eval run has been canceled.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the eval run was canceled.""" + + data: Data + """Event data payload.""" + + type: Literal["eval.run.canceled"] + """The type of the event. Always `eval.run.canceled`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/eval_run_failed_webhook_event.py b/src/openai/types/webhooks/eval_run_failed_webhook_event.py new file mode 100644 index 0000000000..4e4c860abc --- /dev/null +++ b/src/openai/types/webhooks/eval_run_failed_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["EvalRunFailedWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the eval run.""" + + +class EvalRunFailedWebhookEvent(BaseModel): + """Sent when an eval run has failed.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the eval run failed.""" + + data: Data + """Event data payload.""" + + type: Literal["eval.run.failed"] + """The type of the event. Always `eval.run.failed`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/eval_run_succeeded_webhook_event.py b/src/openai/types/webhooks/eval_run_succeeded_webhook_event.py new file mode 100644 index 0000000000..c20f22eeb9 --- /dev/null +++ b/src/openai/types/webhooks/eval_run_succeeded_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["EvalRunSucceededWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the eval run.""" + + +class EvalRunSucceededWebhookEvent(BaseModel): + """Sent when an eval run has succeeded.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the eval run succeeded.""" + + data: Data + """Event data payload.""" + + type: Literal["eval.run.succeeded"] + """The type of the event. Always `eval.run.succeeded`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/fine_tuning_job_cancelled_webhook_event.py b/src/openai/types/webhooks/fine_tuning_job_cancelled_webhook_event.py new file mode 100644 index 0000000000..0cfff85dad --- /dev/null +++ b/src/openai/types/webhooks/fine_tuning_job_cancelled_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FineTuningJobCancelledWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the fine-tuning job.""" + + +class FineTuningJobCancelledWebhookEvent(BaseModel): + """Sent when a fine-tuning job has been cancelled.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the fine-tuning job was cancelled.""" + + data: Data + """Event data payload.""" + + type: Literal["fine_tuning.job.cancelled"] + """The type of the event. Always `fine_tuning.job.cancelled`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/fine_tuning_job_failed_webhook_event.py b/src/openai/types/webhooks/fine_tuning_job_failed_webhook_event.py new file mode 100644 index 0000000000..0eb6bf954f --- /dev/null +++ b/src/openai/types/webhooks/fine_tuning_job_failed_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FineTuningJobFailedWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the fine-tuning job.""" + + +class FineTuningJobFailedWebhookEvent(BaseModel): + """Sent when a fine-tuning job has failed.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the fine-tuning job failed.""" + + data: Data + """Event data payload.""" + + type: Literal["fine_tuning.job.failed"] + """The type of the event. Always `fine_tuning.job.failed`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/fine_tuning_job_succeeded_webhook_event.py b/src/openai/types/webhooks/fine_tuning_job_succeeded_webhook_event.py new file mode 100644 index 0000000000..26b5ea8955 --- /dev/null +++ b/src/openai/types/webhooks/fine_tuning_job_succeeded_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FineTuningJobSucceededWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the fine-tuning job.""" + + +class FineTuningJobSucceededWebhookEvent(BaseModel): + """Sent when a fine-tuning job has succeeded.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the fine-tuning job succeeded.""" + + data: Data + """Event data payload.""" + + type: Literal["fine_tuning.job.succeeded"] + """The type of the event. Always `fine_tuning.job.succeeded`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/realtime_call_incoming_webhook_event.py b/src/openai/types/webhooks/realtime_call_incoming_webhook_event.py new file mode 100644 index 0000000000..4647a2e2ba --- /dev/null +++ b/src/openai/types/webhooks/realtime_call_incoming_webhook_event.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RealtimeCallIncomingWebhookEvent", "Data", "DataSipHeader"] + + +class DataSipHeader(BaseModel): + """A header from the SIP Invite.""" + + name: str + """Name of the SIP Header.""" + + value: str + """Value of the SIP Header.""" + + +class Data(BaseModel): + """Event data payload.""" + + call_id: str + """The unique ID of this call.""" + + sip_headers: List[DataSipHeader] + """Headers from the SIP Invite.""" + + +class RealtimeCallIncomingWebhookEvent(BaseModel): + """Sent when Realtime API Receives a incoming SIP call.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the model response was completed.""" + + data: Data + """Event data payload.""" + + type: Literal["realtime.call.incoming"] + """The type of the event. Always `realtime.call.incoming`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/response_cancelled_webhook_event.py b/src/openai/types/webhooks/response_cancelled_webhook_event.py new file mode 100644 index 0000000000..cd791b3314 --- /dev/null +++ b/src/openai/types/webhooks/response_cancelled_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCancelledWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the model response.""" + + +class ResponseCancelledWebhookEvent(BaseModel): + """Sent when a background response has been cancelled.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the model response was cancelled.""" + + data: Data + """Event data payload.""" + + type: Literal["response.cancelled"] + """The type of the event. Always `response.cancelled`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/response_completed_webhook_event.py b/src/openai/types/webhooks/response_completed_webhook_event.py new file mode 100644 index 0000000000..cf07f0c2c0 --- /dev/null +++ b/src/openai/types/webhooks/response_completed_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseCompletedWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the model response.""" + + +class ResponseCompletedWebhookEvent(BaseModel): + """Sent when a background response has been completed.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the model response was completed.""" + + data: Data + """Event data payload.""" + + type: Literal["response.completed"] + """The type of the event. Always `response.completed`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/response_failed_webhook_event.py b/src/openai/types/webhooks/response_failed_webhook_event.py new file mode 100644 index 0000000000..aecb1b8f47 --- /dev/null +++ b/src/openai/types/webhooks/response_failed_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseFailedWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the model response.""" + + +class ResponseFailedWebhookEvent(BaseModel): + """Sent when a background response has failed.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the model response failed.""" + + data: Data + """Event data payload.""" + + type: Literal["response.failed"] + """The type of the event. Always `response.failed`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/response_incomplete_webhook_event.py b/src/openai/types/webhooks/response_incomplete_webhook_event.py new file mode 100644 index 0000000000..2367731e85 --- /dev/null +++ b/src/openai/types/webhooks/response_incomplete_webhook_event.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ResponseIncompleteWebhookEvent", "Data"] + + +class Data(BaseModel): + """Event data payload.""" + + id: str + """The unique ID of the model response.""" + + +class ResponseIncompleteWebhookEvent(BaseModel): + """Sent when a background response has been interrupted.""" + + id: str + """The unique ID of the event.""" + + created_at: int + """The Unix timestamp (in seconds) of when the model response was interrupted.""" + + data: Data + """Event data payload.""" + + type: Literal["response.incomplete"] + """The type of the event. Always `response.incomplete`.""" + + object: Optional[Literal["event"]] = None + """The object of the event. Always `event`.""" diff --git a/src/openai/types/webhooks/unwrap_webhook_event.py b/src/openai/types/webhooks/unwrap_webhook_event.py new file mode 100644 index 0000000000..952383c049 --- /dev/null +++ b/src/openai/types/webhooks/unwrap_webhook_event.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from ..._utils import PropertyInfo +from .batch_failed_webhook_event import BatchFailedWebhookEvent +from .batch_expired_webhook_event import BatchExpiredWebhookEvent +from .batch_cancelled_webhook_event import BatchCancelledWebhookEvent +from .batch_completed_webhook_event import BatchCompletedWebhookEvent +from .eval_run_failed_webhook_event import EvalRunFailedWebhookEvent +from .response_failed_webhook_event import ResponseFailedWebhookEvent +from .eval_run_canceled_webhook_event import EvalRunCanceledWebhookEvent +from .eval_run_succeeded_webhook_event import EvalRunSucceededWebhookEvent +from .response_cancelled_webhook_event import ResponseCancelledWebhookEvent +from .response_completed_webhook_event import ResponseCompletedWebhookEvent +from .response_incomplete_webhook_event import ResponseIncompleteWebhookEvent +from .fine_tuning_job_failed_webhook_event import FineTuningJobFailedWebhookEvent +from .realtime_call_incoming_webhook_event import RealtimeCallIncomingWebhookEvent +from .fine_tuning_job_cancelled_webhook_event import FineTuningJobCancelledWebhookEvent +from .fine_tuning_job_succeeded_webhook_event import FineTuningJobSucceededWebhookEvent + +__all__ = ["UnwrapWebhookEvent"] + +UnwrapWebhookEvent: TypeAlias = Annotated[ + Union[ + BatchCancelledWebhookEvent, + BatchCompletedWebhookEvent, + BatchExpiredWebhookEvent, + BatchFailedWebhookEvent, + EvalRunCanceledWebhookEvent, + EvalRunFailedWebhookEvent, + EvalRunSucceededWebhookEvent, + FineTuningJobCancelledWebhookEvent, + FineTuningJobFailedWebhookEvent, + FineTuningJobSucceededWebhookEvent, + RealtimeCallIncomingWebhookEvent, + ResponseCancelledWebhookEvent, + ResponseCompletedWebhookEvent, + ResponseFailedWebhookEvent, + ResponseIncompleteWebhookEvent, + ], + PropertyInfo(discriminator="type"), +] diff --git a/src/openai/types/websocket_connection_options.py b/src/openai/types/websocket_connection_options.py new file mode 100644 index 0000000000..519e434124 --- /dev/null +++ b/src/openai/types/websocket_connection_options.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing_extensions import Sequence, TypeAlias, TypedDict + +__all__ = ["WebSocketConnectionOptions", "WebsocketConnectionOptions"] + +if TYPE_CHECKING: + from websockets import Subprotocol + from websockets.extensions import ClientExtensionFactory + + +class WebSocketConnectionOptions(TypedDict, total=False): + """WebSocket connection options copied from `websockets`. + + For example: https://websockets.readthedocs.io/en/stable/reference/asyncio/client.html#websockets.asyncio.client.connect + """ + + extensions: Sequence[ClientExtensionFactory] | None + """List of supported extensions, in order in which they should be negotiated and run.""" + + subprotocols: Sequence[Subprotocol] | None + """List of supported subprotocols, in order of decreasing preference.""" + + compression: str | None + """The “permessage-deflate” extension is enabled by default. Set compression to None to disable it. See the [compression guide](https://websockets.readthedocs.io/en/stable/topics/compression.html) for details.""" + + # limits + max_size: int | None + """Maximum size of incoming messages in bytes. None disables the limit.""" + + max_queue: int | None | tuple[int | None, int | None] + """High-water mark of the buffer where frames are received. It defaults to 16 frames. The low-water mark defaults to max_queue // 4. You may pass a (high, low) tuple to set the high-water and low-water marks. If you want to disable flow control entirely, you may set it to None, although that’s a bad idea.""" + + write_limit: int | tuple[int, int | None] + """High-water mark of write buffer in bytes. It is passed to set_write_buffer_limits(). It defaults to 32 KiB. You may pass a (high, low) tuple to set the high-water and low-water marks.""" + + +# Backward compatibility for pre-rename imports. +WebsocketConnectionOptions: TypeAlias = WebSocketConnectionOptions diff --git a/src/openai/types/websocket_reconnection.py b/src/openai/types/websocket_reconnection.py new file mode 100644 index 0000000000..9ba66d0ca5 --- /dev/null +++ b/src/openai/types/websocket_reconnection.py @@ -0,0 +1,64 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import dataclasses +from typing_extensions import TypedDict + +from .._types import Query, Headers + + +@dataclasses.dataclass(frozen=True) +class ReconnectingEvent: + """Information about a reconnection attempt, passed to the ``on_reconnecting`` handler.""" + + attempt: int + """Which retry attempt this is (1-based).""" + + max_attempts: int + """Total attempts that will be made.""" + + delay: float + """Delay in seconds before this attempt connects.""" + + close_code: int + """The WebSocket close code that triggered reconnection.""" + + extra_query: Query + """The current query parameters.""" + + extra_headers: Headers + """The current headers.""" + + +class ReconnectingOverrides(TypedDict, total=False): + """Optional overrides returned from the ``on_reconnecting`` handler + to customize the next reconnection attempt.""" + + extra_query: Query + """If provided, assigns the query parameters for the next connection.""" + + extra_headers: Headers + """If provided, assigns the headers for the next connection.""" + + abort: bool + """If set to ``True``, will stop attempting to reconnect.""" + + +# RFC 6455 §7.4.1 +_RECOVERABLE_CLOSE_CODES: frozenset[int] = frozenset( + { + 1001, # Going away (server shutting down) + 1005, # No status code (abnormal) + 1006, # Abnormal closure (network drop) + 1011, # Internal server error + 1012, # Service restart + 1013, # Try again later + 1015, # TLS handshake failure + } +) + + +def is_recoverable_close(code: int) -> bool: + """Return ``True`` if the WebSocket close *code* is worth retrying.""" + return code in _RECOVERABLE_CLOSE_CODES diff --git a/tests/api_resources/beta/vector_stores/__init__.py b/tests/api_resources/admin/__init__.py similarity index 100% rename from tests/api_resources/beta/vector_stores/__init__.py rename to tests/api_resources/admin/__init__.py diff --git a/tests/api_resources/admin/organization/__init__.py b/tests/api_resources/admin/organization/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/admin/organization/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/admin/organization/groups/__init__.py b/tests/api_resources/admin/organization/groups/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/admin/organization/groups/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/admin/organization/groups/test_roles.py b/tests/api_resources/admin/organization/groups/test_roles.py new file mode 100644 index 0000000000..6f2235a468 --- /dev/null +++ b/tests/api_resources/admin/organization/groups/test_roles.py @@ -0,0 +1,402 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncNextCursorPage, AsyncNextCursorPage +from openai.types.admin.organization.groups import ( + RoleListResponse, + RoleCreateResponse, + RoleDeleteResponse, + RoleRetrieveResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRoles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + role = client.admin.organization.groups.roles.create( + group_id="group_id", + role_id="role_id", + ) + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.groups.roles.with_raw_response.create( + group_id="group_id", + role_id="role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.groups.roles.with_streaming_response.create( + group_id="group_id", + role_id="role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.roles.with_raw_response.create( + group_id="", + role_id="role_id", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + role = client.admin.organization.groups.roles.retrieve( + role_id="role_id", + group_id="group_id", + ) + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.groups.roles.with_raw_response.retrieve( + role_id="role_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.groups.roles.with_streaming_response.retrieve( + role_id="role_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.roles.with_raw_response.retrieve( + role_id="role_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.groups.roles.with_raw_response.retrieve( + role_id="", + group_id="group_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + role = client.admin.organization.groups.roles.list( + group_id="group_id", + ) + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + role = client.admin.organization.groups.roles.list( + group_id="group_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.groups.roles.with_raw_response.list( + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.groups.roles.with_streaming_response.list( + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.roles.with_raw_response.list( + group_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + role = client.admin.organization.groups.roles.delete( + role_id="role_id", + group_id="group_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.groups.roles.with_raw_response.delete( + role_id="role_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.groups.roles.with_streaming_response.delete( + role_id="role_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.roles.with_raw_response.delete( + role_id="role_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.groups.roles.with_raw_response.delete( + role_id="", + group_id="group_id", + ) + + +class TestAsyncRoles: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.groups.roles.create( + group_id="group_id", + role_id="role_id", + ) + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.roles.with_raw_response.create( + group_id="group_id", + role_id="role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.roles.with_streaming_response.create( + group_id="group_id", + role_id="role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.roles.with_raw_response.create( + group_id="", + role_id="role_id", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.groups.roles.retrieve( + role_id="role_id", + group_id="group_id", + ) + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.roles.with_raw_response.retrieve( + role_id="role_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.roles.with_streaming_response.retrieve( + role_id="role_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.roles.with_raw_response.retrieve( + role_id="role_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.groups.roles.with_raw_response.retrieve( + role_id="", + group_id="group_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.groups.roles.list( + group_id="group_id", + ) + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.groups.roles.list( + group_id="group_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.roles.with_raw_response.list( + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.roles.with_streaming_response.list( + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.roles.with_raw_response.list( + group_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.groups.roles.delete( + role_id="role_id", + group_id="group_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.roles.with_raw_response.delete( + role_id="role_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.roles.with_streaming_response.delete( + role_id="role_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.roles.with_raw_response.delete( + role_id="role_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.groups.roles.with_raw_response.delete( + role_id="", + group_id="group_id", + ) diff --git a/tests/api_resources/admin/organization/groups/test_users.py b/tests/api_resources/admin/organization/groups/test_users.py new file mode 100644 index 0000000000..5003db2ad1 --- /dev/null +++ b/tests/api_resources/admin/organization/groups/test_users.py @@ -0,0 +1,402 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncNextCursorPage, AsyncNextCursorPage +from openai.types.admin.organization.groups import ( + UserCreateResponse, + UserDeleteResponse, + UserRetrieveResponse, + OrganizationGroupUser, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestUsers: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + user = client.admin.organization.groups.users.create( + group_id="group_id", + user_id="user_id", + ) + assert_matches_type(UserCreateResponse, user, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.groups.users.with_raw_response.create( + group_id="group_id", + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(UserCreateResponse, user, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.groups.users.with_streaming_response.create( + group_id="group_id", + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(UserCreateResponse, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.users.with_raw_response.create( + group_id="", + user_id="user_id", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + user = client.admin.organization.groups.users.retrieve( + user_id="user_id", + group_id="group_id", + ) + assert_matches_type(UserRetrieveResponse, user, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.groups.users.with_raw_response.retrieve( + user_id="user_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(UserRetrieveResponse, user, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.groups.users.with_streaming_response.retrieve( + user_id="user_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(UserRetrieveResponse, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.users.with_raw_response.retrieve( + user_id="user_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.groups.users.with_raw_response.retrieve( + user_id="", + group_id="group_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + user = client.admin.organization.groups.users.list( + group_id="group_id", + ) + assert_matches_type(SyncNextCursorPage[OrganizationGroupUser], user, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + user = client.admin.organization.groups.users.list( + group_id="group_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncNextCursorPage[OrganizationGroupUser], user, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.groups.users.with_raw_response.list( + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(SyncNextCursorPage[OrganizationGroupUser], user, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.groups.users.with_streaming_response.list( + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(SyncNextCursorPage[OrganizationGroupUser], user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.users.with_raw_response.list( + group_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + user = client.admin.organization.groups.users.delete( + user_id="user_id", + group_id="group_id", + ) + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.groups.users.with_raw_response.delete( + user_id="user_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.groups.users.with_streaming_response.delete( + user_id="user_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.users.with_raw_response.delete( + user_id="user_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.groups.users.with_raw_response.delete( + user_id="", + group_id="group_id", + ) + + +class TestAsyncUsers: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.groups.users.create( + group_id="group_id", + user_id="user_id", + ) + assert_matches_type(UserCreateResponse, user, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.users.with_raw_response.create( + group_id="group_id", + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(UserCreateResponse, user, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.users.with_streaming_response.create( + group_id="group_id", + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(UserCreateResponse, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.users.with_raw_response.create( + group_id="", + user_id="user_id", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.groups.users.retrieve( + user_id="user_id", + group_id="group_id", + ) + assert_matches_type(UserRetrieveResponse, user, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.users.with_raw_response.retrieve( + user_id="user_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(UserRetrieveResponse, user, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.users.with_streaming_response.retrieve( + user_id="user_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(UserRetrieveResponse, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.users.with_raw_response.retrieve( + user_id="user_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.groups.users.with_raw_response.retrieve( + user_id="", + group_id="group_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.groups.users.list( + group_id="group_id", + ) + assert_matches_type(AsyncNextCursorPage[OrganizationGroupUser], user, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.groups.users.list( + group_id="group_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncNextCursorPage[OrganizationGroupUser], user, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.users.with_raw_response.list( + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(AsyncNextCursorPage[OrganizationGroupUser], user, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.users.with_streaming_response.list( + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(AsyncNextCursorPage[OrganizationGroupUser], user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.users.with_raw_response.list( + group_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.groups.users.delete( + user_id="user_id", + group_id="group_id", + ) + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.users.with_raw_response.delete( + user_id="user_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.users.with_streaming_response.delete( + user_id="user_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.users.with_raw_response.delete( + user_id="user_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.groups.users.with_raw_response.delete( + user_id="", + group_id="group_id", + ) diff --git a/tests/api_resources/admin/organization/projects/__init__.py b/tests/api_resources/admin/organization/projects/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/admin/organization/projects/groups/__init__.py b/tests/api_resources/admin/organization/projects/groups/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/groups/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/admin/organization/projects/groups/test_roles.py b/tests/api_resources/admin/organization/projects/groups/test_roles.py new file mode 100644 index 0000000000..4e62facc55 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/groups/test_roles.py @@ -0,0 +1,494 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncNextCursorPage, AsyncNextCursorPage +from openai.types.admin.organization.projects.groups import ( + RoleListResponse, + RoleCreateResponse, + RoleDeleteResponse, + RoleRetrieveResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRoles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + role = client.admin.organization.projects.groups.roles.create( + group_id="group_id", + project_id="project_id", + role_id="role_id", + ) + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.projects.groups.roles.with_raw_response.create( + group_id="group_id", + project_id="project_id", + role_id="role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.projects.groups.roles.with_streaming_response.create( + group_id="group_id", + project_id="project_id", + role_id="role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.groups.roles.with_raw_response.create( + group_id="group_id", + project_id="", + role_id="role_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.projects.groups.roles.with_raw_response.create( + group_id="", + project_id="project_id", + role_id="role_id", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + role = client.admin.organization.projects.groups.roles.retrieve( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.groups.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.groups.roles.with_streaming_response.retrieve( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.groups.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="", + group_id="group_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.projects.groups.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="project_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.projects.groups.roles.with_raw_response.retrieve( + role_id="", + project_id="project_id", + group_id="group_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + role = client.admin.organization.projects.groups.roles.list( + group_id="group_id", + project_id="project_id", + ) + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + role = client.admin.organization.projects.groups.roles.list( + group_id="group_id", + project_id="project_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.projects.groups.roles.with_raw_response.list( + group_id="group_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.projects.groups.roles.with_streaming_response.list( + group_id="group_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.groups.roles.with_raw_response.list( + group_id="group_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.projects.groups.roles.with_raw_response.list( + group_id="", + project_id="project_id", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + role = client.admin.organization.projects.groups.roles.delete( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.projects.groups.roles.with_raw_response.delete( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.projects.groups.roles.with_streaming_response.delete( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.groups.roles.with_raw_response.delete( + role_id="role_id", + project_id="", + group_id="group_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.projects.groups.roles.with_raw_response.delete( + role_id="role_id", + project_id="project_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.projects.groups.roles.with_raw_response.delete( + role_id="", + project_id="project_id", + group_id="group_id", + ) + + +class TestAsyncRoles: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.groups.roles.create( + group_id="group_id", + project_id="project_id", + role_id="role_id", + ) + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.groups.roles.with_raw_response.create( + group_id="group_id", + project_id="project_id", + role_id="role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.groups.roles.with_streaming_response.create( + group_id="group_id", + project_id="project_id", + role_id="role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.groups.roles.with_raw_response.create( + group_id="group_id", + project_id="", + role_id="role_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.projects.groups.roles.with_raw_response.create( + group_id="", + project_id="project_id", + role_id="role_id", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.groups.roles.retrieve( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.groups.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.groups.roles.with_streaming_response.retrieve( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.groups.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="", + group_id="group_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.projects.groups.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="project_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.projects.groups.roles.with_raw_response.retrieve( + role_id="", + project_id="project_id", + group_id="group_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.groups.roles.list( + group_id="group_id", + project_id="project_id", + ) + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.groups.roles.list( + group_id="group_id", + project_id="project_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.groups.roles.with_raw_response.list( + group_id="group_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.groups.roles.with_streaming_response.list( + group_id="group_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.groups.roles.with_raw_response.list( + group_id="group_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.projects.groups.roles.with_raw_response.list( + group_id="", + project_id="project_id", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.groups.roles.delete( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.groups.roles.with_raw_response.delete( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.groups.roles.with_streaming_response.delete( + role_id="role_id", + project_id="project_id", + group_id="group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.groups.roles.with_raw_response.delete( + role_id="role_id", + project_id="", + group_id="group_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.projects.groups.roles.with_raw_response.delete( + role_id="role_id", + project_id="project_id", + group_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.projects.groups.roles.with_raw_response.delete( + role_id="", + project_id="project_id", + group_id="group_id", + ) diff --git a/tests/api_resources/admin/organization/projects/test_api_keys.py b/tests/api_resources/admin/organization/projects/test_api_keys.py new file mode 100644 index 0000000000..f9aef44216 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_api_keys.py @@ -0,0 +1,311 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization.projects import ProjectAPIKey, APIKeyDeleteResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAPIKeys: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + api_key = client.admin.organization.projects.api_keys.retrieve( + api_key_id="api_key_id", + project_id="project_id", + ) + assert_matches_type(ProjectAPIKey, api_key, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.api_keys.with_raw_response.retrieve( + api_key_id="api_key_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(ProjectAPIKey, api_key, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.api_keys.with_streaming_response.retrieve( + api_key_id="api_key_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = response.parse() + assert_matches_type(ProjectAPIKey, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.api_keys.with_raw_response.retrieve( + api_key_id="api_key_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + client.admin.organization.projects.api_keys.with_raw_response.retrieve( + api_key_id="", + project_id="project_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + api_key = client.admin.organization.projects.api_keys.list( + project_id="project_id", + ) + assert_matches_type(SyncConversationCursorPage[ProjectAPIKey], api_key, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + api_key = client.admin.organization.projects.api_keys.list( + project_id="project_id", + after="after", + limit=0, + ) + assert_matches_type(SyncConversationCursorPage[ProjectAPIKey], api_key, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.projects.api_keys.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(SyncConversationCursorPage[ProjectAPIKey], api_key, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.projects.api_keys.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = response.parse() + assert_matches_type(SyncConversationCursorPage[ProjectAPIKey], api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.api_keys.with_raw_response.list( + project_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + api_key = client.admin.organization.projects.api_keys.delete( + api_key_id="api_key_id", + project_id="project_id", + ) + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.projects.api_keys.with_raw_response.delete( + api_key_id="api_key_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.projects.api_keys.with_streaming_response.delete( + api_key_id="api_key_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = response.parse() + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.api_keys.with_raw_response.delete( + api_key_id="api_key_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + client.admin.organization.projects.api_keys.with_raw_response.delete( + api_key_id="", + project_id="project_id", + ) + + +class TestAsyncAPIKeys: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + api_key = await async_client.admin.organization.projects.api_keys.retrieve( + api_key_id="api_key_id", + project_id="project_id", + ) + assert_matches_type(ProjectAPIKey, api_key, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.api_keys.with_raw_response.retrieve( + api_key_id="api_key_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(ProjectAPIKey, api_key, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.api_keys.with_streaming_response.retrieve( + api_key_id="api_key_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = await response.parse() + assert_matches_type(ProjectAPIKey, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.api_keys.with_raw_response.retrieve( + api_key_id="api_key_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + await async_client.admin.organization.projects.api_keys.with_raw_response.retrieve( + api_key_id="", + project_id="project_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + api_key = await async_client.admin.organization.projects.api_keys.list( + project_id="project_id", + ) + assert_matches_type(AsyncConversationCursorPage[ProjectAPIKey], api_key, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + api_key = await async_client.admin.organization.projects.api_keys.list( + project_id="project_id", + after="after", + limit=0, + ) + assert_matches_type(AsyncConversationCursorPage[ProjectAPIKey], api_key, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.api_keys.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(AsyncConversationCursorPage[ProjectAPIKey], api_key, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.api_keys.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = await response.parse() + assert_matches_type(AsyncConversationCursorPage[ProjectAPIKey], api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.api_keys.with_raw_response.list( + project_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + api_key = await async_client.admin.organization.projects.api_keys.delete( + api_key_id="api_key_id", + project_id="project_id", + ) + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.api_keys.with_raw_response.delete( + api_key_id="api_key_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.api_keys.with_streaming_response.delete( + api_key_id="api_key_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = await response.parse() + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.api_keys.with_raw_response.delete( + api_key_id="api_key_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + await async_client.admin.organization.projects.api_keys.with_raw_response.delete( + api_key_id="", + project_id="project_id", + ) diff --git a/tests/api_resources/admin/organization/projects/test_certificates.py b/tests/api_resources/admin/organization/projects/test_certificates.py new file mode 100644 index 0000000000..e242b7d6d4 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_certificates.py @@ -0,0 +1,293 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncPage, AsyncPage, SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization.projects import ( + CertificateListResponse, + CertificateActivateResponse, + CertificateDeactivateResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCertificates: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + certificate = client.admin.organization.projects.certificates.list( + project_id="project_id", + ) + assert_matches_type(SyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + certificate = client.admin.organization.projects.certificates.list( + project_id="project_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.projects.certificates.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(SyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.projects.certificates.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = response.parse() + assert_matches_type(SyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.certificates.with_raw_response.list( + project_id="", + ) + + @parametrize + def test_method_activate(self, client: OpenAI) -> None: + certificate = client.admin.organization.projects.certificates.activate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) + assert_matches_type(SyncPage[CertificateActivateResponse], certificate, path=["response"]) + + @parametrize + def test_raw_response_activate(self, client: OpenAI) -> None: + response = client.admin.organization.projects.certificates.with_raw_response.activate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(SyncPage[CertificateActivateResponse], certificate, path=["response"]) + + @parametrize + def test_streaming_response_activate(self, client: OpenAI) -> None: + with client.admin.organization.projects.certificates.with_streaming_response.activate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = response.parse() + assert_matches_type(SyncPage[CertificateActivateResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_activate(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.certificates.with_raw_response.activate( + project_id="", + certificate_ids=["cert_abc"], + ) + + @parametrize + def test_method_deactivate(self, client: OpenAI) -> None: + certificate = client.admin.organization.projects.certificates.deactivate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) + assert_matches_type(SyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + @parametrize + def test_raw_response_deactivate(self, client: OpenAI) -> None: + response = client.admin.organization.projects.certificates.with_raw_response.deactivate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(SyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + @parametrize + def test_streaming_response_deactivate(self, client: OpenAI) -> None: + with client.admin.organization.projects.certificates.with_streaming_response.deactivate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = response.parse() + assert_matches_type(SyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_deactivate(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.certificates.with_raw_response.deactivate( + project_id="", + certificate_ids=["cert_abc"], + ) + + +class TestAsyncCertificates: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.projects.certificates.list( + project_id="project_id", + ) + assert_matches_type(AsyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.projects.certificates.list( + project_id="project_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.certificates.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(AsyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.certificates.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = await response.parse() + assert_matches_type(AsyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.certificates.with_raw_response.list( + project_id="", + ) + + @parametrize + async def test_method_activate(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.projects.certificates.activate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) + assert_matches_type(AsyncPage[CertificateActivateResponse], certificate, path=["response"]) + + @parametrize + async def test_raw_response_activate(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.certificates.with_raw_response.activate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(AsyncPage[CertificateActivateResponse], certificate, path=["response"]) + + @parametrize + async def test_streaming_response_activate(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.certificates.with_streaming_response.activate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = await response.parse() + assert_matches_type(AsyncPage[CertificateActivateResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_activate(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.certificates.with_raw_response.activate( + project_id="", + certificate_ids=["cert_abc"], + ) + + @parametrize + async def test_method_deactivate(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.projects.certificates.deactivate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) + assert_matches_type(AsyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + @parametrize + async def test_raw_response_deactivate(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.certificates.with_raw_response.deactivate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(AsyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + @parametrize + async def test_streaming_response_deactivate(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.certificates.with_streaming_response.deactivate( + project_id="project_id", + certificate_ids=["cert_abc"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = await response.parse() + assert_matches_type(AsyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_deactivate(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.certificates.with_raw_response.deactivate( + project_id="", + certificate_ids=["cert_abc"], + ) diff --git a/tests/api_resources/admin/organization/projects/test_data_retention.py b/tests/api_resources/admin/organization/projects/test_data_retention.py new file mode 100644 index 0000000000..d640e78e8d --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_data_retention.py @@ -0,0 +1,184 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types.admin.organization.projects import ProjectDataRetention + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestDataRetention: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + data_retention = client.admin.organization.projects.data_retention.retrieve( + "project_id", + ) + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.data_retention.with_raw_response.retrieve( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_retention = response.parse() + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.data_retention.with_streaming_response.retrieve( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_retention = response.parse() + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.data_retention.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + data_retention = client.admin.organization.projects.data_retention.update( + project_id="project_id", + retention_type="organization_default", + ) + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.projects.data_retention.with_raw_response.update( + project_id="project_id", + retention_type="organization_default", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_retention = response.parse() + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.projects.data_retention.with_streaming_response.update( + project_id="project_id", + retention_type="organization_default", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_retention = response.parse() + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.data_retention.with_raw_response.update( + project_id="", + retention_type="organization_default", + ) + + +class TestAsyncDataRetention: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + data_retention = await async_client.admin.organization.projects.data_retention.retrieve( + "project_id", + ) + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.data_retention.with_raw_response.retrieve( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_retention = response.parse() + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.data_retention.with_streaming_response.retrieve( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_retention = await response.parse() + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.data_retention.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + data_retention = await async_client.admin.organization.projects.data_retention.update( + project_id="project_id", + retention_type="organization_default", + ) + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.data_retention.with_raw_response.update( + project_id="project_id", + retention_type="organization_default", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_retention = response.parse() + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.data_retention.with_streaming_response.update( + project_id="project_id", + retention_type="organization_default", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_retention = await response.parse() + assert_matches_type(ProjectDataRetention, data_retention, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.data_retention.with_raw_response.update( + project_id="", + retention_type="organization_default", + ) diff --git a/tests/api_resources/admin/organization/projects/test_groups.py b/tests/api_resources/admin/organization/projects/test_groups.py new file mode 100644 index 0000000000..46a68076df --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_groups.py @@ -0,0 +1,426 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncNextCursorPage, AsyncNextCursorPage +from openai.types.admin.organization.projects import ( + ProjectGroup, + GroupDeleteResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestGroups: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + group = client.admin.organization.projects.groups.create( + project_id="project_id", + group_id="group_id", + role="role", + ) + assert_matches_type(ProjectGroup, group, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.projects.groups.with_raw_response.create( + project_id="project_id", + group_id="group_id", + role="role", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(ProjectGroup, group, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.projects.groups.with_streaming_response.create( + project_id="project_id", + group_id="group_id", + role="role", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = response.parse() + assert_matches_type(ProjectGroup, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.groups.with_raw_response.create( + project_id="", + group_id="group_id", + role="role", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + group = client.admin.organization.projects.groups.retrieve( + group_id="group_id", + project_id="project_id", + ) + assert_matches_type(ProjectGroup, group, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: OpenAI) -> None: + group = client.admin.organization.projects.groups.retrieve( + group_id="group_id", + project_id="project_id", + group_type="group", + ) + assert_matches_type(ProjectGroup, group, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.groups.with_raw_response.retrieve( + group_id="group_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(ProjectGroup, group, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.groups.with_streaming_response.retrieve( + group_id="group_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = response.parse() + assert_matches_type(ProjectGroup, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.groups.with_raw_response.retrieve( + group_id="group_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.projects.groups.with_raw_response.retrieve( + group_id="", + project_id="project_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + group = client.admin.organization.projects.groups.list( + project_id="project_id", + ) + assert_matches_type(SyncNextCursorPage[ProjectGroup], group, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + group = client.admin.organization.projects.groups.list( + project_id="project_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncNextCursorPage[ProjectGroup], group, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.projects.groups.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(SyncNextCursorPage[ProjectGroup], group, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.projects.groups.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = response.parse() + assert_matches_type(SyncNextCursorPage[ProjectGroup], group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.groups.with_raw_response.list( + project_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + group = client.admin.organization.projects.groups.delete( + group_id="group_id", + project_id="project_id", + ) + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.projects.groups.with_raw_response.delete( + group_id="group_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.projects.groups.with_streaming_response.delete( + group_id="group_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = response.parse() + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.groups.with_raw_response.delete( + group_id="group_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.projects.groups.with_raw_response.delete( + group_id="", + project_id="project_id", + ) + + +class TestAsyncGroups: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.projects.groups.create( + project_id="project_id", + group_id="group_id", + role="role", + ) + assert_matches_type(ProjectGroup, group, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.groups.with_raw_response.create( + project_id="project_id", + group_id="group_id", + role="role", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(ProjectGroup, group, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.groups.with_streaming_response.create( + project_id="project_id", + group_id="group_id", + role="role", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = await response.parse() + assert_matches_type(ProjectGroup, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.groups.with_raw_response.create( + project_id="", + group_id="group_id", + role="role", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.projects.groups.retrieve( + group_id="group_id", + project_id="project_id", + ) + assert_matches_type(ProjectGroup, group, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.projects.groups.retrieve( + group_id="group_id", + project_id="project_id", + group_type="group", + ) + assert_matches_type(ProjectGroup, group, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.groups.with_raw_response.retrieve( + group_id="group_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(ProjectGroup, group, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.groups.with_streaming_response.retrieve( + group_id="group_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = await response.parse() + assert_matches_type(ProjectGroup, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.groups.with_raw_response.retrieve( + group_id="group_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.projects.groups.with_raw_response.retrieve( + group_id="", + project_id="project_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.projects.groups.list( + project_id="project_id", + ) + assert_matches_type(AsyncNextCursorPage[ProjectGroup], group, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.projects.groups.list( + project_id="project_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncNextCursorPage[ProjectGroup], group, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.groups.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(AsyncNextCursorPage[ProjectGroup], group, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.groups.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = await response.parse() + assert_matches_type(AsyncNextCursorPage[ProjectGroup], group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.groups.with_raw_response.list( + project_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.projects.groups.delete( + group_id="group_id", + project_id="project_id", + ) + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.groups.with_raw_response.delete( + group_id="group_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.groups.with_streaming_response.delete( + group_id="group_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = await response.parse() + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.groups.with_raw_response.delete( + group_id="group_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.projects.groups.with_raw_response.delete( + group_id="", + project_id="project_id", + ) diff --git a/tests/api_resources/admin/organization/projects/test_hosted_tool_permissions.py b/tests/api_resources/admin/organization/projects/test_hosted_tool_permissions.py new file mode 100644 index 0000000000..d7d2486874 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_hosted_tool_permissions.py @@ -0,0 +1,200 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types.admin.organization.projects import ProjectHostedToolPermissions + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestHostedToolPermissions: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + hosted_tool_permission = client.admin.organization.projects.hosted_tool_permissions.retrieve( + "project_id", + ) + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.hosted_tool_permissions.with_raw_response.retrieve( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + hosted_tool_permission = response.parse() + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.hosted_tool_permissions.with_streaming_response.retrieve( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + hosted_tool_permission = response.parse() + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.hosted_tool_permissions.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + hosted_tool_permission = client.admin.organization.projects.hosted_tool_permissions.update( + project_id="project_id", + ) + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + hosted_tool_permission = client.admin.organization.projects.hosted_tool_permissions.update( + project_id="project_id", + code_interpreter={"enabled": True}, + file_search={"enabled": True}, + image_generation={"enabled": True}, + mcp={"enabled": True}, + web_search={"enabled": True}, + ) + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.projects.hosted_tool_permissions.with_raw_response.update( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + hosted_tool_permission = response.parse() + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.projects.hosted_tool_permissions.with_streaming_response.update( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + hosted_tool_permission = response.parse() + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.hosted_tool_permissions.with_raw_response.update( + project_id="", + ) + + +class TestAsyncHostedToolPermissions: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + hosted_tool_permission = await async_client.admin.organization.projects.hosted_tool_permissions.retrieve( + "project_id", + ) + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.hosted_tool_permissions.with_raw_response.retrieve( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + hosted_tool_permission = response.parse() + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.hosted_tool_permissions.with_streaming_response.retrieve( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + hosted_tool_permission = await response.parse() + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.hosted_tool_permissions.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + hosted_tool_permission = await async_client.admin.organization.projects.hosted_tool_permissions.update( + project_id="project_id", + ) + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + hosted_tool_permission = await async_client.admin.organization.projects.hosted_tool_permissions.update( + project_id="project_id", + code_interpreter={"enabled": True}, + file_search={"enabled": True}, + image_generation={"enabled": True}, + mcp={"enabled": True}, + web_search={"enabled": True}, + ) + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.hosted_tool_permissions.with_raw_response.update( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + hosted_tool_permission = response.parse() + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.hosted_tool_permissions.with_streaming_response.update( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + hosted_tool_permission = await response.parse() + assert_matches_type(ProjectHostedToolPermissions, hosted_tool_permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.hosted_tool_permissions.with_raw_response.update( + project_id="", + ) diff --git a/tests/api_resources/admin/organization/projects/test_model_permissions.py b/tests/api_resources/admin/organization/projects/test_model_permissions.py new file mode 100644 index 0000000000..1749a70fa5 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_model_permissions.py @@ -0,0 +1,271 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types.admin.organization.projects import ( + ProjectModelPermissions, + ProjectModelPermissionsDeleted, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestModelPermissions: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + model_permission = client.admin.organization.projects.model_permissions.retrieve( + "project_id", + ) + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.model_permissions.with_raw_response.retrieve( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + model_permission = response.parse() + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.model_permissions.with_streaming_response.retrieve( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model_permission = response.parse() + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.model_permissions.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + model_permission = client.admin.organization.projects.model_permissions.update( + project_id="project_id", + mode="allow_list", + model_ids=["string"], + ) + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.projects.model_permissions.with_raw_response.update( + project_id="project_id", + mode="allow_list", + model_ids=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + model_permission = response.parse() + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.projects.model_permissions.with_streaming_response.update( + project_id="project_id", + mode="allow_list", + model_ids=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model_permission = response.parse() + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.model_permissions.with_raw_response.update( + project_id="", + mode="allow_list", + model_ids=["string"], + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + model_permission = client.admin.organization.projects.model_permissions.delete( + "project_id", + ) + assert_matches_type(ProjectModelPermissionsDeleted, model_permission, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.projects.model_permissions.with_raw_response.delete( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + model_permission = response.parse() + assert_matches_type(ProjectModelPermissionsDeleted, model_permission, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.projects.model_permissions.with_streaming_response.delete( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model_permission = response.parse() + assert_matches_type(ProjectModelPermissionsDeleted, model_permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.model_permissions.with_raw_response.delete( + "", + ) + + +class TestAsyncModelPermissions: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + model_permission = await async_client.admin.organization.projects.model_permissions.retrieve( + "project_id", + ) + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.model_permissions.with_raw_response.retrieve( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + model_permission = response.parse() + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.model_permissions.with_streaming_response.retrieve( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model_permission = await response.parse() + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.model_permissions.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + model_permission = await async_client.admin.organization.projects.model_permissions.update( + project_id="project_id", + mode="allow_list", + model_ids=["string"], + ) + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.model_permissions.with_raw_response.update( + project_id="project_id", + mode="allow_list", + model_ids=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + model_permission = response.parse() + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.model_permissions.with_streaming_response.update( + project_id="project_id", + mode="allow_list", + model_ids=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model_permission = await response.parse() + assert_matches_type(ProjectModelPermissions, model_permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.model_permissions.with_raw_response.update( + project_id="", + mode="allow_list", + model_ids=["string"], + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + model_permission = await async_client.admin.organization.projects.model_permissions.delete( + "project_id", + ) + assert_matches_type(ProjectModelPermissionsDeleted, model_permission, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.model_permissions.with_raw_response.delete( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + model_permission = response.parse() + assert_matches_type(ProjectModelPermissionsDeleted, model_permission, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.model_permissions.with_streaming_response.delete( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model_permission = await response.parse() + assert_matches_type(ProjectModelPermissionsDeleted, model_permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.model_permissions.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/admin/organization/projects/test_rate_limits.py b/tests/api_resources/admin/organization/projects/test_rate_limits.py new file mode 100644 index 0000000000..c06077614b --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_rate_limits.py @@ -0,0 +1,247 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization.projects import ( + ProjectRateLimit, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRateLimits: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list_rate_limits(self, client: OpenAI) -> None: + rate_limit = client.admin.organization.projects.rate_limits.list_rate_limits( + project_id="project_id", + ) + assert_matches_type(SyncConversationCursorPage[ProjectRateLimit], rate_limit, path=["response"]) + + @parametrize + def test_method_list_rate_limits_with_all_params(self, client: OpenAI) -> None: + rate_limit = client.admin.organization.projects.rate_limits.list_rate_limits( + project_id="project_id", + after="after", + before="before", + limit=0, + ) + assert_matches_type(SyncConversationCursorPage[ProjectRateLimit], rate_limit, path=["response"]) + + @parametrize + def test_raw_response_list_rate_limits(self, client: OpenAI) -> None: + response = client.admin.organization.projects.rate_limits.with_raw_response.list_rate_limits( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rate_limit = response.parse() + assert_matches_type(SyncConversationCursorPage[ProjectRateLimit], rate_limit, path=["response"]) + + @parametrize + def test_streaming_response_list_rate_limits(self, client: OpenAI) -> None: + with client.admin.organization.projects.rate_limits.with_streaming_response.list_rate_limits( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rate_limit = response.parse() + assert_matches_type(SyncConversationCursorPage[ProjectRateLimit], rate_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list_rate_limits(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.rate_limits.with_raw_response.list_rate_limits( + project_id="", + ) + + @parametrize + def test_method_update_rate_limit(self, client: OpenAI) -> None: + rate_limit = client.admin.organization.projects.rate_limits.update_rate_limit( + rate_limit_id="rate_limit_id", + project_id="project_id", + ) + assert_matches_type(ProjectRateLimit, rate_limit, path=["response"]) + + @parametrize + def test_method_update_rate_limit_with_all_params(self, client: OpenAI) -> None: + rate_limit = client.admin.organization.projects.rate_limits.update_rate_limit( + rate_limit_id="rate_limit_id", + project_id="project_id", + batch_1_day_max_input_tokens=0, + max_audio_megabytes_per_1_minute=0, + max_images_per_1_minute=0, + max_requests_per_1_day=0, + max_requests_per_1_minute=0, + max_tokens_per_1_minute=0, + ) + assert_matches_type(ProjectRateLimit, rate_limit, path=["response"]) + + @parametrize + def test_raw_response_update_rate_limit(self, client: OpenAI) -> None: + response = client.admin.organization.projects.rate_limits.with_raw_response.update_rate_limit( + rate_limit_id="rate_limit_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rate_limit = response.parse() + assert_matches_type(ProjectRateLimit, rate_limit, path=["response"]) + + @parametrize + def test_streaming_response_update_rate_limit(self, client: OpenAI) -> None: + with client.admin.organization.projects.rate_limits.with_streaming_response.update_rate_limit( + rate_limit_id="rate_limit_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rate_limit = response.parse() + assert_matches_type(ProjectRateLimit, rate_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update_rate_limit(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.rate_limits.with_raw_response.update_rate_limit( + rate_limit_id="rate_limit_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rate_limit_id` but received ''"): + client.admin.organization.projects.rate_limits.with_raw_response.update_rate_limit( + rate_limit_id="", + project_id="project_id", + ) + + +class TestAsyncRateLimits: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list_rate_limits(self, async_client: AsyncOpenAI) -> None: + rate_limit = await async_client.admin.organization.projects.rate_limits.list_rate_limits( + project_id="project_id", + ) + assert_matches_type(AsyncConversationCursorPage[ProjectRateLimit], rate_limit, path=["response"]) + + @parametrize + async def test_method_list_rate_limits_with_all_params(self, async_client: AsyncOpenAI) -> None: + rate_limit = await async_client.admin.organization.projects.rate_limits.list_rate_limits( + project_id="project_id", + after="after", + before="before", + limit=0, + ) + assert_matches_type(AsyncConversationCursorPage[ProjectRateLimit], rate_limit, path=["response"]) + + @parametrize + async def test_raw_response_list_rate_limits(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.rate_limits.with_raw_response.list_rate_limits( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rate_limit = response.parse() + assert_matches_type(AsyncConversationCursorPage[ProjectRateLimit], rate_limit, path=["response"]) + + @parametrize + async def test_streaming_response_list_rate_limits(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.rate_limits.with_streaming_response.list_rate_limits( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rate_limit = await response.parse() + assert_matches_type(AsyncConversationCursorPage[ProjectRateLimit], rate_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list_rate_limits(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.rate_limits.with_raw_response.list_rate_limits( + project_id="", + ) + + @parametrize + async def test_method_update_rate_limit(self, async_client: AsyncOpenAI) -> None: + rate_limit = await async_client.admin.organization.projects.rate_limits.update_rate_limit( + rate_limit_id="rate_limit_id", + project_id="project_id", + ) + assert_matches_type(ProjectRateLimit, rate_limit, path=["response"]) + + @parametrize + async def test_method_update_rate_limit_with_all_params(self, async_client: AsyncOpenAI) -> None: + rate_limit = await async_client.admin.organization.projects.rate_limits.update_rate_limit( + rate_limit_id="rate_limit_id", + project_id="project_id", + batch_1_day_max_input_tokens=0, + max_audio_megabytes_per_1_minute=0, + max_images_per_1_minute=0, + max_requests_per_1_day=0, + max_requests_per_1_minute=0, + max_tokens_per_1_minute=0, + ) + assert_matches_type(ProjectRateLimit, rate_limit, path=["response"]) + + @parametrize + async def test_raw_response_update_rate_limit(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.rate_limits.with_raw_response.update_rate_limit( + rate_limit_id="rate_limit_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rate_limit = response.parse() + assert_matches_type(ProjectRateLimit, rate_limit, path=["response"]) + + @parametrize + async def test_streaming_response_update_rate_limit(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.rate_limits.with_streaming_response.update_rate_limit( + rate_limit_id="rate_limit_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rate_limit = await response.parse() + assert_matches_type(ProjectRateLimit, rate_limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update_rate_limit(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.rate_limits.with_raw_response.update_rate_limit( + rate_limit_id="rate_limit_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rate_limit_id` but received ''"): + await async_client.admin.organization.projects.rate_limits.with_raw_response.update_rate_limit( + rate_limit_id="", + project_id="project_id", + ) diff --git a/tests/api_resources/admin/organization/projects/test_roles.py b/tests/api_resources/admin/organization/projects/test_roles.py new file mode 100644 index 0000000000..862ea1f412 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_roles.py @@ -0,0 +1,546 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncNextCursorPage, AsyncNextCursorPage +from openai.types.admin.organization import Role +from openai.types.admin.organization.projects import ( + RoleDeleteResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRoles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + role = client.admin.organization.projects.roles.create( + project_id="project_id", + permissions=["string"], + role_name="role_name", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + role = client.admin.organization.projects.roles.create( + project_id="project_id", + permissions=["string"], + role_name="role_name", + description="description", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.projects.roles.with_raw_response.create( + project_id="project_id", + permissions=["string"], + role_name="role_name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.projects.roles.with_streaming_response.create( + project_id="project_id", + permissions=["string"], + role_name="role_name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.roles.with_raw_response.create( + project_id="", + permissions=["string"], + role_name="role_name", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + role = client.admin.organization.projects.roles.retrieve( + role_id="role_id", + project_id="project_id", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.roles.with_streaming_response.retrieve( + role_id="role_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.projects.roles.with_raw_response.retrieve( + role_id="", + project_id="project_id", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + role = client.admin.organization.projects.roles.update( + role_id="role_id", + project_id="project_id", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + role = client.admin.organization.projects.roles.update( + role_id="role_id", + project_id="project_id", + description="description", + permissions=["string"], + role_name="role_name", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.projects.roles.with_raw_response.update( + role_id="role_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.projects.roles.with_streaming_response.update( + role_id="role_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.roles.with_raw_response.update( + role_id="role_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.projects.roles.with_raw_response.update( + role_id="", + project_id="project_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + role = client.admin.organization.projects.roles.list( + project_id="project_id", + ) + assert_matches_type(SyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + role = client.admin.organization.projects.roles.list( + project_id="project_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.projects.roles.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(SyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.projects.roles.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(SyncNextCursorPage[Role], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.roles.with_raw_response.list( + project_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + role = client.admin.organization.projects.roles.delete( + role_id="role_id", + project_id="project_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.projects.roles.with_raw_response.delete( + role_id="role_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.projects.roles.with_streaming_response.delete( + role_id="role_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.roles.with_raw_response.delete( + role_id="role_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.projects.roles.with_raw_response.delete( + role_id="", + project_id="project_id", + ) + + +class TestAsyncRoles: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.roles.create( + project_id="project_id", + permissions=["string"], + role_name="role_name", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.roles.create( + project_id="project_id", + permissions=["string"], + role_name="role_name", + description="description", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.roles.with_raw_response.create( + project_id="project_id", + permissions=["string"], + role_name="role_name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.roles.with_streaming_response.create( + project_id="project_id", + permissions=["string"], + role_name="role_name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.roles.with_raw_response.create( + project_id="", + permissions=["string"], + role_name="role_name", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.roles.retrieve( + role_id="role_id", + project_id="project_id", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.roles.with_streaming_response.retrieve( + role_id="role_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.projects.roles.with_raw_response.retrieve( + role_id="", + project_id="project_id", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.roles.update( + role_id="role_id", + project_id="project_id", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.roles.update( + role_id="role_id", + project_id="project_id", + description="description", + permissions=["string"], + role_name="role_name", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.roles.with_raw_response.update( + role_id="role_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.roles.with_streaming_response.update( + role_id="role_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.roles.with_raw_response.update( + role_id="role_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.projects.roles.with_raw_response.update( + role_id="", + project_id="project_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.roles.list( + project_id="project_id", + ) + assert_matches_type(AsyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.roles.list( + project_id="project_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.roles.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(AsyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.roles.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(AsyncNextCursorPage[Role], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.roles.with_raw_response.list( + project_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.roles.delete( + role_id="role_id", + project_id="project_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.roles.with_raw_response.delete( + role_id="role_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.roles.with_streaming_response.delete( + role_id="role_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.roles.with_raw_response.delete( + role_id="role_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.projects.roles.with_raw_response.delete( + role_id="", + project_id="project_id", + ) diff --git a/tests/api_resources/admin/organization/projects/test_service_accounts.py b/tests/api_resources/admin/organization/projects/test_service_accounts.py new file mode 100644 index 0000000000..e8e68596a2 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_service_accounts.py @@ -0,0 +1,515 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization.projects import ( + ProjectServiceAccount, + ServiceAccountCreateResponse, + ServiceAccountDeleteResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestServiceAccounts: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + service_account = client.admin.organization.projects.service_accounts.create( + project_id="project_id", + name="name", + ) + assert_matches_type(ServiceAccountCreateResponse, service_account, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.projects.service_accounts.with_raw_response.create( + project_id="project_id", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + service_account = response.parse() + assert_matches_type(ServiceAccountCreateResponse, service_account, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.projects.service_accounts.with_streaming_response.create( + project_id="project_id", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + service_account = response.parse() + assert_matches_type(ServiceAccountCreateResponse, service_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.service_accounts.with_raw_response.create( + project_id="", + name="name", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + service_account = client.admin.organization.projects.service_accounts.retrieve( + service_account_id="service_account_id", + project_id="project_id", + ) + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.service_accounts.with_raw_response.retrieve( + service_account_id="service_account_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + service_account = response.parse() + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.service_accounts.with_streaming_response.retrieve( + service_account_id="service_account_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + service_account = response.parse() + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.service_accounts.with_raw_response.retrieve( + service_account_id="service_account_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `service_account_id` but received ''"): + client.admin.organization.projects.service_accounts.with_raw_response.retrieve( + service_account_id="", + project_id="project_id", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + service_account = client.admin.organization.projects.service_accounts.update( + service_account_id="service_account_id", + project_id="project_id", + ) + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + service_account = client.admin.organization.projects.service_accounts.update( + service_account_id="service_account_id", + project_id="project_id", + name="name", + role="member", + ) + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.projects.service_accounts.with_raw_response.update( + service_account_id="service_account_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + service_account = response.parse() + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.projects.service_accounts.with_streaming_response.update( + service_account_id="service_account_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + service_account = response.parse() + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.service_accounts.with_raw_response.update( + service_account_id="service_account_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `service_account_id` but received ''"): + client.admin.organization.projects.service_accounts.with_raw_response.update( + service_account_id="", + project_id="project_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + service_account = client.admin.organization.projects.service_accounts.list( + project_id="project_id", + ) + assert_matches_type(SyncConversationCursorPage[ProjectServiceAccount], service_account, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + service_account = client.admin.organization.projects.service_accounts.list( + project_id="project_id", + after="after", + limit=0, + ) + assert_matches_type(SyncConversationCursorPage[ProjectServiceAccount], service_account, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.projects.service_accounts.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + service_account = response.parse() + assert_matches_type(SyncConversationCursorPage[ProjectServiceAccount], service_account, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.projects.service_accounts.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + service_account = response.parse() + assert_matches_type(SyncConversationCursorPage[ProjectServiceAccount], service_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.service_accounts.with_raw_response.list( + project_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + service_account = client.admin.organization.projects.service_accounts.delete( + service_account_id="service_account_id", + project_id="project_id", + ) + assert_matches_type(ServiceAccountDeleteResponse, service_account, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.projects.service_accounts.with_raw_response.delete( + service_account_id="service_account_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + service_account = response.parse() + assert_matches_type(ServiceAccountDeleteResponse, service_account, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.projects.service_accounts.with_streaming_response.delete( + service_account_id="service_account_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + service_account = response.parse() + assert_matches_type(ServiceAccountDeleteResponse, service_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.service_accounts.with_raw_response.delete( + service_account_id="service_account_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `service_account_id` but received ''"): + client.admin.organization.projects.service_accounts.with_raw_response.delete( + service_account_id="", + project_id="project_id", + ) + + +class TestAsyncServiceAccounts: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + service_account = await async_client.admin.organization.projects.service_accounts.create( + project_id="project_id", + name="name", + ) + assert_matches_type(ServiceAccountCreateResponse, service_account, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.service_accounts.with_raw_response.create( + project_id="project_id", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + service_account = response.parse() + assert_matches_type(ServiceAccountCreateResponse, service_account, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.service_accounts.with_streaming_response.create( + project_id="project_id", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + service_account = await response.parse() + assert_matches_type(ServiceAccountCreateResponse, service_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.service_accounts.with_raw_response.create( + project_id="", + name="name", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + service_account = await async_client.admin.organization.projects.service_accounts.retrieve( + service_account_id="service_account_id", + project_id="project_id", + ) + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.service_accounts.with_raw_response.retrieve( + service_account_id="service_account_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + service_account = response.parse() + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.service_accounts.with_streaming_response.retrieve( + service_account_id="service_account_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + service_account = await response.parse() + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.service_accounts.with_raw_response.retrieve( + service_account_id="service_account_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `service_account_id` but received ''"): + await async_client.admin.organization.projects.service_accounts.with_raw_response.retrieve( + service_account_id="", + project_id="project_id", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + service_account = await async_client.admin.organization.projects.service_accounts.update( + service_account_id="service_account_id", + project_id="project_id", + ) + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + service_account = await async_client.admin.organization.projects.service_accounts.update( + service_account_id="service_account_id", + project_id="project_id", + name="name", + role="member", + ) + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.service_accounts.with_raw_response.update( + service_account_id="service_account_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + service_account = response.parse() + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.service_accounts.with_streaming_response.update( + service_account_id="service_account_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + service_account = await response.parse() + assert_matches_type(ProjectServiceAccount, service_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.service_accounts.with_raw_response.update( + service_account_id="service_account_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `service_account_id` but received ''"): + await async_client.admin.organization.projects.service_accounts.with_raw_response.update( + service_account_id="", + project_id="project_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + service_account = await async_client.admin.organization.projects.service_accounts.list( + project_id="project_id", + ) + assert_matches_type(AsyncConversationCursorPage[ProjectServiceAccount], service_account, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + service_account = await async_client.admin.organization.projects.service_accounts.list( + project_id="project_id", + after="after", + limit=0, + ) + assert_matches_type(AsyncConversationCursorPage[ProjectServiceAccount], service_account, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.service_accounts.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + service_account = response.parse() + assert_matches_type(AsyncConversationCursorPage[ProjectServiceAccount], service_account, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.service_accounts.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + service_account = await response.parse() + assert_matches_type(AsyncConversationCursorPage[ProjectServiceAccount], service_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.service_accounts.with_raw_response.list( + project_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + service_account = await async_client.admin.organization.projects.service_accounts.delete( + service_account_id="service_account_id", + project_id="project_id", + ) + assert_matches_type(ServiceAccountDeleteResponse, service_account, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.service_accounts.with_raw_response.delete( + service_account_id="service_account_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + service_account = response.parse() + assert_matches_type(ServiceAccountDeleteResponse, service_account, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.service_accounts.with_streaming_response.delete( + service_account_id="service_account_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + service_account = await response.parse() + assert_matches_type(ServiceAccountDeleteResponse, service_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.service_accounts.with_raw_response.delete( + service_account_id="service_account_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `service_account_id` but received ''"): + await async_client.admin.organization.projects.service_accounts.with_raw_response.delete( + service_account_id="", + project_id="project_id", + ) diff --git a/tests/api_resources/admin/organization/projects/test_spend_alerts.py b/tests/api_resources/admin/organization/projects/test_spend_alerts.py new file mode 100644 index 0000000000..c763401af3 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_spend_alerts.py @@ -0,0 +1,582 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization.projects import ( + ProjectSpendAlert, + ProjectSpendAlertDeleted, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSpendAlerts: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.projects.spend_alerts.create( + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.projects.spend_alerts.create( + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + "subject_prefix": "subject_prefix", + }, + threshold_amount=0, + ) + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.projects.spend_alerts.with_raw_response.create( + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.projects.spend_alerts.with_streaming_response.create( + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = response.parse() + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.spend_alerts.with_raw_response.create( + project_id="", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.projects.spend_alerts.update( + alert_id="alert_id", + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.projects.spend_alerts.update( + alert_id="alert_id", + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + "subject_prefix": "subject_prefix", + }, + threshold_amount=0, + ) + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.projects.spend_alerts.with_raw_response.update( + alert_id="alert_id", + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.projects.spend_alerts.with_streaming_response.update( + alert_id="alert_id", + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = response.parse() + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.spend_alerts.with_raw_response.update( + alert_id="alert_id", + project_id="", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `alert_id` but received ''"): + client.admin.organization.projects.spend_alerts.with_raw_response.update( + alert_id="", + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.projects.spend_alerts.list( + project_id="project_id", + ) + assert_matches_type(SyncConversationCursorPage[ProjectSpendAlert], spend_alert, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.projects.spend_alerts.list( + project_id="project_id", + after="after", + before="before", + limit=0, + order="asc", + ) + assert_matches_type(SyncConversationCursorPage[ProjectSpendAlert], spend_alert, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.projects.spend_alerts.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(SyncConversationCursorPage[ProjectSpendAlert], spend_alert, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.projects.spend_alerts.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = response.parse() + assert_matches_type(SyncConversationCursorPage[ProjectSpendAlert], spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.spend_alerts.with_raw_response.list( + project_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.projects.spend_alerts.delete( + alert_id="alert_id", + project_id="project_id", + ) + assert_matches_type(ProjectSpendAlertDeleted, spend_alert, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.projects.spend_alerts.with_raw_response.delete( + alert_id="alert_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(ProjectSpendAlertDeleted, spend_alert, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.projects.spend_alerts.with_streaming_response.delete( + alert_id="alert_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = response.parse() + assert_matches_type(ProjectSpendAlertDeleted, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.spend_alerts.with_raw_response.delete( + alert_id="alert_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `alert_id` but received ''"): + client.admin.organization.projects.spend_alerts.with_raw_response.delete( + alert_id="", + project_id="project_id", + ) + + +class TestAsyncSpendAlerts: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.projects.spend_alerts.create( + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.projects.spend_alerts.create( + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + "subject_prefix": "subject_prefix", + }, + threshold_amount=0, + ) + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.spend_alerts.with_raw_response.create( + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.spend_alerts.with_streaming_response.create( + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = await response.parse() + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.spend_alerts.with_raw_response.create( + project_id="", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.projects.spend_alerts.update( + alert_id="alert_id", + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.projects.spend_alerts.update( + alert_id="alert_id", + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + "subject_prefix": "subject_prefix", + }, + threshold_amount=0, + ) + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.spend_alerts.with_raw_response.update( + alert_id="alert_id", + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.spend_alerts.with_streaming_response.update( + alert_id="alert_id", + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = await response.parse() + assert_matches_type(ProjectSpendAlert, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.spend_alerts.with_raw_response.update( + alert_id="alert_id", + project_id="", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `alert_id` but received ''"): + await async_client.admin.organization.projects.spend_alerts.with_raw_response.update( + alert_id="", + project_id="project_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.projects.spend_alerts.list( + project_id="project_id", + ) + assert_matches_type(AsyncConversationCursorPage[ProjectSpendAlert], spend_alert, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.projects.spend_alerts.list( + project_id="project_id", + after="after", + before="before", + limit=0, + order="asc", + ) + assert_matches_type(AsyncConversationCursorPage[ProjectSpendAlert], spend_alert, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.spend_alerts.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(AsyncConversationCursorPage[ProjectSpendAlert], spend_alert, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.spend_alerts.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = await response.parse() + assert_matches_type(AsyncConversationCursorPage[ProjectSpendAlert], spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.spend_alerts.with_raw_response.list( + project_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.projects.spend_alerts.delete( + alert_id="alert_id", + project_id="project_id", + ) + assert_matches_type(ProjectSpendAlertDeleted, spend_alert, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.spend_alerts.with_raw_response.delete( + alert_id="alert_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(ProjectSpendAlertDeleted, spend_alert, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.spend_alerts.with_streaming_response.delete( + alert_id="alert_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = await response.parse() + assert_matches_type(ProjectSpendAlertDeleted, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.spend_alerts.with_raw_response.delete( + alert_id="alert_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `alert_id` but received ''"): + await async_client.admin.organization.projects.spend_alerts.with_raw_response.delete( + alert_id="", + project_id="project_id", + ) diff --git a/tests/api_resources/admin/organization/projects/test_users.py b/tests/api_resources/admin/organization/projects/test_users.py new file mode 100644 index 0000000000..66005bf657 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/test_users.py @@ -0,0 +1,532 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization.projects import ( + ProjectUser, + UserDeleteResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestUsers: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + user = client.admin.organization.projects.users.create( + project_id="project_id", + role="role", + ) + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + user = client.admin.organization.projects.users.create( + project_id="project_id", + role="role", + email="email", + user_id="user_id", + ) + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.projects.users.with_raw_response.create( + project_id="project_id", + role="role", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.projects.users.with_streaming_response.create( + project_id="project_id", + role="role", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.users.with_raw_response.create( + project_id="", + role="role", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + user = client.admin.organization.projects.users.retrieve( + user_id="user_id", + project_id="project_id", + ) + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.users.with_raw_response.retrieve( + user_id="user_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.users.with_streaming_response.retrieve( + user_id="user_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.users.with_raw_response.retrieve( + user_id="user_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.projects.users.with_raw_response.retrieve( + user_id="", + project_id="project_id", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + user = client.admin.organization.projects.users.update( + user_id="user_id", + project_id="project_id", + ) + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + user = client.admin.organization.projects.users.update( + user_id="user_id", + project_id="project_id", + role="role", + ) + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.projects.users.with_raw_response.update( + user_id="user_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.projects.users.with_streaming_response.update( + user_id="user_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.users.with_raw_response.update( + user_id="user_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.projects.users.with_raw_response.update( + user_id="", + project_id="project_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + user = client.admin.organization.projects.users.list( + project_id="project_id", + ) + assert_matches_type(SyncConversationCursorPage[ProjectUser], user, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + user = client.admin.organization.projects.users.list( + project_id="project_id", + after="after", + limit=0, + ) + assert_matches_type(SyncConversationCursorPage[ProjectUser], user, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.projects.users.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(SyncConversationCursorPage[ProjectUser], user, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.projects.users.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(SyncConversationCursorPage[ProjectUser], user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.users.with_raw_response.list( + project_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + user = client.admin.organization.projects.users.delete( + user_id="user_id", + project_id="project_id", + ) + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.projects.users.with_raw_response.delete( + user_id="user_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.projects.users.with_streaming_response.delete( + user_id="user_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.users.with_raw_response.delete( + user_id="user_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.projects.users.with_raw_response.delete( + user_id="", + project_id="project_id", + ) + + +class TestAsyncUsers: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.projects.users.create( + project_id="project_id", + role="role", + ) + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.projects.users.create( + project_id="project_id", + role="role", + email="email", + user_id="user_id", + ) + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.users.with_raw_response.create( + project_id="project_id", + role="role", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.users.with_streaming_response.create( + project_id="project_id", + role="role", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.users.with_raw_response.create( + project_id="", + role="role", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.projects.users.retrieve( + user_id="user_id", + project_id="project_id", + ) + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.users.with_raw_response.retrieve( + user_id="user_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.users.with_streaming_response.retrieve( + user_id="user_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.users.with_raw_response.retrieve( + user_id="user_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.projects.users.with_raw_response.retrieve( + user_id="", + project_id="project_id", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.projects.users.update( + user_id="user_id", + project_id="project_id", + ) + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.projects.users.update( + user_id="user_id", + project_id="project_id", + role="role", + ) + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.users.with_raw_response.update( + user_id="user_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.users.with_streaming_response.update( + user_id="user_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(ProjectUser, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.users.with_raw_response.update( + user_id="user_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.projects.users.with_raw_response.update( + user_id="", + project_id="project_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.projects.users.list( + project_id="project_id", + ) + assert_matches_type(AsyncConversationCursorPage[ProjectUser], user, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.projects.users.list( + project_id="project_id", + after="after", + limit=0, + ) + assert_matches_type(AsyncConversationCursorPage[ProjectUser], user, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.users.with_raw_response.list( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(AsyncConversationCursorPage[ProjectUser], user, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.users.with_streaming_response.list( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(AsyncConversationCursorPage[ProjectUser], user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.users.with_raw_response.list( + project_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.projects.users.delete( + user_id="user_id", + project_id="project_id", + ) + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.users.with_raw_response.delete( + user_id="user_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.users.with_streaming_response.delete( + user_id="user_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.users.with_raw_response.delete( + user_id="user_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.projects.users.with_raw_response.delete( + user_id="", + project_id="project_id", + ) diff --git a/tests/api_resources/admin/organization/projects/users/__init__.py b/tests/api_resources/admin/organization/projects/users/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/admin/organization/projects/users/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/admin/organization/projects/users/test_roles.py b/tests/api_resources/admin/organization/projects/users/test_roles.py new file mode 100644 index 0000000000..5212e5a59c --- /dev/null +++ b/tests/api_resources/admin/organization/projects/users/test_roles.py @@ -0,0 +1,494 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncNextCursorPage, AsyncNextCursorPage +from openai.types.admin.organization.projects.users import ( + RoleListResponse, + RoleCreateResponse, + RoleDeleteResponse, + RoleRetrieveResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRoles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + role = client.admin.organization.projects.users.roles.create( + user_id="user_id", + project_id="project_id", + role_id="role_id", + ) + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.projects.users.roles.with_raw_response.create( + user_id="user_id", + project_id="project_id", + role_id="role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.projects.users.roles.with_streaming_response.create( + user_id="user_id", + project_id="project_id", + role_id="role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.users.roles.with_raw_response.create( + user_id="user_id", + project_id="", + role_id="role_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.projects.users.roles.with_raw_response.create( + user_id="", + project_id="project_id", + role_id="role_id", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + role = client.admin.organization.projects.users.roles.retrieve( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.users.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.users.roles.with_streaming_response.retrieve( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.users.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="", + user_id="user_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.projects.users.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="project_id", + user_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.projects.users.roles.with_raw_response.retrieve( + role_id="", + project_id="project_id", + user_id="user_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + role = client.admin.organization.projects.users.roles.list( + user_id="user_id", + project_id="project_id", + ) + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + role = client.admin.organization.projects.users.roles.list( + user_id="user_id", + project_id="project_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.projects.users.roles.with_raw_response.list( + user_id="user_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.projects.users.roles.with_streaming_response.list( + user_id="user_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.users.roles.with_raw_response.list( + user_id="user_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.projects.users.roles.with_raw_response.list( + user_id="", + project_id="project_id", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + role = client.admin.organization.projects.users.roles.delete( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.projects.users.roles.with_raw_response.delete( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.projects.users.roles.with_streaming_response.delete( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.users.roles.with_raw_response.delete( + role_id="role_id", + project_id="", + user_id="user_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.projects.users.roles.with_raw_response.delete( + role_id="role_id", + project_id="project_id", + user_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.projects.users.roles.with_raw_response.delete( + role_id="", + project_id="project_id", + user_id="user_id", + ) + + +class TestAsyncRoles: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.users.roles.create( + user_id="user_id", + project_id="project_id", + role_id="role_id", + ) + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.users.roles.with_raw_response.create( + user_id="user_id", + project_id="project_id", + role_id="role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.users.roles.with_streaming_response.create( + user_id="user_id", + project_id="project_id", + role_id="role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.users.roles.with_raw_response.create( + user_id="user_id", + project_id="", + role_id="role_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.projects.users.roles.with_raw_response.create( + user_id="", + project_id="project_id", + role_id="role_id", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.users.roles.retrieve( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.users.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.users.roles.with_streaming_response.retrieve( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.users.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="", + user_id="user_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.projects.users.roles.with_raw_response.retrieve( + role_id="role_id", + project_id="project_id", + user_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.projects.users.roles.with_raw_response.retrieve( + role_id="", + project_id="project_id", + user_id="user_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.users.roles.list( + user_id="user_id", + project_id="project_id", + ) + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.users.roles.list( + user_id="user_id", + project_id="project_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.users.roles.with_raw_response.list( + user_id="user_id", + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.users.roles.with_streaming_response.list( + user_id="user_id", + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.users.roles.with_raw_response.list( + user_id="user_id", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.projects.users.roles.with_raw_response.list( + user_id="", + project_id="project_id", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.projects.users.roles.delete( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.users.roles.with_raw_response.delete( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.users.roles.with_streaming_response.delete( + role_id="role_id", + project_id="project_id", + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.users.roles.with_raw_response.delete( + role_id="role_id", + project_id="", + user_id="user_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.projects.users.roles.with_raw_response.delete( + role_id="role_id", + project_id="project_id", + user_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.projects.users.roles.with_raw_response.delete( + role_id="", + project_id="project_id", + user_id="user_id", + ) diff --git a/tests/api_resources/admin/organization/test_admin_api_keys.py b/tests/api_resources/admin/organization/test_admin_api_keys.py new file mode 100644 index 0000000000..59eed910e8 --- /dev/null +++ b/tests/api_resources/admin/organization/test_admin_api_keys.py @@ -0,0 +1,311 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncCursorPage, AsyncCursorPage +from openai.types.admin.organization import ( + AdminAPIKey, + AdminAPIKeyCreateResponse, + AdminAPIKeyDeleteResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAdminAPIKeys: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + admin_api_key = client.admin.organization.admin_api_keys.create( + name="New Admin Key", + ) + assert_matches_type(AdminAPIKeyCreateResponse, admin_api_key, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.admin_api_keys.with_raw_response.create( + name="New Admin Key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + admin_api_key = response.parse() + assert_matches_type(AdminAPIKeyCreateResponse, admin_api_key, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.admin_api_keys.with_streaming_response.create( + name="New Admin Key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + admin_api_key = response.parse() + assert_matches_type(AdminAPIKeyCreateResponse, admin_api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + admin_api_key = client.admin.organization.admin_api_keys.retrieve( + "key_id", + ) + assert_matches_type(AdminAPIKey, admin_api_key, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.admin_api_keys.with_raw_response.retrieve( + "key_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + admin_api_key = response.parse() + assert_matches_type(AdminAPIKey, admin_api_key, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.admin_api_keys.with_streaming_response.retrieve( + "key_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + admin_api_key = response.parse() + assert_matches_type(AdminAPIKey, admin_api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `key_id` but received ''"): + client.admin.organization.admin_api_keys.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + admin_api_key = client.admin.organization.admin_api_keys.list() + assert_matches_type(SyncCursorPage[AdminAPIKey], admin_api_key, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + admin_api_key = client.admin.organization.admin_api_keys.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncCursorPage[AdminAPIKey], admin_api_key, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.admin_api_keys.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + admin_api_key = response.parse() + assert_matches_type(SyncCursorPage[AdminAPIKey], admin_api_key, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.admin_api_keys.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + admin_api_key = response.parse() + assert_matches_type(SyncCursorPage[AdminAPIKey], admin_api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + admin_api_key = client.admin.organization.admin_api_keys.delete( + "key_id", + ) + assert_matches_type(AdminAPIKeyDeleteResponse, admin_api_key, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.admin_api_keys.with_raw_response.delete( + "key_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + admin_api_key = response.parse() + assert_matches_type(AdminAPIKeyDeleteResponse, admin_api_key, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.admin_api_keys.with_streaming_response.delete( + "key_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + admin_api_key = response.parse() + assert_matches_type(AdminAPIKeyDeleteResponse, admin_api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `key_id` but received ''"): + client.admin.organization.admin_api_keys.with_raw_response.delete( + "", + ) + + +class TestAsyncAdminAPIKeys: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + admin_api_key = await async_client.admin.organization.admin_api_keys.create( + name="New Admin Key", + ) + assert_matches_type(AdminAPIKeyCreateResponse, admin_api_key, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.admin_api_keys.with_raw_response.create( + name="New Admin Key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + admin_api_key = response.parse() + assert_matches_type(AdminAPIKeyCreateResponse, admin_api_key, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.admin_api_keys.with_streaming_response.create( + name="New Admin Key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + admin_api_key = await response.parse() + assert_matches_type(AdminAPIKeyCreateResponse, admin_api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + admin_api_key = await async_client.admin.organization.admin_api_keys.retrieve( + "key_id", + ) + assert_matches_type(AdminAPIKey, admin_api_key, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.admin_api_keys.with_raw_response.retrieve( + "key_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + admin_api_key = response.parse() + assert_matches_type(AdminAPIKey, admin_api_key, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.admin_api_keys.with_streaming_response.retrieve( + "key_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + admin_api_key = await response.parse() + assert_matches_type(AdminAPIKey, admin_api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `key_id` but received ''"): + await async_client.admin.organization.admin_api_keys.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + admin_api_key = await async_client.admin.organization.admin_api_keys.list() + assert_matches_type(AsyncCursorPage[AdminAPIKey], admin_api_key, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + admin_api_key = await async_client.admin.organization.admin_api_keys.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncCursorPage[AdminAPIKey], admin_api_key, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.admin_api_keys.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + admin_api_key = response.parse() + assert_matches_type(AsyncCursorPage[AdminAPIKey], admin_api_key, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.admin_api_keys.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + admin_api_key = await response.parse() + assert_matches_type(AsyncCursorPage[AdminAPIKey], admin_api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + admin_api_key = await async_client.admin.organization.admin_api_keys.delete( + "key_id", + ) + assert_matches_type(AdminAPIKeyDeleteResponse, admin_api_key, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.admin_api_keys.with_raw_response.delete( + "key_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + admin_api_key = response.parse() + assert_matches_type(AdminAPIKeyDeleteResponse, admin_api_key, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.admin_api_keys.with_streaming_response.delete( + "key_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + admin_api_key = await response.parse() + assert_matches_type(AdminAPIKeyDeleteResponse, admin_api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `key_id` but received ''"): + await async_client.admin.organization.admin_api_keys.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/admin/organization/test_audit_logs.py b/tests/api_resources/admin/organization/test_audit_logs.py new file mode 100644 index 0000000000..5e696461fa --- /dev/null +++ b/tests/api_resources/admin/organization/test_audit_logs.py @@ -0,0 +1,115 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization import AuditLogListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAuditLogs: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + audit_log = client.admin.organization.audit_logs.list() + assert_matches_type(SyncConversationCursorPage[AuditLogListResponse], audit_log, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + audit_log = client.admin.organization.audit_logs.list( + actor_emails=["string"], + actor_ids=["string"], + after="after", + before="before", + effective_at={ + "gt": 0, + "gte": 0, + "lt": 0, + "lte": 0, + }, + event_types=["api_key.created"], + limit=0, + project_ids=["string"], + resource_ids=["string"], + ) + assert_matches_type(SyncConversationCursorPage[AuditLogListResponse], audit_log, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.audit_logs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audit_log = response.parse() + assert_matches_type(SyncConversationCursorPage[AuditLogListResponse], audit_log, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.audit_logs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audit_log = response.parse() + assert_matches_type(SyncConversationCursorPage[AuditLogListResponse], audit_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncAuditLogs: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + audit_log = await async_client.admin.organization.audit_logs.list() + assert_matches_type(AsyncConversationCursorPage[AuditLogListResponse], audit_log, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + audit_log = await async_client.admin.organization.audit_logs.list( + actor_emails=["string"], + actor_ids=["string"], + after="after", + before="before", + effective_at={ + "gt": 0, + "gte": 0, + "lt": 0, + "lte": 0, + }, + event_types=["api_key.created"], + limit=0, + project_ids=["string"], + resource_ids=["string"], + ) + assert_matches_type(AsyncConversationCursorPage[AuditLogListResponse], audit_log, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.audit_logs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audit_log = response.parse() + assert_matches_type(AsyncConversationCursorPage[AuditLogListResponse], audit_log, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.audit_logs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audit_log = await response.parse() + assert_matches_type(AsyncConversationCursorPage[AuditLogListResponse], audit_log, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/admin/organization/test_certificates.py b/tests/api_resources/admin/organization/test_certificates.py new file mode 100644 index 0000000000..209fd9220d --- /dev/null +++ b/tests/api_resources/admin/organization/test_certificates.py @@ -0,0 +1,561 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncPage, AsyncPage, SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization import ( + Certificate, + CertificateListResponse, + CertificateDeleteResponse, + CertificateActivateResponse, + CertificateDeactivateResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCertificates: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.create( + certificate="certificate", + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.create( + certificate="certificate", + name="name", + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.certificates.with_raw_response.create( + certificate="certificate", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.certificates.with_streaming_response.create( + certificate="certificate", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.retrieve( + certificate_id="certificate_id", + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.retrieve( + certificate_id="certificate_id", + include=["content"], + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.certificates.with_raw_response.retrieve( + certificate_id="certificate_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.certificates.with_streaming_response.retrieve( + certificate_id="certificate_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `certificate_id` but received ''"): + client.admin.organization.certificates.with_raw_response.retrieve( + certificate_id="", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.update( + certificate_id="certificate_id", + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.update( + certificate_id="certificate_id", + name="name", + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.certificates.with_raw_response.update( + certificate_id="certificate_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.certificates.with_streaming_response.update( + certificate_id="certificate_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `certificate_id` but received ''"): + client.admin.organization.certificates.with_raw_response.update( + certificate_id="", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.list() + assert_matches_type(SyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.certificates.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(SyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.certificates.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = response.parse() + assert_matches_type(SyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.delete( + "certificate_id", + ) + assert_matches_type(CertificateDeleteResponse, certificate, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.certificates.with_raw_response.delete( + "certificate_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(CertificateDeleteResponse, certificate, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.certificates.with_streaming_response.delete( + "certificate_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = response.parse() + assert_matches_type(CertificateDeleteResponse, certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `certificate_id` but received ''"): + client.admin.organization.certificates.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_activate(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.activate( + certificate_ids=["cert_abc"], + ) + assert_matches_type(SyncPage[CertificateActivateResponse], certificate, path=["response"]) + + @parametrize + def test_raw_response_activate(self, client: OpenAI) -> None: + response = client.admin.organization.certificates.with_raw_response.activate( + certificate_ids=["cert_abc"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(SyncPage[CertificateActivateResponse], certificate, path=["response"]) + + @parametrize + def test_streaming_response_activate(self, client: OpenAI) -> None: + with client.admin.organization.certificates.with_streaming_response.activate( + certificate_ids=["cert_abc"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = response.parse() + assert_matches_type(SyncPage[CertificateActivateResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_deactivate(self, client: OpenAI) -> None: + certificate = client.admin.organization.certificates.deactivate( + certificate_ids=["cert_abc"], + ) + assert_matches_type(SyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + @parametrize + def test_raw_response_deactivate(self, client: OpenAI) -> None: + response = client.admin.organization.certificates.with_raw_response.deactivate( + certificate_ids=["cert_abc"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(SyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + @parametrize + def test_streaming_response_deactivate(self, client: OpenAI) -> None: + with client.admin.organization.certificates.with_streaming_response.deactivate( + certificate_ids=["cert_abc"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = response.parse() + assert_matches_type(SyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncCertificates: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.create( + certificate="certificate", + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.create( + certificate="certificate", + name="name", + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.certificates.with_raw_response.create( + certificate="certificate", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.certificates.with_streaming_response.create( + certificate="certificate", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = await response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.retrieve( + certificate_id="certificate_id", + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.retrieve( + certificate_id="certificate_id", + include=["content"], + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.certificates.with_raw_response.retrieve( + certificate_id="certificate_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.certificates.with_streaming_response.retrieve( + certificate_id="certificate_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = await response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `certificate_id` but received ''"): + await async_client.admin.organization.certificates.with_raw_response.retrieve( + certificate_id="", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.update( + certificate_id="certificate_id", + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.update( + certificate_id="certificate_id", + name="name", + ) + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.certificates.with_raw_response.update( + certificate_id="certificate_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.certificates.with_streaming_response.update( + certificate_id="certificate_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = await response.parse() + assert_matches_type(Certificate, certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `certificate_id` but received ''"): + await async_client.admin.organization.certificates.with_raw_response.update( + certificate_id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.list() + assert_matches_type(AsyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.certificates.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(AsyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.certificates.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = await response.parse() + assert_matches_type(AsyncConversationCursorPage[CertificateListResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.delete( + "certificate_id", + ) + assert_matches_type(CertificateDeleteResponse, certificate, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.certificates.with_raw_response.delete( + "certificate_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(CertificateDeleteResponse, certificate, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.certificates.with_streaming_response.delete( + "certificate_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = await response.parse() + assert_matches_type(CertificateDeleteResponse, certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `certificate_id` but received ''"): + await async_client.admin.organization.certificates.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_activate(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.activate( + certificate_ids=["cert_abc"], + ) + assert_matches_type(AsyncPage[CertificateActivateResponse], certificate, path=["response"]) + + @parametrize + async def test_raw_response_activate(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.certificates.with_raw_response.activate( + certificate_ids=["cert_abc"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(AsyncPage[CertificateActivateResponse], certificate, path=["response"]) + + @parametrize + async def test_streaming_response_activate(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.certificates.with_streaming_response.activate( + certificate_ids=["cert_abc"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = await response.parse() + assert_matches_type(AsyncPage[CertificateActivateResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_deactivate(self, async_client: AsyncOpenAI) -> None: + certificate = await async_client.admin.organization.certificates.deactivate( + certificate_ids=["cert_abc"], + ) + assert_matches_type(AsyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + @parametrize + async def test_raw_response_deactivate(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.certificates.with_raw_response.deactivate( + certificate_ids=["cert_abc"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + certificate = response.parse() + assert_matches_type(AsyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + @parametrize + async def test_streaming_response_deactivate(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.certificates.with_streaming_response.deactivate( + certificate_ids=["cert_abc"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + certificate = await response.parse() + assert_matches_type(AsyncPage[CertificateDeactivateResponse], certificate, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/admin/organization/test_data_retention.py b/tests/api_resources/admin/organization/test_data_retention.py new file mode 100644 index 0000000000..8d943832e6 --- /dev/null +++ b/tests/api_resources/admin/organization/test_data_retention.py @@ -0,0 +1,136 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types.admin.organization import OrganizationDataRetention + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestDataRetention: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + data_retention = client.admin.organization.data_retention.retrieve() + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.data_retention.with_raw_response.retrieve() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_retention = response.parse() + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.data_retention.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_retention = response.parse() + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + data_retention = client.admin.organization.data_retention.update( + retention_type="zero_data_retention", + ) + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.data_retention.with_raw_response.update( + retention_type="zero_data_retention", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_retention = response.parse() + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.data_retention.with_streaming_response.update( + retention_type="zero_data_retention", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_retention = response.parse() + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncDataRetention: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + data_retention = await async_client.admin.organization.data_retention.retrieve() + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.data_retention.with_raw_response.retrieve() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_retention = response.parse() + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.data_retention.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_retention = await response.parse() + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + data_retention = await async_client.admin.organization.data_retention.update( + retention_type="zero_data_retention", + ) + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.data_retention.with_raw_response.update( + retention_type="zero_data_retention", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_retention = response.parse() + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.data_retention.with_streaming_response.update( + retention_type="zero_data_retention", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_retention = await response.parse() + assert_matches_type(OrganizationDataRetention, data_retention, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/admin/organization/test_groups.py b/tests/api_resources/admin/organization/test_groups.py new file mode 100644 index 0000000000..d97ddf579e --- /dev/null +++ b/tests/api_resources/admin/organization/test_groups.py @@ -0,0 +1,395 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncNextCursorPage, AsyncNextCursorPage +from openai.types.admin.organization import ( + Group, + GroupDeleteResponse, + GroupUpdateResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestGroups: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + group = client.admin.organization.groups.create( + name="x", + ) + assert_matches_type(Group, group, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.groups.with_raw_response.create( + name="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(Group, group, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.groups.with_streaming_response.create( + name="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = response.parse() + assert_matches_type(Group, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + group = client.admin.organization.groups.retrieve( + "group_id", + ) + assert_matches_type(Group, group, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.groups.with_raw_response.retrieve( + "group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(Group, group, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.groups.with_streaming_response.retrieve( + "group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = response.parse() + assert_matches_type(Group, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + group = client.admin.organization.groups.update( + group_id="group_id", + name="x", + ) + assert_matches_type(GroupUpdateResponse, group, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.groups.with_raw_response.update( + group_id="group_id", + name="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(GroupUpdateResponse, group, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.groups.with_streaming_response.update( + group_id="group_id", + name="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = response.parse() + assert_matches_type(GroupUpdateResponse, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.with_raw_response.update( + group_id="", + name="x", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + group = client.admin.organization.groups.list() + assert_matches_type(SyncNextCursorPage[Group], group, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + group = client.admin.organization.groups.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncNextCursorPage[Group], group, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.groups.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(SyncNextCursorPage[Group], group, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.groups.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = response.parse() + assert_matches_type(SyncNextCursorPage[Group], group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + group = client.admin.organization.groups.delete( + "group_id", + ) + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.groups.with_raw_response.delete( + "group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.groups.with_streaming_response.delete( + "group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = response.parse() + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + client.admin.organization.groups.with_raw_response.delete( + "", + ) + + +class TestAsyncGroups: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.groups.create( + name="x", + ) + assert_matches_type(Group, group, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.with_raw_response.create( + name="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(Group, group, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.with_streaming_response.create( + name="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = await response.parse() + assert_matches_type(Group, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.groups.retrieve( + "group_id", + ) + assert_matches_type(Group, group, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.with_raw_response.retrieve( + "group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(Group, group, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.with_streaming_response.retrieve( + "group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = await response.parse() + assert_matches_type(Group, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.groups.update( + group_id="group_id", + name="x", + ) + assert_matches_type(GroupUpdateResponse, group, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.with_raw_response.update( + group_id="group_id", + name="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(GroupUpdateResponse, group, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.with_streaming_response.update( + group_id="group_id", + name="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = await response.parse() + assert_matches_type(GroupUpdateResponse, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.with_raw_response.update( + group_id="", + name="x", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.groups.list() + assert_matches_type(AsyncNextCursorPage[Group], group, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.groups.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncNextCursorPage[Group], group, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(AsyncNextCursorPage[Group], group, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = await response.parse() + assert_matches_type(AsyncNextCursorPage[Group], group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + group = await async_client.admin.organization.groups.delete( + "group_id", + ) + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.groups.with_raw_response.delete( + "group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.groups.with_streaming_response.delete( + "group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = await response.parse() + assert_matches_type(GroupDeleteResponse, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): + await async_client.admin.organization.groups.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/admin/organization/test_invites.py b/tests/api_resources/admin/organization/test_invites.py new file mode 100644 index 0000000000..ddad6e6e31 --- /dev/null +++ b/tests/api_resources/admin/organization/test_invites.py @@ -0,0 +1,339 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization import Invite, InviteDeleteResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestInvites: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + invite = client.admin.organization.invites.create( + email="email", + role="reader", + ) + assert_matches_type(Invite, invite, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + invite = client.admin.organization.invites.create( + email="email", + role="reader", + projects=[ + { + "id": "id", + "role": "member", + } + ], + ) + assert_matches_type(Invite, invite, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.invites.with_raw_response.create( + email="email", + role="reader", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invite = response.parse() + assert_matches_type(Invite, invite, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.invites.with_streaming_response.create( + email="email", + role="reader", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invite = response.parse() + assert_matches_type(Invite, invite, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + invite = client.admin.organization.invites.retrieve( + "invite_id", + ) + assert_matches_type(Invite, invite, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.invites.with_raw_response.retrieve( + "invite_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invite = response.parse() + assert_matches_type(Invite, invite, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.invites.with_streaming_response.retrieve( + "invite_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invite = response.parse() + assert_matches_type(Invite, invite, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invite_id` but received ''"): + client.admin.organization.invites.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + invite = client.admin.organization.invites.list() + assert_matches_type(SyncConversationCursorPage[Invite], invite, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + invite = client.admin.organization.invites.list( + after="after", + limit=0, + ) + assert_matches_type(SyncConversationCursorPage[Invite], invite, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.invites.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invite = response.parse() + assert_matches_type(SyncConversationCursorPage[Invite], invite, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.invites.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invite = response.parse() + assert_matches_type(SyncConversationCursorPage[Invite], invite, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + invite = client.admin.organization.invites.delete( + "invite_id", + ) + assert_matches_type(InviteDeleteResponse, invite, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.invites.with_raw_response.delete( + "invite_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invite = response.parse() + assert_matches_type(InviteDeleteResponse, invite, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.invites.with_streaming_response.delete( + "invite_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invite = response.parse() + assert_matches_type(InviteDeleteResponse, invite, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invite_id` but received ''"): + client.admin.organization.invites.with_raw_response.delete( + "", + ) + + +class TestAsyncInvites: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + invite = await async_client.admin.organization.invites.create( + email="email", + role="reader", + ) + assert_matches_type(Invite, invite, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + invite = await async_client.admin.organization.invites.create( + email="email", + role="reader", + projects=[ + { + "id": "id", + "role": "member", + } + ], + ) + assert_matches_type(Invite, invite, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.invites.with_raw_response.create( + email="email", + role="reader", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invite = response.parse() + assert_matches_type(Invite, invite, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.invites.with_streaming_response.create( + email="email", + role="reader", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invite = await response.parse() + assert_matches_type(Invite, invite, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + invite = await async_client.admin.organization.invites.retrieve( + "invite_id", + ) + assert_matches_type(Invite, invite, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.invites.with_raw_response.retrieve( + "invite_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invite = response.parse() + assert_matches_type(Invite, invite, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.invites.with_streaming_response.retrieve( + "invite_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invite = await response.parse() + assert_matches_type(Invite, invite, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invite_id` but received ''"): + await async_client.admin.organization.invites.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + invite = await async_client.admin.organization.invites.list() + assert_matches_type(AsyncConversationCursorPage[Invite], invite, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + invite = await async_client.admin.organization.invites.list( + after="after", + limit=0, + ) + assert_matches_type(AsyncConversationCursorPage[Invite], invite, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.invites.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invite = response.parse() + assert_matches_type(AsyncConversationCursorPage[Invite], invite, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.invites.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invite = await response.parse() + assert_matches_type(AsyncConversationCursorPage[Invite], invite, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + invite = await async_client.admin.organization.invites.delete( + "invite_id", + ) + assert_matches_type(InviteDeleteResponse, invite, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.invites.with_raw_response.delete( + "invite_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invite = response.parse() + assert_matches_type(InviteDeleteResponse, invite, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.invites.with_streaming_response.delete( + "invite_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invite = await response.parse() + assert_matches_type(InviteDeleteResponse, invite, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invite_id` but received ''"): + await async_client.admin.organization.invites.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/admin/organization/test_projects.py b/tests/api_resources/admin/organization/test_projects.py new file mode 100644 index 0000000000..5e07ff1496 --- /dev/null +++ b/tests/api_resources/admin/organization/test_projects.py @@ -0,0 +1,421 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization import Project + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestProjects: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + project = client.admin.organization.projects.create( + name="name", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + project = client.admin.organization.projects.create( + name="name", + external_key_id="external_key_id", + geography="geography", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.projects.with_raw_response.create( + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.projects.with_streaming_response.create( + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + project = client.admin.organization.projects.retrieve( + "project_id", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.projects.with_raw_response.retrieve( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.projects.with_streaming_response.retrieve( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + project = client.admin.organization.projects.update( + project_id="project_id", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + project = client.admin.organization.projects.update( + project_id="project_id", + external_key_id="external_key_id", + geography="geography", + name="name", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.projects.with_raw_response.update( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.projects.with_streaming_response.update( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.with_raw_response.update( + project_id="", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + project = client.admin.organization.projects.list() + assert_matches_type(SyncConversationCursorPage[Project], project, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + project = client.admin.organization.projects.list( + after="after", + include_archived=True, + limit=0, + ) + assert_matches_type(SyncConversationCursorPage[Project], project, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.projects.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(SyncConversationCursorPage[Project], project, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.projects.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(SyncConversationCursorPage[Project], project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_archive(self, client: OpenAI) -> None: + project = client.admin.organization.projects.archive( + "project_id", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + def test_raw_response_archive(self, client: OpenAI) -> None: + response = client.admin.organization.projects.with_raw_response.archive( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + @parametrize + def test_streaming_response_archive(self, client: OpenAI) -> None: + with client.admin.organization.projects.with_streaming_response.archive( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_archive(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.admin.organization.projects.with_raw_response.archive( + "", + ) + + +class TestAsyncProjects: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + project = await async_client.admin.organization.projects.create( + name="name", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + project = await async_client.admin.organization.projects.create( + name="name", + external_key_id="external_key_id", + geography="geography", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.with_raw_response.create( + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.with_streaming_response.create( + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(Project, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + project = await async_client.admin.organization.projects.retrieve( + "project_id", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.with_raw_response.retrieve( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.with_streaming_response.retrieve( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(Project, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + project = await async_client.admin.organization.projects.update( + project_id="project_id", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + project = await async_client.admin.organization.projects.update( + project_id="project_id", + external_key_id="external_key_id", + geography="geography", + name="name", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.with_raw_response.update( + project_id="project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.with_streaming_response.update( + project_id="project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(Project, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.with_raw_response.update( + project_id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + project = await async_client.admin.organization.projects.list() + assert_matches_type(AsyncConversationCursorPage[Project], project, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + project = await async_client.admin.organization.projects.list( + after="after", + include_archived=True, + limit=0, + ) + assert_matches_type(AsyncConversationCursorPage[Project], project, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(AsyncConversationCursorPage[Project], project, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(AsyncConversationCursorPage[Project], project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_archive(self, async_client: AsyncOpenAI) -> None: + project = await async_client.admin.organization.projects.archive( + "project_id", + ) + assert_matches_type(Project, project, path=["response"]) + + @parametrize + async def test_raw_response_archive(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.projects.with_raw_response.archive( + "project_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(Project, project, path=["response"]) + + @parametrize + async def test_streaming_response_archive(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.projects.with_streaming_response.archive( + "project_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(Project, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_archive(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.admin.organization.projects.with_raw_response.archive( + "", + ) diff --git a/tests/api_resources/admin/organization/test_roles.py b/tests/api_resources/admin/organization/test_roles.py new file mode 100644 index 0000000000..fbb8515f1c --- /dev/null +++ b/tests/api_resources/admin/organization/test_roles.py @@ -0,0 +1,430 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncNextCursorPage, AsyncNextCursorPage +from openai.types.admin.organization import ( + Role, + RoleDeleteResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRoles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + role = client.admin.organization.roles.create( + permissions=["string"], + role_name="role_name", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + role = client.admin.organization.roles.create( + permissions=["string"], + role_name="role_name", + description="description", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.roles.with_raw_response.create( + permissions=["string"], + role_name="role_name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.roles.with_streaming_response.create( + permissions=["string"], + role_name="role_name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + role = client.admin.organization.roles.retrieve( + "role_id", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.roles.with_raw_response.retrieve( + "role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.roles.with_streaming_response.retrieve( + "role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.roles.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + role = client.admin.organization.roles.update( + role_id="role_id", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + role = client.admin.organization.roles.update( + role_id="role_id", + description="description", + permissions=["string"], + role_name="role_name", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.roles.with_raw_response.update( + role_id="role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.roles.with_streaming_response.update( + role_id="role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.roles.with_raw_response.update( + role_id="", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + role = client.admin.organization.roles.list() + assert_matches_type(SyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + role = client.admin.organization.roles.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.roles.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(SyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.roles.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(SyncNextCursorPage[Role], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + role = client.admin.organization.roles.delete( + "role_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.roles.with_raw_response.delete( + "role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.roles.with_streaming_response.delete( + "role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.roles.with_raw_response.delete( + "", + ) + + +class TestAsyncRoles: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.roles.create( + permissions=["string"], + role_name="role_name", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.roles.create( + permissions=["string"], + role_name="role_name", + description="description", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.roles.with_raw_response.create( + permissions=["string"], + role_name="role_name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.roles.with_streaming_response.create( + permissions=["string"], + role_name="role_name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.roles.retrieve( + "role_id", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.roles.with_raw_response.retrieve( + "role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.roles.with_streaming_response.retrieve( + "role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.roles.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.roles.update( + role_id="role_id", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.roles.update( + role_id="role_id", + description="description", + permissions=["string"], + role_name="role_name", + ) + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.roles.with_raw_response.update( + role_id="role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(Role, role, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.roles.with_streaming_response.update( + role_id="role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(Role, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.roles.with_raw_response.update( + role_id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.roles.list() + assert_matches_type(AsyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.roles.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.roles.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(AsyncNextCursorPage[Role], role, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.roles.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(AsyncNextCursorPage[Role], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.roles.delete( + "role_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.roles.with_raw_response.delete( + "role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.roles.with_streaming_response.delete( + "role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.roles.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/admin/organization/test_spend_alerts.py b/tests/api_resources/admin/organization/test_spend_alerts.py new file mode 100644 index 0000000000..08a3c445c3 --- /dev/null +++ b/tests/api_resources/admin/organization/test_spend_alerts.py @@ -0,0 +1,462 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization import ( + OrganizationSpendAlert, + OrganizationSpendAlertDeleted, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSpendAlerts: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.spend_alerts.create( + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.spend_alerts.create( + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + "subject_prefix": "subject_prefix", + }, + threshold_amount=0, + ) + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.spend_alerts.with_raw_response.create( + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.spend_alerts.with_streaming_response.create( + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = response.parse() + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.spend_alerts.update( + alert_id="alert_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.spend_alerts.update( + alert_id="alert_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + "subject_prefix": "subject_prefix", + }, + threshold_amount=0, + ) + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.spend_alerts.with_raw_response.update( + alert_id="alert_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.spend_alerts.with_streaming_response.update( + alert_id="alert_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = response.parse() + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `alert_id` but received ''"): + client.admin.organization.spend_alerts.with_raw_response.update( + alert_id="", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.spend_alerts.list() + assert_matches_type(SyncConversationCursorPage[OrganizationSpendAlert], spend_alert, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.spend_alerts.list( + after="after", + before="before", + limit=0, + order="asc", + ) + assert_matches_type(SyncConversationCursorPage[OrganizationSpendAlert], spend_alert, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.spend_alerts.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(SyncConversationCursorPage[OrganizationSpendAlert], spend_alert, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.spend_alerts.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = response.parse() + assert_matches_type(SyncConversationCursorPage[OrganizationSpendAlert], spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + spend_alert = client.admin.organization.spend_alerts.delete( + "alert_id", + ) + assert_matches_type(OrganizationSpendAlertDeleted, spend_alert, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.spend_alerts.with_raw_response.delete( + "alert_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(OrganizationSpendAlertDeleted, spend_alert, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.spend_alerts.with_streaming_response.delete( + "alert_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = response.parse() + assert_matches_type(OrganizationSpendAlertDeleted, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `alert_id` but received ''"): + client.admin.organization.spend_alerts.with_raw_response.delete( + "", + ) + + +class TestAsyncSpendAlerts: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.spend_alerts.create( + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.spend_alerts.create( + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + "subject_prefix": "subject_prefix", + }, + threshold_amount=0, + ) + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.spend_alerts.with_raw_response.create( + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.spend_alerts.with_streaming_response.create( + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = await response.parse() + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.spend_alerts.update( + alert_id="alert_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.spend_alerts.update( + alert_id="alert_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + "subject_prefix": "subject_prefix", + }, + threshold_amount=0, + ) + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.spend_alerts.with_raw_response.update( + alert_id="alert_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.spend_alerts.with_streaming_response.update( + alert_id="alert_id", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = await response.parse() + assert_matches_type(OrganizationSpendAlert, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `alert_id` but received ''"): + await async_client.admin.organization.spend_alerts.with_raw_response.update( + alert_id="", + currency="USD", + interval="month", + notification_channel={ + "recipients": ["string"], + "type": "email", + }, + threshold_amount=0, + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.spend_alerts.list() + assert_matches_type(AsyncConversationCursorPage[OrganizationSpendAlert], spend_alert, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.spend_alerts.list( + after="after", + before="before", + limit=0, + order="asc", + ) + assert_matches_type(AsyncConversationCursorPage[OrganizationSpendAlert], spend_alert, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.spend_alerts.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(AsyncConversationCursorPage[OrganizationSpendAlert], spend_alert, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.spend_alerts.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = await response.parse() + assert_matches_type(AsyncConversationCursorPage[OrganizationSpendAlert], spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + spend_alert = await async_client.admin.organization.spend_alerts.delete( + "alert_id", + ) + assert_matches_type(OrganizationSpendAlertDeleted, spend_alert, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.spend_alerts.with_raw_response.delete( + "alert_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + spend_alert = response.parse() + assert_matches_type(OrganizationSpendAlertDeleted, spend_alert, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.spend_alerts.with_streaming_response.delete( + "alert_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + spend_alert = await response.parse() + assert_matches_type(OrganizationSpendAlertDeleted, spend_alert, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `alert_id` but received ''"): + await async_client.admin.organization.spend_alerts.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/admin/organization/test_usage.py b/tests/api_resources/admin/organization/test_usage.py new file mode 100644 index 0000000000..b7aef88dcb --- /dev/null +++ b/tests/api_resources/admin/organization/test_usage.py @@ -0,0 +1,1062 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types.admin.organization import ( + UsageCostsResponse, + UsageImagesResponse, + UsageEmbeddingsResponse, + UsageCompletionsResponse, + UsageModerationsResponse, + UsageVectorStoresResponse, + UsageAudioSpeechesResponse, + UsageWebSearchCallsResponse, + UsageFileSearchCallsResponse, + UsageAudioTranscriptionsResponse, + UsageCodeInterpreterSessionsResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestUsage: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_audio_speeches(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.audio_speeches( + start_time=0, + ) + assert_matches_type(UsageAudioSpeechesResponse, usage, path=["response"]) + + @parametrize + def test_method_audio_speeches_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.audio_speeches( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageAudioSpeechesResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_audio_speeches(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.audio_speeches( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageAudioSpeechesResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_audio_speeches(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.audio_speeches( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageAudioSpeechesResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_audio_transcriptions(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.audio_transcriptions( + start_time=0, + ) + assert_matches_type(UsageAudioTranscriptionsResponse, usage, path=["response"]) + + @parametrize + def test_method_audio_transcriptions_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.audio_transcriptions( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageAudioTranscriptionsResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_audio_transcriptions(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.audio_transcriptions( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageAudioTranscriptionsResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_audio_transcriptions(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.audio_transcriptions( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageAudioTranscriptionsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_code_interpreter_sessions(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.code_interpreter_sessions( + start_time=0, + ) + assert_matches_type(UsageCodeInterpreterSessionsResponse, usage, path=["response"]) + + @parametrize + def test_method_code_interpreter_sessions_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.code_interpreter_sessions( + start_time=0, + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + page="page", + project_ids=["string"], + ) + assert_matches_type(UsageCodeInterpreterSessionsResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_code_interpreter_sessions(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.code_interpreter_sessions( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageCodeInterpreterSessionsResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_code_interpreter_sessions(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.code_interpreter_sessions( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageCodeInterpreterSessionsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_completions(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.completions( + start_time=0, + ) + assert_matches_type(UsageCompletionsResponse, usage, path=["response"]) + + @parametrize + def test_method_completions_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.completions( + start_time=0, + api_key_ids=["string"], + batch=True, + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageCompletionsResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_completions(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.completions( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageCompletionsResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_completions(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.completions( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageCompletionsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_costs(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.costs( + start_time=0, + ) + assert_matches_type(UsageCostsResponse, usage, path=["response"]) + + @parametrize + def test_method_costs_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.costs( + start_time=0, + api_key_ids=["string"], + bucket_width="1d", + end_time=0, + group_by=["project_id"], + limit=0, + page="page", + project_ids=["string"], + ) + assert_matches_type(UsageCostsResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_costs(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.costs( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageCostsResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_costs(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.costs( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageCostsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_embeddings(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.embeddings( + start_time=0, + ) + assert_matches_type(UsageEmbeddingsResponse, usage, path=["response"]) + + @parametrize + def test_method_embeddings_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.embeddings( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageEmbeddingsResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_embeddings(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.embeddings( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageEmbeddingsResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_embeddings(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.embeddings( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageEmbeddingsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_file_search_calls(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.file_search_calls( + start_time=0, + ) + assert_matches_type(UsageFileSearchCallsResponse, usage, path=["response"]) + + @parametrize + def test_method_file_search_calls_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.file_search_calls( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + page="page", + project_ids=["string"], + user_ids=["string"], + vector_store_ids=["string"], + ) + assert_matches_type(UsageFileSearchCallsResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_file_search_calls(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.file_search_calls( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageFileSearchCallsResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_file_search_calls(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.file_search_calls( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageFileSearchCallsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_images(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.images( + start_time=0, + ) + assert_matches_type(UsageImagesResponse, usage, path=["response"]) + + @parametrize + def test_method_images_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.images( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + sizes=["256x256"], + sources=["image.generation"], + user_ids=["string"], + ) + assert_matches_type(UsageImagesResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_images(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.images( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageImagesResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_images(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.images( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageImagesResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_moderations(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.moderations( + start_time=0, + ) + assert_matches_type(UsageModerationsResponse, usage, path=["response"]) + + @parametrize + def test_method_moderations_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.moderations( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageModerationsResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_moderations(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.moderations( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageModerationsResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_moderations(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.moderations( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageModerationsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_vector_stores(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.vector_stores( + start_time=0, + ) + assert_matches_type(UsageVectorStoresResponse, usage, path=["response"]) + + @parametrize + def test_method_vector_stores_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.vector_stores( + start_time=0, + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + page="page", + project_ids=["string"], + ) + assert_matches_type(UsageVectorStoresResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_vector_stores(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.vector_stores( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageVectorStoresResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_vector_stores(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.vector_stores( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageVectorStoresResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_web_search_calls(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.web_search_calls( + start_time=0, + ) + assert_matches_type(UsageWebSearchCallsResponse, usage, path=["response"]) + + @parametrize + def test_method_web_search_calls_with_all_params(self, client: OpenAI) -> None: + usage = client.admin.organization.usage.web_search_calls( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + context_levels=["low"], + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageWebSearchCallsResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_web_search_calls(self, client: OpenAI) -> None: + response = client.admin.organization.usage.with_raw_response.web_search_calls( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageWebSearchCallsResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_web_search_calls(self, client: OpenAI) -> None: + with client.admin.organization.usage.with_streaming_response.web_search_calls( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageWebSearchCallsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncUsage: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_audio_speeches(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.audio_speeches( + start_time=0, + ) + assert_matches_type(UsageAudioSpeechesResponse, usage, path=["response"]) + + @parametrize + async def test_method_audio_speeches_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.audio_speeches( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageAudioSpeechesResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_audio_speeches(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.audio_speeches( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageAudioSpeechesResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_audio_speeches(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.audio_speeches( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageAudioSpeechesResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_audio_transcriptions(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.audio_transcriptions( + start_time=0, + ) + assert_matches_type(UsageAudioTranscriptionsResponse, usage, path=["response"]) + + @parametrize + async def test_method_audio_transcriptions_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.audio_transcriptions( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageAudioTranscriptionsResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_audio_transcriptions(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.audio_transcriptions( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageAudioTranscriptionsResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_audio_transcriptions(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.audio_transcriptions( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageAudioTranscriptionsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_code_interpreter_sessions(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.code_interpreter_sessions( + start_time=0, + ) + assert_matches_type(UsageCodeInterpreterSessionsResponse, usage, path=["response"]) + + @parametrize + async def test_method_code_interpreter_sessions_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.code_interpreter_sessions( + start_time=0, + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + page="page", + project_ids=["string"], + ) + assert_matches_type(UsageCodeInterpreterSessionsResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_code_interpreter_sessions(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.code_interpreter_sessions( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageCodeInterpreterSessionsResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_code_interpreter_sessions(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.code_interpreter_sessions( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageCodeInterpreterSessionsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_completions(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.completions( + start_time=0, + ) + assert_matches_type(UsageCompletionsResponse, usage, path=["response"]) + + @parametrize + async def test_method_completions_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.completions( + start_time=0, + api_key_ids=["string"], + batch=True, + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageCompletionsResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_completions(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.completions( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageCompletionsResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_completions(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.completions( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageCompletionsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_costs(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.costs( + start_time=0, + ) + assert_matches_type(UsageCostsResponse, usage, path=["response"]) + + @parametrize + async def test_method_costs_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.costs( + start_time=0, + api_key_ids=["string"], + bucket_width="1d", + end_time=0, + group_by=["project_id"], + limit=0, + page="page", + project_ids=["string"], + ) + assert_matches_type(UsageCostsResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_costs(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.costs( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageCostsResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_costs(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.costs( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageCostsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_embeddings(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.embeddings( + start_time=0, + ) + assert_matches_type(UsageEmbeddingsResponse, usage, path=["response"]) + + @parametrize + async def test_method_embeddings_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.embeddings( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageEmbeddingsResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_embeddings(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.embeddings( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageEmbeddingsResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_embeddings(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.embeddings( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageEmbeddingsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_file_search_calls(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.file_search_calls( + start_time=0, + ) + assert_matches_type(UsageFileSearchCallsResponse, usage, path=["response"]) + + @parametrize + async def test_method_file_search_calls_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.file_search_calls( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + page="page", + project_ids=["string"], + user_ids=["string"], + vector_store_ids=["string"], + ) + assert_matches_type(UsageFileSearchCallsResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_file_search_calls(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.file_search_calls( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageFileSearchCallsResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_file_search_calls(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.file_search_calls( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageFileSearchCallsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_images(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.images( + start_time=0, + ) + assert_matches_type(UsageImagesResponse, usage, path=["response"]) + + @parametrize + async def test_method_images_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.images( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + sizes=["256x256"], + sources=["image.generation"], + user_ids=["string"], + ) + assert_matches_type(UsageImagesResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_images(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.images( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageImagesResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_images(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.images( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageImagesResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_moderations(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.moderations( + start_time=0, + ) + assert_matches_type(UsageModerationsResponse, usage, path=["response"]) + + @parametrize + async def test_method_moderations_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.moderations( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageModerationsResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_moderations(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.moderations( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageModerationsResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_moderations(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.moderations( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageModerationsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_vector_stores(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.vector_stores( + start_time=0, + ) + assert_matches_type(UsageVectorStoresResponse, usage, path=["response"]) + + @parametrize + async def test_method_vector_stores_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.vector_stores( + start_time=0, + bucket_width="1m", + end_time=0, + group_by=["project_id"], + limit=0, + page="page", + project_ids=["string"], + ) + assert_matches_type(UsageVectorStoresResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_vector_stores(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.vector_stores( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageVectorStoresResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_vector_stores(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.vector_stores( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageVectorStoresResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_web_search_calls(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.web_search_calls( + start_time=0, + ) + assert_matches_type(UsageWebSearchCallsResponse, usage, path=["response"]) + + @parametrize + async def test_method_web_search_calls_with_all_params(self, async_client: AsyncOpenAI) -> None: + usage = await async_client.admin.organization.usage.web_search_calls( + start_time=0, + api_key_ids=["string"], + bucket_width="1m", + context_levels=["low"], + end_time=0, + group_by=["project_id"], + limit=0, + models=["string"], + page="page", + project_ids=["string"], + user_ids=["string"], + ) + assert_matches_type(UsageWebSearchCallsResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_web_search_calls(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.usage.with_raw_response.web_search_calls( + start_time=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageWebSearchCallsResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_web_search_calls(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.usage.with_streaming_response.web_search_calls( + start_time=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageWebSearchCallsResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/admin/organization/test_users.py b/tests/api_resources/admin/organization/test_users.py new file mode 100644 index 0000000000..308b199bc6 --- /dev/null +++ b/tests/api_resources/admin/organization/test_users.py @@ -0,0 +1,343 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.admin.organization import OrganizationUser, UserDeleteResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestUsers: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + user = client.admin.organization.users.retrieve( + "user_id", + ) + assert_matches_type(OrganizationUser, user, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.users.with_raw_response.retrieve( + "user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(OrganizationUser, user, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.users.with_streaming_response.retrieve( + "user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(OrganizationUser, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.users.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + user = client.admin.organization.users.update( + user_id="user_id", + ) + assert_matches_type(OrganizationUser, user, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + user = client.admin.organization.users.update( + user_id="user_id", + developer_persona="developer_persona", + role="role", + role_id="role_id", + technical_level="technical_level", + ) + assert_matches_type(OrganizationUser, user, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.admin.organization.users.with_raw_response.update( + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(OrganizationUser, user, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.admin.organization.users.with_streaming_response.update( + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(OrganizationUser, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.users.with_raw_response.update( + user_id="", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + user = client.admin.organization.users.list() + assert_matches_type(SyncConversationCursorPage[OrganizationUser], user, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + user = client.admin.organization.users.list( + after="after", + emails=["string"], + limit=0, + ) + assert_matches_type(SyncConversationCursorPage[OrganizationUser], user, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.users.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(SyncConversationCursorPage[OrganizationUser], user, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.users.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(SyncConversationCursorPage[OrganizationUser], user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + user = client.admin.organization.users.delete( + "user_id", + ) + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.users.with_raw_response.delete( + "user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.users.with_streaming_response.delete( + "user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.users.with_raw_response.delete( + "", + ) + + +class TestAsyncUsers: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.users.retrieve( + "user_id", + ) + assert_matches_type(OrganizationUser, user, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.users.with_raw_response.retrieve( + "user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(OrganizationUser, user, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.users.with_streaming_response.retrieve( + "user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(OrganizationUser, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.users.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.users.update( + user_id="user_id", + ) + assert_matches_type(OrganizationUser, user, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.users.update( + user_id="user_id", + developer_persona="developer_persona", + role="role", + role_id="role_id", + technical_level="technical_level", + ) + assert_matches_type(OrganizationUser, user, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.users.with_raw_response.update( + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(OrganizationUser, user, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.users.with_streaming_response.update( + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(OrganizationUser, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.users.with_raw_response.update( + user_id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.users.list() + assert_matches_type(AsyncConversationCursorPage[OrganizationUser], user, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.users.list( + after="after", + emails=["string"], + limit=0, + ) + assert_matches_type(AsyncConversationCursorPage[OrganizationUser], user, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.users.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(AsyncConversationCursorPage[OrganizationUser], user, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.users.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(AsyncConversationCursorPage[OrganizationUser], user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + user = await async_client.admin.organization.users.delete( + "user_id", + ) + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.users.with_raw_response.delete( + "user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.users.with_streaming_response.delete( + "user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(UserDeleteResponse, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.users.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/admin/organization/users/__init__.py b/tests/api_resources/admin/organization/users/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/admin/organization/users/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/admin/organization/users/test_roles.py b/tests/api_resources/admin/organization/users/test_roles.py new file mode 100644 index 0000000000..81247b2416 --- /dev/null +++ b/tests/api_resources/admin/organization/users/test_roles.py @@ -0,0 +1,402 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncNextCursorPage, AsyncNextCursorPage +from openai.types.admin.organization.users import ( + RoleListResponse, + RoleCreateResponse, + RoleDeleteResponse, + RoleRetrieveResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRoles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + role = client.admin.organization.users.roles.create( + user_id="user_id", + role_id="role_id", + ) + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.admin.organization.users.roles.with_raw_response.create( + user_id="user_id", + role_id="role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.admin.organization.users.roles.with_streaming_response.create( + user_id="user_id", + role_id="role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.users.roles.with_raw_response.create( + user_id="", + role_id="role_id", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + role = client.admin.organization.users.roles.retrieve( + role_id="role_id", + user_id="user_id", + ) + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.users.roles.with_raw_response.retrieve( + role_id="role_id", + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.users.roles.with_streaming_response.retrieve( + role_id="role_id", + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.users.roles.with_raw_response.retrieve( + role_id="role_id", + user_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.users.roles.with_raw_response.retrieve( + role_id="", + user_id="user_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + role = client.admin.organization.users.roles.list( + user_id="user_id", + ) + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + role = client.admin.organization.users.roles.list( + user_id="user_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.users.roles.with_raw_response.list( + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.users.roles.with_streaming_response.list( + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(SyncNextCursorPage[RoleListResponse], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.users.roles.with_raw_response.list( + user_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + role = client.admin.organization.users.roles.delete( + role_id="role_id", + user_id="user_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.users.roles.with_raw_response.delete( + role_id="role_id", + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.users.roles.with_streaming_response.delete( + role_id="role_id", + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + client.admin.organization.users.roles.with_raw_response.delete( + role_id="role_id", + user_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + client.admin.organization.users.roles.with_raw_response.delete( + role_id="", + user_id="user_id", + ) + + +class TestAsyncRoles: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.users.roles.create( + user_id="user_id", + role_id="role_id", + ) + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.users.roles.with_raw_response.create( + user_id="user_id", + role_id="role_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.users.roles.with_streaming_response.create( + user_id="user_id", + role_id="role_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleCreateResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.users.roles.with_raw_response.create( + user_id="", + role_id="role_id", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.users.roles.retrieve( + role_id="role_id", + user_id="user_id", + ) + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.users.roles.with_raw_response.retrieve( + role_id="role_id", + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.users.roles.with_streaming_response.retrieve( + role_id="role_id", + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleRetrieveResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.users.roles.with_raw_response.retrieve( + role_id="role_id", + user_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.users.roles.with_raw_response.retrieve( + role_id="", + user_id="user_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.users.roles.list( + user_id="user_id", + ) + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.users.roles.list( + user_id="user_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.users.roles.with_raw_response.list( + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.users.roles.with_streaming_response.list( + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(AsyncNextCursorPage[RoleListResponse], role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.users.roles.with_raw_response.list( + user_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + role = await async_client.admin.organization.users.roles.delete( + role_id="role_id", + user_id="user_id", + ) + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.users.roles.with_raw_response.delete( + role_id="role_id", + user_id="user_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + role = response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.users.roles.with_streaming_response.delete( + role_id="role_id", + user_id="user_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + role = await response.parse() + assert_matches_type(RoleDeleteResponse, role, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `user_id` but received ''"): + await async_client.admin.organization.users.roles.with_raw_response.delete( + role_id="role_id", + user_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `role_id` but received ''"): + await async_client.admin.organization.users.roles.with_raw_response.delete( + role_id="", + user_id="user_id", + ) diff --git a/tests/api_resources/audio/test_speech.py b/tests/api_resources/audio/test_speech.py index 781ebeceb9..93ede5193d 100644 --- a/tests/api_resources/audio/test_speech.py +++ b/tests/api_resources/audio/test_speech.py @@ -27,7 +27,7 @@ def test_method_create(self, client: OpenAI, respx_mock: MockRouter) -> None: respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) speech = client.audio.speech.create( input="string", - model="string", + model="tts-1", voice="alloy", ) assert isinstance(speech, _legacy_response.HttpxBinaryResponseContent) @@ -39,10 +39,12 @@ def test_method_create_with_all_params(self, client: OpenAI, respx_mock: MockRou respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) speech = client.audio.speech.create( input="string", - model="string", + model="tts-1", voice="alloy", + instructions="instructions", response_format="mp3", speed=0.25, + stream_format="sse", ) assert isinstance(speech, _legacy_response.HttpxBinaryResponseContent) assert speech.json() == {"foo": "bar"} @@ -54,7 +56,7 @@ def test_raw_response_create(self, client: OpenAI, respx_mock: MockRouter) -> No response = client.audio.speech.with_raw_response.create( input="string", - model="string", + model="tts-1", voice="alloy", ) @@ -69,7 +71,7 @@ def test_streaming_response_create(self, client: OpenAI, respx_mock: MockRouter) respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) with client.audio.speech.with_streaming_response.create( input="string", - model="string", + model="tts-1", voice="alloy", ) as response: assert not response.is_closed @@ -82,7 +84,9 @@ def test_streaming_response_create(self, client: OpenAI, respx_mock: MockRouter) class TestAsyncSpeech: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize @pytest.mark.respx(base_url=base_url) @@ -90,7 +94,7 @@ async def test_method_create(self, async_client: AsyncOpenAI, respx_mock: MockRo respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) speech = await async_client.audio.speech.create( input="string", - model="string", + model="tts-1", voice="alloy", ) assert isinstance(speech, _legacy_response.HttpxBinaryResponseContent) @@ -102,10 +106,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpenAI, re respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) speech = await async_client.audio.speech.create( input="string", - model="string", + model="tts-1", voice="alloy", + instructions="instructions", response_format="mp3", speed=0.25, + stream_format="sse", ) assert isinstance(speech, _legacy_response.HttpxBinaryResponseContent) assert speech.json() == {"foo": "bar"} @@ -117,7 +123,7 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI, respx_mock: response = await async_client.audio.speech.with_raw_response.create( input="string", - model="string", + model="tts-1", voice="alloy", ) @@ -132,7 +138,7 @@ async def test_streaming_response_create(self, async_client: AsyncOpenAI, respx_ respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) async with async_client.audio.speech.with_streaming_response.create( input="string", - model="string", + model="tts-1", voice="alloy", ) as response: assert not response.is_closed diff --git a/tests/api_resources/audio/test_transcriptions.py b/tests/api_resources/audio/test_transcriptions.py index ba8e9e4099..b4525937b4 100644 --- a/tests/api_resources/audio/test_transcriptions.py +++ b/tests/api_resources/audio/test_transcriptions.py @@ -9,7 +9,7 @@ from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type -from openai.types.audio import Transcription +from openai.types.audio import TranscriptionCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -18,99 +18,219 @@ class TestTranscriptions: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_create(self, client: OpenAI) -> None: + def test_method_create_overload_1(self, client: OpenAI) -> None: transcription = client.audio.transcriptions.create( - file=b"raw file contents", - model="whisper-1", + file=b"Example data", + model="gpt-4o-transcribe", ) - assert_matches_type(Transcription, transcription, path=["response"]) + assert_matches_type(TranscriptionCreateResponse, transcription, path=["response"]) @parametrize - def test_method_create_with_all_params(self, client: OpenAI) -> None: + def test_method_create_with_all_params_overload_1(self, client: OpenAI) -> None: transcription = client.audio.transcriptions.create( - file=b"raw file contents", - model="whisper-1", - language="string", - prompt="string", + file=b"Example data", + model="gpt-4o-transcribe", + chunking_strategy="auto", + include=["logprobs"], + known_speaker_names=["string"], + known_speaker_references=["string"], + language="language", + prompt="prompt", response_format="json", + stream=False, temperature=0, - timestamp_granularities=["word", "segment"], + timestamp_granularities=["word"], ) - assert_matches_type(Transcription, transcription, path=["response"]) + assert_matches_type(TranscriptionCreateResponse, transcription, path=["response"]) @parametrize - def test_raw_response_create(self, client: OpenAI) -> None: + def test_raw_response_create_overload_1(self, client: OpenAI) -> None: response = client.audio.transcriptions.with_raw_response.create( - file=b"raw file contents", - model="whisper-1", + file=b"Example data", + model="gpt-4o-transcribe", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" transcription = response.parse() - assert_matches_type(Transcription, transcription, path=["response"]) + assert_matches_type(TranscriptionCreateResponse, transcription, path=["response"]) @parametrize - def test_streaming_response_create(self, client: OpenAI) -> None: + def test_streaming_response_create_overload_1(self, client: OpenAI) -> None: with client.audio.transcriptions.with_streaming_response.create( - file=b"raw file contents", - model="whisper-1", + file=b"Example data", + model="gpt-4o-transcribe", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" transcription = response.parse() - assert_matches_type(Transcription, transcription, path=["response"]) + assert_matches_type(TranscriptionCreateResponse, transcription, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_create_overload_2(self, client: OpenAI) -> None: + transcription_stream = client.audio.transcriptions.create( + file=b"Example data", + model="gpt-4o-transcribe", + stream=True, + ) + transcription_stream.response.close() + + @parametrize + def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: + transcription_stream = client.audio.transcriptions.create( + file=b"Example data", + model="gpt-4o-transcribe", + stream=True, + chunking_strategy="auto", + include=["logprobs"], + known_speaker_names=["string"], + known_speaker_references=["string"], + language="language", + prompt="prompt", + response_format="json", + temperature=0, + timestamp_granularities=["word"], + ) + transcription_stream.response.close() + + @parametrize + def test_raw_response_create_overload_2(self, client: OpenAI) -> None: + response = client.audio.transcriptions.with_raw_response.create( + file=b"Example data", + model="gpt-4o-transcribe", + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @parametrize + def test_streaming_response_create_overload_2(self, client: OpenAI) -> None: + with client.audio.transcriptions.with_streaming_response.create( + file=b"Example data", + model="gpt-4o-transcribe", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() assert cast(Any, response.is_closed) is True class TestAsyncTranscriptions: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize - async def test_method_create(self, async_client: AsyncOpenAI) -> None: + async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None: transcription = await async_client.audio.transcriptions.create( - file=b"raw file contents", - model="whisper-1", + file=b"Example data", + model="gpt-4o-transcribe", ) - assert_matches_type(Transcription, transcription, path=["response"]) + assert_matches_type(TranscriptionCreateResponse, transcription, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + async def test_method_create_with_all_params_overload_1(self, async_client: AsyncOpenAI) -> None: transcription = await async_client.audio.transcriptions.create( - file=b"raw file contents", - model="whisper-1", - language="string", - prompt="string", + file=b"Example data", + model="gpt-4o-transcribe", + chunking_strategy="auto", + include=["logprobs"], + known_speaker_names=["string"], + known_speaker_references=["string"], + language="language", + prompt="prompt", response_format="json", + stream=False, temperature=0, - timestamp_granularities=["word", "segment"], + timestamp_granularities=["word"], ) - assert_matches_type(Transcription, transcription, path=["response"]) + assert_matches_type(TranscriptionCreateResponse, transcription, path=["response"]) @parametrize - async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + async def test_raw_response_create_overload_1(self, async_client: AsyncOpenAI) -> None: response = await async_client.audio.transcriptions.with_raw_response.create( - file=b"raw file contents", - model="whisper-1", + file=b"Example data", + model="gpt-4o-transcribe", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" transcription = response.parse() - assert_matches_type(Transcription, transcription, path=["response"]) + assert_matches_type(TranscriptionCreateResponse, transcription, path=["response"]) @parametrize - async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async def test_streaming_response_create_overload_1(self, async_client: AsyncOpenAI) -> None: async with async_client.audio.transcriptions.with_streaming_response.create( - file=b"raw file contents", - model="whisper-1", + file=b"Example data", + model="gpt-4o-transcribe", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" transcription = await response.parse() - assert_matches_type(Transcription, transcription, path=["response"]) + assert_matches_type(TranscriptionCreateResponse, transcription, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_create_overload_2(self, async_client: AsyncOpenAI) -> None: + transcription_stream = await async_client.audio.transcriptions.create( + file=b"Example data", + model="gpt-4o-transcribe", + stream=True, + ) + await transcription_stream.response.aclose() + + @parametrize + async def test_method_create_with_all_params_overload_2(self, async_client: AsyncOpenAI) -> None: + transcription_stream = await async_client.audio.transcriptions.create( + file=b"Example data", + model="gpt-4o-transcribe", + stream=True, + chunking_strategy="auto", + include=["logprobs"], + known_speaker_names=["string"], + known_speaker_references=["string"], + language="language", + prompt="prompt", + response_format="json", + temperature=0, + timestamp_granularities=["word"], + ) + await transcription_stream.response.aclose() + + @parametrize + async def test_raw_response_create_overload_2(self, async_client: AsyncOpenAI) -> None: + response = await async_client.audio.transcriptions.with_raw_response.create( + file=b"Example data", + model="gpt-4o-transcribe", + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + await stream.close() + + @parametrize + async def test_streaming_response_create_overload_2(self, async_client: AsyncOpenAI) -> None: + async with async_client.audio.transcriptions.with_streaming_response.create( + file=b"Example data", + model="gpt-4o-transcribe", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/audio/test_translations.py b/tests/api_resources/audio/test_translations.py index f5c6c68f0b..d6848d1278 100644 --- a/tests/api_resources/audio/test_translations.py +++ b/tests/api_resources/audio/test_translations.py @@ -9,7 +9,7 @@ from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type -from openai.types.audio import Translation +from openai.types.audio import TranslationCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -20,93 +20,95 @@ class TestTranslations: @parametrize def test_method_create(self, client: OpenAI) -> None: translation = client.audio.translations.create( - file=b"raw file contents", + file=b"Example data", model="whisper-1", ) - assert_matches_type(Translation, translation, path=["response"]) + assert_matches_type(TranslationCreateResponse, translation, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: OpenAI) -> None: translation = client.audio.translations.create( - file=b"raw file contents", + file=b"Example data", model="whisper-1", - prompt="string", - response_format="string", + prompt="prompt", + response_format="json", temperature=0, ) - assert_matches_type(Translation, translation, path=["response"]) + assert_matches_type(TranslationCreateResponse, translation, path=["response"]) @parametrize def test_raw_response_create(self, client: OpenAI) -> None: response = client.audio.translations.with_raw_response.create( - file=b"raw file contents", + file=b"Example data", model="whisper-1", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" translation = response.parse() - assert_matches_type(Translation, translation, path=["response"]) + assert_matches_type(TranslationCreateResponse, translation, path=["response"]) @parametrize def test_streaming_response_create(self, client: OpenAI) -> None: with client.audio.translations.with_streaming_response.create( - file=b"raw file contents", + file=b"Example data", model="whisper-1", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" translation = response.parse() - assert_matches_type(Translation, translation, path=["response"]) + assert_matches_type(TranslationCreateResponse, translation, path=["response"]) assert cast(Any, response.is_closed) is True class TestAsyncTranslations: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: translation = await async_client.audio.translations.create( - file=b"raw file contents", + file=b"Example data", model="whisper-1", ) - assert_matches_type(Translation, translation, path=["response"]) + assert_matches_type(TranslationCreateResponse, translation, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: translation = await async_client.audio.translations.create( - file=b"raw file contents", + file=b"Example data", model="whisper-1", - prompt="string", - response_format="string", + prompt="prompt", + response_format="json", temperature=0, ) - assert_matches_type(Translation, translation, path=["response"]) + assert_matches_type(TranslationCreateResponse, translation, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: response = await async_client.audio.translations.with_raw_response.create( - file=b"raw file contents", + file=b"Example data", model="whisper-1", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" translation = response.parse() - assert_matches_type(Translation, translation, path=["response"]) + assert_matches_type(TranslationCreateResponse, translation, path=["response"]) @parametrize async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: async with async_client.audio.translations.with_streaming_response.create( - file=b"raw file contents", + file=b"Example data", model="whisper-1", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" translation = await response.parse() - assert_matches_type(Translation, translation, path=["response"]) + assert_matches_type(TranslationCreateResponse, translation, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/beta/chatkit/__init__.py b/tests/api_resources/beta/chatkit/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/beta/chatkit/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/beta/chatkit/test_sessions.py b/tests/api_resources/beta/chatkit/test_sessions.py new file mode 100644 index 0000000000..c94e4c92ae --- /dev/null +++ b/tests/api_resources/beta/chatkit/test_sessions.py @@ -0,0 +1,230 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types.beta.chatkit import ( + ChatSession, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSessions: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + session = client.beta.chatkit.sessions.create( + user="x", + workflow={"id": "id"}, + ) + assert_matches_type(ChatSession, session, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + session = client.beta.chatkit.sessions.create( + user="x", + workflow={ + "id": "id", + "state_variables": {"foo": "string"}, + "tracing": {"enabled": True}, + "version": "version", + }, + chatkit_configuration={ + "automatic_thread_titling": {"enabled": True}, + "file_upload": { + "enabled": True, + "max_file_size": 1, + "max_files": 1, + }, + "history": { + "enabled": True, + "recent_threads": 1, + }, + }, + expires_after={ + "anchor": "created_at", + "seconds": 1, + }, + rate_limits={"max_requests_per_1_minute": 1}, + ) + assert_matches_type(ChatSession, session, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.beta.chatkit.sessions.with_raw_response.create( + user="x", + workflow={"id": "id"}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + session = response.parse() + assert_matches_type(ChatSession, session, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.beta.chatkit.sessions.with_streaming_response.create( + user="x", + workflow={"id": "id"}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + session = response.parse() + assert_matches_type(ChatSession, session, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_cancel(self, client: OpenAI) -> None: + session = client.beta.chatkit.sessions.cancel( + "cksess_123", + ) + assert_matches_type(ChatSession, session, path=["response"]) + + @parametrize + def test_raw_response_cancel(self, client: OpenAI) -> None: + response = client.beta.chatkit.sessions.with_raw_response.cancel( + "cksess_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + session = response.parse() + assert_matches_type(ChatSession, session, path=["response"]) + + @parametrize + def test_streaming_response_cancel(self, client: OpenAI) -> None: + with client.beta.chatkit.sessions.with_streaming_response.cancel( + "cksess_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + session = response.parse() + assert_matches_type(ChatSession, session, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_cancel(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"): + client.beta.chatkit.sessions.with_raw_response.cancel( + "", + ) + + +class TestAsyncSessions: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + session = await async_client.beta.chatkit.sessions.create( + user="x", + workflow={"id": "id"}, + ) + assert_matches_type(ChatSession, session, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + session = await async_client.beta.chatkit.sessions.create( + user="x", + workflow={ + "id": "id", + "state_variables": {"foo": "string"}, + "tracing": {"enabled": True}, + "version": "version", + }, + chatkit_configuration={ + "automatic_thread_titling": {"enabled": True}, + "file_upload": { + "enabled": True, + "max_file_size": 1, + "max_files": 1, + }, + "history": { + "enabled": True, + "recent_threads": 1, + }, + }, + expires_after={ + "anchor": "created_at", + "seconds": 1, + }, + rate_limits={"max_requests_per_1_minute": 1}, + ) + assert_matches_type(ChatSession, session, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.beta.chatkit.sessions.with_raw_response.create( + user="x", + workflow={"id": "id"}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + session = response.parse() + assert_matches_type(ChatSession, session, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.beta.chatkit.sessions.with_streaming_response.create( + user="x", + workflow={"id": "id"}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + session = await response.parse() + assert_matches_type(ChatSession, session, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_cancel(self, async_client: AsyncOpenAI) -> None: + session = await async_client.beta.chatkit.sessions.cancel( + "cksess_123", + ) + assert_matches_type(ChatSession, session, path=["response"]) + + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncOpenAI) -> None: + response = await async_client.beta.chatkit.sessions.with_raw_response.cancel( + "cksess_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + session = response.parse() + assert_matches_type(ChatSession, session, path=["response"]) + + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncOpenAI) -> None: + async with async_client.beta.chatkit.sessions.with_streaming_response.cancel( + "cksess_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + session = await response.parse() + assert_matches_type(ChatSession, session, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_cancel(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"): + await async_client.beta.chatkit.sessions.with_raw_response.cancel( + "", + ) diff --git a/tests/api_resources/beta/chatkit/test_threads.py b/tests/api_resources/beta/chatkit/test_threads.py new file mode 100644 index 0000000000..6395b72b2f --- /dev/null +++ b/tests/api_resources/beta/chatkit/test_threads.py @@ -0,0 +1,348 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.beta.chatkit import ChatKitThread, ThreadDeleteResponse +from openai.types.beta.chatkit.chatkit_thread_item_list import Data + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestThreads: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + thread = client.beta.chatkit.threads.retrieve( + "cthr_123", + ) + assert_matches_type(ChatKitThread, thread, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.beta.chatkit.threads.with_raw_response.retrieve( + "cthr_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + thread = response.parse() + assert_matches_type(ChatKitThread, thread, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.beta.chatkit.threads.with_streaming_response.retrieve( + "cthr_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = response.parse() + assert_matches_type(ChatKitThread, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.chatkit.threads.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + thread = client.beta.chatkit.threads.list() + assert_matches_type(SyncConversationCursorPage[ChatKitThread], thread, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + thread = client.beta.chatkit.threads.list( + after="after", + before="before", + limit=0, + order="asc", + user="x", + ) + assert_matches_type(SyncConversationCursorPage[ChatKitThread], thread, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.beta.chatkit.threads.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + thread = response.parse() + assert_matches_type(SyncConversationCursorPage[ChatKitThread], thread, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.beta.chatkit.threads.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = response.parse() + assert_matches_type(SyncConversationCursorPage[ChatKitThread], thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + thread = client.beta.chatkit.threads.delete( + "cthr_123", + ) + assert_matches_type(ThreadDeleteResponse, thread, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.beta.chatkit.threads.with_raw_response.delete( + "cthr_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + thread = response.parse() + assert_matches_type(ThreadDeleteResponse, thread, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.beta.chatkit.threads.with_streaming_response.delete( + "cthr_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = response.parse() + assert_matches_type(ThreadDeleteResponse, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.chatkit.threads.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_list_items(self, client: OpenAI) -> None: + thread = client.beta.chatkit.threads.list_items( + thread_id="cthr_123", + ) + assert_matches_type(SyncConversationCursorPage[Data], thread, path=["response"]) + + @parametrize + def test_method_list_items_with_all_params(self, client: OpenAI) -> None: + thread = client.beta.chatkit.threads.list_items( + thread_id="cthr_123", + after="after", + before="before", + limit=0, + order="asc", + ) + assert_matches_type(SyncConversationCursorPage[Data], thread, path=["response"]) + + @parametrize + def test_raw_response_list_items(self, client: OpenAI) -> None: + response = client.beta.chatkit.threads.with_raw_response.list_items( + thread_id="cthr_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + thread = response.parse() + assert_matches_type(SyncConversationCursorPage[Data], thread, path=["response"]) + + @parametrize + def test_streaming_response_list_items(self, client: OpenAI) -> None: + with client.beta.chatkit.threads.with_streaming_response.list_items( + thread_id="cthr_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = response.parse() + assert_matches_type(SyncConversationCursorPage[Data], thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list_items(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.chatkit.threads.with_raw_response.list_items( + thread_id="", + ) + + +class TestAsyncThreads: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + thread = await async_client.beta.chatkit.threads.retrieve( + "cthr_123", + ) + assert_matches_type(ChatKitThread, thread, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.beta.chatkit.threads.with_raw_response.retrieve( + "cthr_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + thread = response.parse() + assert_matches_type(ChatKitThread, thread, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.beta.chatkit.threads.with_streaming_response.retrieve( + "cthr_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = await response.parse() + assert_matches_type(ChatKitThread, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.chatkit.threads.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + thread = await async_client.beta.chatkit.threads.list() + assert_matches_type(AsyncConversationCursorPage[ChatKitThread], thread, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + thread = await async_client.beta.chatkit.threads.list( + after="after", + before="before", + limit=0, + order="asc", + user="x", + ) + assert_matches_type(AsyncConversationCursorPage[ChatKitThread], thread, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.beta.chatkit.threads.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + thread = response.parse() + assert_matches_type(AsyncConversationCursorPage[ChatKitThread], thread, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.beta.chatkit.threads.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = await response.parse() + assert_matches_type(AsyncConversationCursorPage[ChatKitThread], thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + thread = await async_client.beta.chatkit.threads.delete( + "cthr_123", + ) + assert_matches_type(ThreadDeleteResponse, thread, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.beta.chatkit.threads.with_raw_response.delete( + "cthr_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + thread = response.parse() + assert_matches_type(ThreadDeleteResponse, thread, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.beta.chatkit.threads.with_streaming_response.delete( + "cthr_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = await response.parse() + assert_matches_type(ThreadDeleteResponse, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.chatkit.threads.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_list_items(self, async_client: AsyncOpenAI) -> None: + thread = await async_client.beta.chatkit.threads.list_items( + thread_id="cthr_123", + ) + assert_matches_type(AsyncConversationCursorPage[Data], thread, path=["response"]) + + @parametrize + async def test_method_list_items_with_all_params(self, async_client: AsyncOpenAI) -> None: + thread = await async_client.beta.chatkit.threads.list_items( + thread_id="cthr_123", + after="after", + before="before", + limit=0, + order="asc", + ) + assert_matches_type(AsyncConversationCursorPage[Data], thread, path=["response"]) + + @parametrize + async def test_raw_response_list_items(self, async_client: AsyncOpenAI) -> None: + response = await async_client.beta.chatkit.threads.with_raw_response.list_items( + thread_id="cthr_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + thread = response.parse() + assert_matches_type(AsyncConversationCursorPage[Data], thread, path=["response"]) + + @parametrize + async def test_streaming_response_list_items(self, async_client: AsyncOpenAI) -> None: + async with async_client.beta.chatkit.threads.with_streaming_response.list_items( + thread_id="cthr_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = await response.parse() + assert_matches_type(AsyncConversationCursorPage[Data], thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list_items(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.chatkit.threads.with_raw_response.list_items( + thread_id="", + ) diff --git a/tests/api_resources/beta/test_assistants.py b/tests/api_resources/beta/test_assistants.py index 642935cdaf..91ff13dded 100644 --- a/tests/api_resources/beta/test_assistants.py +++ b/tests/api_resources/beta/test_assistants.py @@ -15,6 +15,8 @@ AssistantDeleted, ) +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -23,44 +25,50 @@ class TestAssistants: @parametrize def test_method_create(self, client: OpenAI) -> None: - assistant = client.beta.assistants.create( - model="gpt-4o", - ) + with pytest.warns(DeprecationWarning): + assistant = client.beta.assistants.create( + model="gpt-4o", + ) + assert_matches_type(Assistant, assistant, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: OpenAI) -> None: - assistant = client.beta.assistants.create( - model="gpt-4o", - description="description", - instructions="instructions", - metadata={}, - name="name", - response_format="auto", - temperature=1, - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": { - "vector_store_ids": ["string"], - "vector_stores": [ - { - "chunking_strategy": {"type": "auto"}, - "file_ids": ["string", "string", "string"], - "metadata": {}, - } - ], + with pytest.warns(DeprecationWarning): + assistant = client.beta.assistants.create( + model="gpt-4o", + description="description", + instructions="instructions", + metadata={"foo": "string"}, + name="name", + reasoning_effort="none", + response_format="auto", + temperature=1, + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": { + "vector_store_ids": ["string"], + "vector_stores": [ + { + "chunking_strategy": {"type": "auto"}, + "file_ids": ["string"], + "metadata": {"foo": "string"}, + } + ], + }, }, - }, - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - ) + tools=[{"type": "code_interpreter"}], + top_p=1, + ) + assert_matches_type(Assistant, assistant, path=["response"]) @parametrize def test_raw_response_create(self, client: OpenAI) -> None: - response = client.beta.assistants.with_raw_response.create( - model="gpt-4o", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.assistants.with_raw_response.create( + model="gpt-4o", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -69,29 +77,33 @@ def test_raw_response_create(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create(self, client: OpenAI) -> None: - with client.beta.assistants.with_streaming_response.create( - model="gpt-4o", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.assistants.with_streaming_response.create( + model="gpt-4o", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assistant = response.parse() - assert_matches_type(Assistant, assistant, path=["response"]) + assistant = response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_method_retrieve(self, client: OpenAI) -> None: - assistant = client.beta.assistants.retrieve( - "assistant_id", - ) + with pytest.warns(DeprecationWarning): + assistant = client.beta.assistants.retrieve( + "assistant_id", + ) + assert_matches_type(Assistant, assistant, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: OpenAI) -> None: - response = client.beta.assistants.with_raw_response.retrieve( - "assistant_id", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.assistants.with_raw_response.retrieve( + "assistant_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -100,56 +112,64 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: @parametrize def test_streaming_response_retrieve(self, client: OpenAI) -> None: - with client.beta.assistants.with_streaming_response.retrieve( - "assistant_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.assistants.with_streaming_response.retrieve( + "assistant_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assistant = response.parse() - assert_matches_type(Assistant, assistant, path=["response"]) + assistant = response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_retrieve(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): - client.beta.assistants.with_raw_response.retrieve( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): + client.beta.assistants.with_raw_response.retrieve( + "", + ) @parametrize def test_method_update(self, client: OpenAI) -> None: - assistant = client.beta.assistants.update( - assistant_id="assistant_id", - ) + with pytest.warns(DeprecationWarning): + assistant = client.beta.assistants.update( + assistant_id="assistant_id", + ) + assert_matches_type(Assistant, assistant, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: OpenAI) -> None: - assistant = client.beta.assistants.update( - assistant_id="assistant_id", - description="description", - instructions="instructions", - metadata={}, - model="model", - name="name", - response_format="auto", - temperature=1, - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": {"vector_store_ids": ["string"]}, - }, - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - ) + with pytest.warns(DeprecationWarning): + assistant = client.beta.assistants.update( + assistant_id="assistant_id", + description="description", + instructions="instructions", + metadata={"foo": "string"}, + model="gpt-5", + name="name", + reasoning_effort="none", + response_format="auto", + temperature=1, + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": {"vector_store_ids": ["string"]}, + }, + tools=[{"type": "code_interpreter"}], + top_p=1, + ) + assert_matches_type(Assistant, assistant, path=["response"]) @parametrize def test_raw_response_update(self, client: OpenAI) -> None: - response = client.beta.assistants.with_raw_response.update( - assistant_id="assistant_id", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.assistants.with_raw_response.update( + assistant_id="assistant_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -158,42 +178,49 @@ def test_raw_response_update(self, client: OpenAI) -> None: @parametrize def test_streaming_response_update(self, client: OpenAI) -> None: - with client.beta.assistants.with_streaming_response.update( - assistant_id="assistant_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.assistants.with_streaming_response.update( + assistant_id="assistant_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assistant = response.parse() - assert_matches_type(Assistant, assistant, path=["response"]) + assistant = response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_update(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): - client.beta.assistants.with_raw_response.update( - assistant_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): + client.beta.assistants.with_raw_response.update( + assistant_id="", + ) @parametrize def test_method_list(self, client: OpenAI) -> None: - assistant = client.beta.assistants.list() + with pytest.warns(DeprecationWarning): + assistant = client.beta.assistants.list() + assert_matches_type(SyncCursorPage[Assistant], assistant, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: OpenAI) -> None: - assistant = client.beta.assistants.list( - after="after", - before="before", - limit=0, - order="asc", - ) + with pytest.warns(DeprecationWarning): + assistant = client.beta.assistants.list( + after="after", + before="before", + limit=0, + order="asc", + ) + assert_matches_type(SyncCursorPage[Assistant], assistant, path=["response"]) @parametrize def test_raw_response_list(self, client: OpenAI) -> None: - response = client.beta.assistants.with_raw_response.list() + with pytest.warns(DeprecationWarning): + response = client.beta.assistants.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -202,27 +229,31 @@ def test_raw_response_list(self, client: OpenAI) -> None: @parametrize def test_streaming_response_list(self, client: OpenAI) -> None: - with client.beta.assistants.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.assistants.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assistant = response.parse() - assert_matches_type(SyncCursorPage[Assistant], assistant, path=["response"]) + assistant = response.parse() + assert_matches_type(SyncCursorPage[Assistant], assistant, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_method_delete(self, client: OpenAI) -> None: - assistant = client.beta.assistants.delete( - "assistant_id", - ) + with pytest.warns(DeprecationWarning): + assistant = client.beta.assistants.delete( + "assistant_id", + ) + assert_matches_type(AssistantDeleted, assistant, path=["response"]) @parametrize def test_raw_response_delete(self, client: OpenAI) -> None: - response = client.beta.assistants.with_raw_response.delete( - "assistant_id", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.assistants.with_raw_response.delete( + "assistant_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -231,68 +262,78 @@ def test_raw_response_delete(self, client: OpenAI) -> None: @parametrize def test_streaming_response_delete(self, client: OpenAI) -> None: - with client.beta.assistants.with_streaming_response.delete( - "assistant_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.assistants.with_streaming_response.delete( + "assistant_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assistant = response.parse() - assert_matches_type(AssistantDeleted, assistant, path=["response"]) + assistant = response.parse() + assert_matches_type(AssistantDeleted, assistant, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_delete(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): - client.beta.assistants.with_raw_response.delete( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): + client.beta.assistants.with_raw_response.delete( + "", + ) class TestAsyncAssistants: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: - assistant = await async_client.beta.assistants.create( - model="gpt-4o", - ) + with pytest.warns(DeprecationWarning): + assistant = await async_client.beta.assistants.create( + model="gpt-4o", + ) + assert_matches_type(Assistant, assistant, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: - assistant = await async_client.beta.assistants.create( - model="gpt-4o", - description="description", - instructions="instructions", - metadata={}, - name="name", - response_format="auto", - temperature=1, - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": { - "vector_store_ids": ["string"], - "vector_stores": [ - { - "chunking_strategy": {"type": "auto"}, - "file_ids": ["string", "string", "string"], - "metadata": {}, - } - ], + with pytest.warns(DeprecationWarning): + assistant = await async_client.beta.assistants.create( + model="gpt-4o", + description="description", + instructions="instructions", + metadata={"foo": "string"}, + name="name", + reasoning_effort="none", + response_format="auto", + temperature=1, + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": { + "vector_store_ids": ["string"], + "vector_stores": [ + { + "chunking_strategy": {"type": "auto"}, + "file_ids": ["string"], + "metadata": {"foo": "string"}, + } + ], + }, }, - }, - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - ) + tools=[{"type": "code_interpreter"}], + top_p=1, + ) + assert_matches_type(Assistant, assistant, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.assistants.with_raw_response.create( - model="gpt-4o", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.assistants.with_raw_response.create( + model="gpt-4o", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -301,29 +342,33 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.assistants.with_streaming_response.create( - model="gpt-4o", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.assistants.with_streaming_response.create( + model="gpt-4o", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assistant = await response.parse() - assert_matches_type(Assistant, assistant, path=["response"]) + assistant = await response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: - assistant = await async_client.beta.assistants.retrieve( - "assistant_id", - ) + with pytest.warns(DeprecationWarning): + assistant = await async_client.beta.assistants.retrieve( + "assistant_id", + ) + assert_matches_type(Assistant, assistant, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.assistants.with_raw_response.retrieve( - "assistant_id", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.assistants.with_raw_response.retrieve( + "assistant_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -332,56 +377,64 @@ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.assistants.with_streaming_response.retrieve( - "assistant_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.assistants.with_streaming_response.retrieve( + "assistant_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assistant = await response.parse() - assert_matches_type(Assistant, assistant, path=["response"]) + assistant = await response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): - await async_client.beta.assistants.with_raw_response.retrieve( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): + await async_client.beta.assistants.with_raw_response.retrieve( + "", + ) @parametrize async def test_method_update(self, async_client: AsyncOpenAI) -> None: - assistant = await async_client.beta.assistants.update( - assistant_id="assistant_id", - ) + with pytest.warns(DeprecationWarning): + assistant = await async_client.beta.assistants.update( + assistant_id="assistant_id", + ) + assert_matches_type(Assistant, assistant, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: - assistant = await async_client.beta.assistants.update( - assistant_id="assistant_id", - description="description", - instructions="instructions", - metadata={}, - model="model", - name="name", - response_format="auto", - temperature=1, - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": {"vector_store_ids": ["string"]}, - }, - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - ) + with pytest.warns(DeprecationWarning): + assistant = await async_client.beta.assistants.update( + assistant_id="assistant_id", + description="description", + instructions="instructions", + metadata={"foo": "string"}, + model="gpt-5", + name="name", + reasoning_effort="none", + response_format="auto", + temperature=1, + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": {"vector_store_ids": ["string"]}, + }, + tools=[{"type": "code_interpreter"}], + top_p=1, + ) + assert_matches_type(Assistant, assistant, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.assistants.with_raw_response.update( - assistant_id="assistant_id", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.assistants.with_raw_response.update( + assistant_id="assistant_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -390,42 +443,49 @@ async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.assistants.with_streaming_response.update( - assistant_id="assistant_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.assistants.with_streaming_response.update( + assistant_id="assistant_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assistant = await response.parse() - assert_matches_type(Assistant, assistant, path=["response"]) + assistant = await response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): - await async_client.beta.assistants.with_raw_response.update( - assistant_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): + await async_client.beta.assistants.with_raw_response.update( + assistant_id="", + ) @parametrize async def test_method_list(self, async_client: AsyncOpenAI) -> None: - assistant = await async_client.beta.assistants.list() + with pytest.warns(DeprecationWarning): + assistant = await async_client.beta.assistants.list() + assert_matches_type(AsyncCursorPage[Assistant], assistant, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: - assistant = await async_client.beta.assistants.list( - after="after", - before="before", - limit=0, - order="asc", - ) + with pytest.warns(DeprecationWarning): + assistant = await async_client.beta.assistants.list( + after="after", + before="before", + limit=0, + order="asc", + ) + assert_matches_type(AsyncCursorPage[Assistant], assistant, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.assistants.with_raw_response.list() + with pytest.warns(DeprecationWarning): + response = await async_client.beta.assistants.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -434,27 +494,31 @@ async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.assistants.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.assistants.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assistant = await response.parse() - assert_matches_type(AsyncCursorPage[Assistant], assistant, path=["response"]) + assistant = await response.parse() + assert_matches_type(AsyncCursorPage[Assistant], assistant, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_method_delete(self, async_client: AsyncOpenAI) -> None: - assistant = await async_client.beta.assistants.delete( - "assistant_id", - ) + with pytest.warns(DeprecationWarning): + assistant = await async_client.beta.assistants.delete( + "assistant_id", + ) + assert_matches_type(AssistantDeleted, assistant, path=["response"]) @parametrize async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.assistants.with_raw_response.delete( - "assistant_id", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.assistants.with_raw_response.delete( + "assistant_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -463,20 +527,22 @@ async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.assistants.with_streaming_response.delete( - "assistant_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.assistants.with_streaming_response.delete( + "assistant_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assistant = await response.parse() - assert_matches_type(AssistantDeleted, assistant, path=["response"]) + assistant = await response.parse() + assert_matches_type(AssistantDeleted, assistant, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): - await async_client.beta.assistants.with_raw_response.delete( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): + await async_client.beta.assistants.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/beta/test_realtime.py b/tests/api_resources/beta/test_realtime.py new file mode 100644 index 0000000000..8f752a0fd3 --- /dev/null +++ b/tests/api_resources/beta/test_realtime.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os + +import pytest + +# pyright: reportDeprecated=false + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRealtime: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + +class TestAsyncRealtime: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) diff --git a/tests/api_resources/beta/test_threads.py b/tests/api_resources/beta/test_threads.py index 95bebd84f5..7163511951 100644 --- a/tests/api_resources/beta/test_threads.py +++ b/tests/api_resources/beta/test_threads.py @@ -15,6 +15,8 @@ ) from openai.types.beta.threads import Run +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -23,127 +25,50 @@ class TestThreads: @parametrize def test_method_create(self, client: OpenAI) -> None: - thread = client.beta.threads.create() + with pytest.warns(DeprecationWarning): + thread = client.beta.threads.create() + assert_matches_type(Thread, thread, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: OpenAI) -> None: - thread = client.beta.threads.create( - messages=[ - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - ], - metadata={}, - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": { - "vector_store_ids": ["string"], - "vector_stores": [ - { - "chunking_strategy": {"type": "auto"}, - "file_ids": ["string", "string", "string"], - "metadata": {}, - } - ], + with pytest.warns(DeprecationWarning): + thread = client.beta.threads.create( + messages=[ + { + "content": "string", + "role": "user", + "attachments": [ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + "metadata": {"foo": "string"}, + } + ], + metadata={"foo": "string"}, + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": { + "vector_store_ids": ["string"], + "vector_stores": [ + { + "chunking_strategy": {"type": "auto"}, + "file_ids": ["string"], + "metadata": {"foo": "string"}, + } + ], + }, }, - }, - ) + ) + assert_matches_type(Thread, thread, path=["response"]) @parametrize def test_raw_response_create(self, client: OpenAI) -> None: - response = client.beta.threads.with_raw_response.create() + with pytest.warns(DeprecationWarning): + response = client.beta.threads.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -152,27 +77,31 @@ def test_raw_response_create(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create(self, client: OpenAI) -> None: - with client.beta.threads.with_streaming_response.create() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - thread = response.parse() - assert_matches_type(Thread, thread, path=["response"]) + thread = response.parse() + assert_matches_type(Thread, thread, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_method_retrieve(self, client: OpenAI) -> None: - thread = client.beta.threads.retrieve( - "string", - ) + with pytest.warns(DeprecationWarning): + thread = client.beta.threads.retrieve( + "thread_id", + ) + assert_matches_type(Thread, thread, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: OpenAI) -> None: - response = client.beta.threads.with_raw_response.retrieve( - "string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.with_raw_response.retrieve( + "thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -181,48 +110,55 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: @parametrize def test_streaming_response_retrieve(self, client: OpenAI) -> None: - with client.beta.threads.with_streaming_response.retrieve( - "string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.with_streaming_response.retrieve( + "thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - thread = response.parse() - assert_matches_type(Thread, thread, path=["response"]) + thread = response.parse() + assert_matches_type(Thread, thread, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_retrieve(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.with_raw_response.retrieve( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.with_raw_response.retrieve( + "", + ) @parametrize def test_method_update(self, client: OpenAI) -> None: - thread = client.beta.threads.update( - "string", - ) + with pytest.warns(DeprecationWarning): + thread = client.beta.threads.update( + thread_id="thread_id", + ) + assert_matches_type(Thread, thread, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: OpenAI) -> None: - thread = client.beta.threads.update( - "string", - metadata={}, - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": {"vector_store_ids": ["string"]}, - }, - ) + with pytest.warns(DeprecationWarning): + thread = client.beta.threads.update( + thread_id="thread_id", + metadata={"foo": "string"}, + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": {"vector_store_ids": ["string"]}, + }, + ) + assert_matches_type(Thread, thread, path=["response"]) @parametrize def test_raw_response_update(self, client: OpenAI) -> None: - response = client.beta.threads.with_raw_response.update( - "string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.with_raw_response.update( + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -231,36 +167,41 @@ def test_raw_response_update(self, client: OpenAI) -> None: @parametrize def test_streaming_response_update(self, client: OpenAI) -> None: - with client.beta.threads.with_streaming_response.update( - "string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.with_streaming_response.update( + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - thread = response.parse() - assert_matches_type(Thread, thread, path=["response"]) + thread = response.parse() + assert_matches_type(Thread, thread, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_update(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.with_raw_response.update( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.with_raw_response.update( + thread_id="", + ) @parametrize def test_method_delete(self, client: OpenAI) -> None: - thread = client.beta.threads.delete( - "string", - ) + with pytest.warns(DeprecationWarning): + thread = client.beta.threads.delete( + "thread_id", + ) + assert_matches_type(ThreadDeleted, thread, path=["response"]) @parametrize def test_raw_response_delete(self, client: OpenAI) -> None: - response = client.beta.threads.with_raw_response.delete( - "string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.with_raw_response.delete( + "thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -269,174 +210,99 @@ def test_raw_response_delete(self, client: OpenAI) -> None: @parametrize def test_streaming_response_delete(self, client: OpenAI) -> None: - with client.beta.threads.with_streaming_response.delete( - "string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.with_streaming_response.delete( + "thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - thread = response.parse() - assert_matches_type(ThreadDeleted, thread, path=["response"]) + thread = response.parse() + assert_matches_type(ThreadDeleted, thread, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_delete(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.with_raw_response.delete( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.with_raw_response.delete( + "", + ) @parametrize def test_method_create_and_run_overload_1(self, client: OpenAI) -> None: - thread = client.beta.threads.create_and_run( - assistant_id="string", - ) + with pytest.warns(DeprecationWarning): + thread = client.beta.threads.create_and_run( + assistant_id="assistant_id", + ) + assert_matches_type(Run, thread, path=["response"]) @parametrize def test_method_create_and_run_with_all_params_overload_1(self, client: OpenAI) -> None: - thread = client.beta.threads.create_and_run( - assistant_id="string", - instructions="string", - max_completion_tokens=256, - max_prompt_tokens=256, - metadata={}, - model="gpt-4o", - parallel_tool_calls=True, - response_format="auto", - stream=False, - temperature=1, - thread={ - "messages": [ - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - ], - "metadata": {}, - "tool_resources": { - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": { - "vector_store_ids": ["string"], - "vector_stores": [ - { - "chunking_strategy": {"type": "auto"}, - "file_ids": ["string", "string", "string"], - "metadata": {}, - } - ], + with pytest.warns(DeprecationWarning): + thread = client.beta.threads.create_and_run( + assistant_id="assistant_id", + instructions="instructions", + max_completion_tokens=256, + max_prompt_tokens=256, + metadata={"foo": "string"}, + model="gpt-5.4", + parallel_tool_calls=True, + response_format="auto", + stream=False, + temperature=1, + thread={ + "messages": [ + { + "content": "string", + "role": "user", + "attachments": [ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + "metadata": {"foo": "string"}, + } + ], + "metadata": {"foo": "string"}, + "tool_resources": { + "code_interpreter": {"file_ids": ["string"]}, + "file_search": { + "vector_store_ids": ["string"], + "vector_stores": [ + { + "chunking_strategy": {"type": "auto"}, + "file_ids": ["string"], + "metadata": {"foo": "string"}, + } + ], + }, }, }, - }, - tool_choice="none", - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": {"vector_store_ids": ["string"]}, - }, - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - truncation_strategy={ - "type": "auto", - "last_messages": 1, - }, - ) + tool_choice="none", + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": {"vector_store_ids": ["string"]}, + }, + tools=[{"type": "code_interpreter"}], + top_p=1, + truncation_strategy={ + "type": "auto", + "last_messages": 1, + }, + ) + assert_matches_type(Run, thread, path=["response"]) @parametrize def test_raw_response_create_and_run_overload_1(self, client: OpenAI) -> None: - response = client.beta.threads.with_raw_response.create_and_run( - assistant_id="string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.with_raw_response.create_and_run( + assistant_id="assistant_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -445,169 +311,93 @@ def test_raw_response_create_and_run_overload_1(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create_and_run_overload_1(self, client: OpenAI) -> None: - with client.beta.threads.with_streaming_response.create_and_run( - assistant_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.with_streaming_response.create_and_run( + assistant_id="assistant_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - thread = response.parse() - assert_matches_type(Run, thread, path=["response"]) + thread = response.parse() + assert_matches_type(Run, thread, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_method_create_and_run_overload_2(self, client: OpenAI) -> None: - thread_stream = client.beta.threads.create_and_run( - assistant_id="string", - stream=True, - ) + with pytest.warns(DeprecationWarning): + thread_stream = client.beta.threads.create_and_run( + assistant_id="assistant_id", + stream=True, + ) + thread_stream.response.close() @parametrize def test_method_create_and_run_with_all_params_overload_2(self, client: OpenAI) -> None: - thread_stream = client.beta.threads.create_and_run( - assistant_id="string", - stream=True, - instructions="string", - max_completion_tokens=256, - max_prompt_tokens=256, - metadata={}, - model="gpt-4o", - parallel_tool_calls=True, - response_format="auto", - temperature=1, - thread={ - "messages": [ - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - ], - "metadata": {}, - "tool_resources": { - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": { - "vector_store_ids": ["string"], - "vector_stores": [ - { - "chunking_strategy": {"type": "auto"}, - "file_ids": ["string", "string", "string"], - "metadata": {}, - } - ], + with pytest.warns(DeprecationWarning): + thread_stream = client.beta.threads.create_and_run( + assistant_id="assistant_id", + stream=True, + instructions="instructions", + max_completion_tokens=256, + max_prompt_tokens=256, + metadata={"foo": "string"}, + model="gpt-5.4", + parallel_tool_calls=True, + response_format="auto", + temperature=1, + thread={ + "messages": [ + { + "content": "string", + "role": "user", + "attachments": [ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + "metadata": {"foo": "string"}, + } + ], + "metadata": {"foo": "string"}, + "tool_resources": { + "code_interpreter": {"file_ids": ["string"]}, + "file_search": { + "vector_store_ids": ["string"], + "vector_stores": [ + { + "chunking_strategy": {"type": "auto"}, + "file_ids": ["string"], + "metadata": {"foo": "string"}, + } + ], + }, }, }, - }, - tool_choice="none", - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": {"vector_store_ids": ["string"]}, - }, - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - truncation_strategy={ - "type": "auto", - "last_messages": 1, - }, - ) + tool_choice="none", + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": {"vector_store_ids": ["string"]}, + }, + tools=[{"type": "code_interpreter"}], + top_p=1, + truncation_strategy={ + "type": "auto", + "last_messages": 1, + }, + ) + thread_stream.response.close() @parametrize def test_raw_response_create_and_run_overload_2(self, client: OpenAI) -> None: - response = client.beta.threads.with_raw_response.create_and_run( - assistant_id="string", - stream=True, - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.with_raw_response.create_and_run( + assistant_id="assistant_id", + stream=True, + ) assert response.http_request.headers.get("X-Stainless-Lang") == "python" stream = response.parse() @@ -615,145 +405,71 @@ def test_raw_response_create_and_run_overload_2(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create_and_run_overload_2(self, client: OpenAI) -> None: - with client.beta.threads.with_streaming_response.create_and_run( - assistant_id="string", - stream=True, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.with_streaming_response.create_and_run( + assistant_id="assistant_id", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - stream = response.parse() - stream.close() + stream = response.parse() + stream.close() assert cast(Any, response.is_closed) is True class TestAsyncThreads: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: - thread = await async_client.beta.threads.create() + with pytest.warns(DeprecationWarning): + thread = await async_client.beta.threads.create() + assert_matches_type(Thread, thread, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: - thread = await async_client.beta.threads.create( - messages=[ - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - ], - metadata={}, - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": { - "vector_store_ids": ["string"], - "vector_stores": [ - { - "chunking_strategy": {"type": "auto"}, - "file_ids": ["string", "string", "string"], - "metadata": {}, - } - ], + with pytest.warns(DeprecationWarning): + thread = await async_client.beta.threads.create( + messages=[ + { + "content": "string", + "role": "user", + "attachments": [ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + "metadata": {"foo": "string"}, + } + ], + metadata={"foo": "string"}, + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": { + "vector_store_ids": ["string"], + "vector_stores": [ + { + "chunking_strategy": {"type": "auto"}, + "file_ids": ["string"], + "metadata": {"foo": "string"}, + } + ], + }, }, - }, - ) + ) + assert_matches_type(Thread, thread, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.with_raw_response.create() + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -762,27 +478,31 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.with_streaming_response.create() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - thread = await response.parse() - assert_matches_type(Thread, thread, path=["response"]) + thread = await response.parse() + assert_matches_type(Thread, thread, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: - thread = await async_client.beta.threads.retrieve( - "string", - ) + with pytest.warns(DeprecationWarning): + thread = await async_client.beta.threads.retrieve( + "thread_id", + ) + assert_matches_type(Thread, thread, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.with_raw_response.retrieve( - "string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.with_raw_response.retrieve( + "thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -791,48 +511,55 @@ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.with_streaming_response.retrieve( - "string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.with_streaming_response.retrieve( + "thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - thread = await response.parse() - assert_matches_type(Thread, thread, path=["response"]) + thread = await response.parse() + assert_matches_type(Thread, thread, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.with_raw_response.retrieve( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.with_raw_response.retrieve( + "", + ) @parametrize async def test_method_update(self, async_client: AsyncOpenAI) -> None: - thread = await async_client.beta.threads.update( - "string", - ) + with pytest.warns(DeprecationWarning): + thread = await async_client.beta.threads.update( + thread_id="thread_id", + ) + assert_matches_type(Thread, thread, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: - thread = await async_client.beta.threads.update( - "string", - metadata={}, - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": {"vector_store_ids": ["string"]}, - }, - ) + with pytest.warns(DeprecationWarning): + thread = await async_client.beta.threads.update( + thread_id="thread_id", + metadata={"foo": "string"}, + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": {"vector_store_ids": ["string"]}, + }, + ) + assert_matches_type(Thread, thread, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.with_raw_response.update( - "string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.with_raw_response.update( + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -841,36 +568,41 @@ async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.with_streaming_response.update( - "string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.with_streaming_response.update( + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - thread = await response.parse() - assert_matches_type(Thread, thread, path=["response"]) + thread = await response.parse() + assert_matches_type(Thread, thread, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.with_raw_response.update( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.with_raw_response.update( + thread_id="", + ) @parametrize async def test_method_delete(self, async_client: AsyncOpenAI) -> None: - thread = await async_client.beta.threads.delete( - "string", - ) + with pytest.warns(DeprecationWarning): + thread = await async_client.beta.threads.delete( + "thread_id", + ) + assert_matches_type(ThreadDeleted, thread, path=["response"]) @parametrize async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.with_raw_response.delete( - "string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.with_raw_response.delete( + "thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -879,174 +611,99 @@ async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.with_streaming_response.delete( - "string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.with_streaming_response.delete( + "thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - thread = await response.parse() - assert_matches_type(ThreadDeleted, thread, path=["response"]) + thread = await response.parse() + assert_matches_type(ThreadDeleted, thread, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.with_raw_response.delete( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.with_raw_response.delete( + "", + ) @parametrize async def test_method_create_and_run_overload_1(self, async_client: AsyncOpenAI) -> None: - thread = await async_client.beta.threads.create_and_run( - assistant_id="string", - ) + with pytest.warns(DeprecationWarning): + thread = await async_client.beta.threads.create_and_run( + assistant_id="assistant_id", + ) + assert_matches_type(Run, thread, path=["response"]) @parametrize async def test_method_create_and_run_with_all_params_overload_1(self, async_client: AsyncOpenAI) -> None: - thread = await async_client.beta.threads.create_and_run( - assistant_id="string", - instructions="string", - max_completion_tokens=256, - max_prompt_tokens=256, - metadata={}, - model="gpt-4o", - parallel_tool_calls=True, - response_format="auto", - stream=False, - temperature=1, - thread={ - "messages": [ - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - ], - "metadata": {}, - "tool_resources": { - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": { - "vector_store_ids": ["string"], - "vector_stores": [ - { - "chunking_strategy": {"type": "auto"}, - "file_ids": ["string", "string", "string"], - "metadata": {}, - } - ], + with pytest.warns(DeprecationWarning): + thread = await async_client.beta.threads.create_and_run( + assistant_id="assistant_id", + instructions="instructions", + max_completion_tokens=256, + max_prompt_tokens=256, + metadata={"foo": "string"}, + model="gpt-5.4", + parallel_tool_calls=True, + response_format="auto", + stream=False, + temperature=1, + thread={ + "messages": [ + { + "content": "string", + "role": "user", + "attachments": [ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + "metadata": {"foo": "string"}, + } + ], + "metadata": {"foo": "string"}, + "tool_resources": { + "code_interpreter": {"file_ids": ["string"]}, + "file_search": { + "vector_store_ids": ["string"], + "vector_stores": [ + { + "chunking_strategy": {"type": "auto"}, + "file_ids": ["string"], + "metadata": {"foo": "string"}, + } + ], + }, }, }, - }, - tool_choice="none", - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": {"vector_store_ids": ["string"]}, - }, - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - truncation_strategy={ - "type": "auto", - "last_messages": 1, - }, - ) + tool_choice="none", + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": {"vector_store_ids": ["string"]}, + }, + tools=[{"type": "code_interpreter"}], + top_p=1, + truncation_strategy={ + "type": "auto", + "last_messages": 1, + }, + ) + assert_matches_type(Run, thread, path=["response"]) @parametrize async def test_raw_response_create_and_run_overload_1(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.with_raw_response.create_and_run( - assistant_id="string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.with_raw_response.create_and_run( + assistant_id="assistant_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1055,169 +712,93 @@ async def test_raw_response_create_and_run_overload_1(self, async_client: AsyncO @parametrize async def test_streaming_response_create_and_run_overload_1(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.with_streaming_response.create_and_run( - assistant_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.with_streaming_response.create_and_run( + assistant_id="assistant_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - thread = await response.parse() - assert_matches_type(Run, thread, path=["response"]) + thread = await response.parse() + assert_matches_type(Run, thread, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_method_create_and_run_overload_2(self, async_client: AsyncOpenAI) -> None: - thread_stream = await async_client.beta.threads.create_and_run( - assistant_id="string", - stream=True, - ) + with pytest.warns(DeprecationWarning): + thread_stream = await async_client.beta.threads.create_and_run( + assistant_id="assistant_id", + stream=True, + ) + await thread_stream.response.aclose() @parametrize async def test_method_create_and_run_with_all_params_overload_2(self, async_client: AsyncOpenAI) -> None: - thread_stream = await async_client.beta.threads.create_and_run( - assistant_id="string", - stream=True, - instructions="string", - max_completion_tokens=256, - max_prompt_tokens=256, - metadata={}, - model="gpt-4o", - parallel_tool_calls=True, - response_format="auto", - temperature=1, - thread={ - "messages": [ - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - ], - "metadata": {}, - "tool_resources": { - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": { - "vector_store_ids": ["string"], - "vector_stores": [ - { - "chunking_strategy": {"type": "auto"}, - "file_ids": ["string", "string", "string"], - "metadata": {}, - } - ], + with pytest.warns(DeprecationWarning): + thread_stream = await async_client.beta.threads.create_and_run( + assistant_id="assistant_id", + stream=True, + instructions="instructions", + max_completion_tokens=256, + max_prompt_tokens=256, + metadata={"foo": "string"}, + model="gpt-5.4", + parallel_tool_calls=True, + response_format="auto", + temperature=1, + thread={ + "messages": [ + { + "content": "string", + "role": "user", + "attachments": [ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + "metadata": {"foo": "string"}, + } + ], + "metadata": {"foo": "string"}, + "tool_resources": { + "code_interpreter": {"file_ids": ["string"]}, + "file_search": { + "vector_store_ids": ["string"], + "vector_stores": [ + { + "chunking_strategy": {"type": "auto"}, + "file_ids": ["string"], + "metadata": {"foo": "string"}, + } + ], + }, }, }, - }, - tool_choice="none", - tool_resources={ - "code_interpreter": {"file_ids": ["string", "string", "string"]}, - "file_search": {"vector_store_ids": ["string"]}, - }, - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - truncation_strategy={ - "type": "auto", - "last_messages": 1, - }, - ) + tool_choice="none", + tool_resources={ + "code_interpreter": {"file_ids": ["string"]}, + "file_search": {"vector_store_ids": ["string"]}, + }, + tools=[{"type": "code_interpreter"}], + top_p=1, + truncation_strategy={ + "type": "auto", + "last_messages": 1, + }, + ) + await thread_stream.response.aclose() @parametrize async def test_raw_response_create_and_run_overload_2(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.with_raw_response.create_and_run( - assistant_id="string", - stream=True, - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.with_raw_response.create_and_run( + assistant_id="assistant_id", + stream=True, + ) assert response.http_request.headers.get("X-Stainless-Lang") == "python" stream = response.parse() @@ -1225,14 +806,15 @@ async def test_raw_response_create_and_run_overload_2(self, async_client: AsyncO @parametrize async def test_streaming_response_create_and_run_overload_2(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.with_streaming_response.create_and_run( - assistant_id="string", - stream=True, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - stream = await response.parse() - await stream.close() + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.with_streaming_response.create_and_run( + assistant_id="assistant_id", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/beta/threads/runs/test_steps.py b/tests/api_resources/beta/threads/runs/test_steps.py index f5dc17e0b5..ba44eec63d 100644 --- a/tests/api_resources/beta/threads/runs/test_steps.py +++ b/tests/api_resources/beta/threads/runs/test_steps.py @@ -12,6 +12,8 @@ from openai.pagination import SyncCursorPage, AsyncCursorPage from openai.types.beta.threads.runs import RunStep +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -20,30 +22,35 @@ class TestSteps: @parametrize def test_method_retrieve(self, client: OpenAI) -> None: - step = client.beta.threads.runs.steps.retrieve( - "string", - thread_id="string", - run_id="string", - ) + with pytest.warns(DeprecationWarning): + step = client.beta.threads.runs.steps.retrieve( + step_id="step_id", + thread_id="thread_id", + run_id="run_id", + ) + assert_matches_type(RunStep, step, path=["response"]) @parametrize def test_method_retrieve_with_all_params(self, client: OpenAI) -> None: - step = client.beta.threads.runs.steps.retrieve( - step_id="step_id", - thread_id="thread_id", - run_id="run_id", - include=["step_details.tool_calls[*].file_search.results[*].content"], - ) + with pytest.warns(DeprecationWarning): + step = client.beta.threads.runs.steps.retrieve( + step_id="step_id", + thread_id="thread_id", + run_id="run_id", + include=["step_details.tool_calls[*].file_search.results[*].content"], + ) + assert_matches_type(RunStep, step, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: OpenAI) -> None: - response = client.beta.threads.runs.steps.with_raw_response.retrieve( - "string", - thread_id="string", - run_id="string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.runs.steps.with_raw_response.retrieve( + step_id="step_id", + thread_id="thread_id", + run_id="run_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -52,69 +59,76 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: @parametrize def test_streaming_response_retrieve(self, client: OpenAI) -> None: - with client.beta.threads.runs.steps.with_streaming_response.retrieve( - "string", - thread_id="string", - run_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - step = response.parse() - assert_matches_type(RunStep, step, path=["response"]) + with pytest.warns(DeprecationWarning): + with client.beta.threads.runs.steps.with_streaming_response.retrieve( + step_id="step_id", + thread_id="thread_id", + run_id="run_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + step = response.parse() + assert_matches_type(RunStep, step, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_retrieve(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.runs.steps.with_raw_response.retrieve( - "string", - thread_id="", - run_id="string", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - client.beta.threads.runs.steps.with_raw_response.retrieve( - "string", - thread_id="string", - run_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `step_id` but received ''"): - client.beta.threads.runs.steps.with_raw_response.retrieve( - "", - thread_id="string", - run_id="string", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.runs.steps.with_raw_response.retrieve( + step_id="step_id", + thread_id="", + run_id="run_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.beta.threads.runs.steps.with_raw_response.retrieve( + step_id="step_id", + thread_id="thread_id", + run_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `step_id` but received ''"): + client.beta.threads.runs.steps.with_raw_response.retrieve( + step_id="", + thread_id="thread_id", + run_id="run_id", + ) @parametrize def test_method_list(self, client: OpenAI) -> None: - step = client.beta.threads.runs.steps.list( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + step = client.beta.threads.runs.steps.list( + run_id="run_id", + thread_id="thread_id", + ) + assert_matches_type(SyncCursorPage[RunStep], step, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: OpenAI) -> None: - step = client.beta.threads.runs.steps.list( - run_id="run_id", - thread_id="thread_id", - after="after", - before="before", - include=["step_details.tool_calls[*].file_search.results[*].content"], - limit=0, - order="asc", - ) + with pytest.warns(DeprecationWarning): + step = client.beta.threads.runs.steps.list( + run_id="run_id", + thread_id="thread_id", + after="after", + before="before", + include=["step_details.tool_calls[*].file_search.results[*].content"], + limit=0, + order="asc", + ) + assert_matches_type(SyncCursorPage[RunStep], step, path=["response"]) @parametrize def test_raw_response_list(self, client: OpenAI) -> None: - response = client.beta.threads.runs.steps.with_raw_response.list( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.runs.steps.with_raw_response.list( + run_id="run_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -123,62 +137,71 @@ def test_raw_response_list(self, client: OpenAI) -> None: @parametrize def test_streaming_response_list(self, client: OpenAI) -> None: - with client.beta.threads.runs.steps.with_streaming_response.list( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.runs.steps.with_streaming_response.list( + run_id="run_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - step = response.parse() - assert_matches_type(SyncCursorPage[RunStep], step, path=["response"]) + step = response.parse() + assert_matches_type(SyncCursorPage[RunStep], step, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_list(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.runs.steps.with_raw_response.list( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.runs.steps.with_raw_response.list( + run_id="run_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - client.beta.threads.runs.steps.with_raw_response.list( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.beta.threads.runs.steps.with_raw_response.list( + run_id="", + thread_id="thread_id", + ) class TestAsyncSteps: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: - step = await async_client.beta.threads.runs.steps.retrieve( - "string", - thread_id="string", - run_id="string", - ) + with pytest.warns(DeprecationWarning): + step = await async_client.beta.threads.runs.steps.retrieve( + step_id="step_id", + thread_id="thread_id", + run_id="run_id", + ) + assert_matches_type(RunStep, step, path=["response"]) @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncOpenAI) -> None: - step = await async_client.beta.threads.runs.steps.retrieve( - step_id="step_id", - thread_id="thread_id", - run_id="run_id", - include=["step_details.tool_calls[*].file_search.results[*].content"], - ) + with pytest.warns(DeprecationWarning): + step = await async_client.beta.threads.runs.steps.retrieve( + step_id="step_id", + thread_id="thread_id", + run_id="run_id", + include=["step_details.tool_calls[*].file_search.results[*].content"], + ) + assert_matches_type(RunStep, step, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.runs.steps.with_raw_response.retrieve( - "string", - thread_id="string", - run_id="string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.runs.steps.with_raw_response.retrieve( + step_id="step_id", + thread_id="thread_id", + run_id="run_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -187,69 +210,76 @@ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.runs.steps.with_streaming_response.retrieve( - "string", - thread_id="string", - run_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - step = await response.parse() - assert_matches_type(RunStep, step, path=["response"]) + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.runs.steps.with_streaming_response.retrieve( + step_id="step_id", + thread_id="thread_id", + run_id="run_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + step = await response.parse() + assert_matches_type(RunStep, step, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.runs.steps.with_raw_response.retrieve( - "string", - thread_id="", - run_id="string", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - await async_client.beta.threads.runs.steps.with_raw_response.retrieve( - "string", - thread_id="string", - run_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `step_id` but received ''"): - await async_client.beta.threads.runs.steps.with_raw_response.retrieve( - "", - thread_id="string", - run_id="string", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.runs.steps.with_raw_response.retrieve( + step_id="step_id", + thread_id="", + run_id="run_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.beta.threads.runs.steps.with_raw_response.retrieve( + step_id="step_id", + thread_id="thread_id", + run_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `step_id` but received ''"): + await async_client.beta.threads.runs.steps.with_raw_response.retrieve( + step_id="", + thread_id="thread_id", + run_id="run_id", + ) @parametrize async def test_method_list(self, async_client: AsyncOpenAI) -> None: - step = await async_client.beta.threads.runs.steps.list( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + step = await async_client.beta.threads.runs.steps.list( + run_id="run_id", + thread_id="thread_id", + ) + assert_matches_type(AsyncCursorPage[RunStep], step, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: - step = await async_client.beta.threads.runs.steps.list( - run_id="run_id", - thread_id="thread_id", - after="after", - before="before", - include=["step_details.tool_calls[*].file_search.results[*].content"], - limit=0, - order="asc", - ) + with pytest.warns(DeprecationWarning): + step = await async_client.beta.threads.runs.steps.list( + run_id="run_id", + thread_id="thread_id", + after="after", + before="before", + include=["step_details.tool_calls[*].file_search.results[*].content"], + limit=0, + order="asc", + ) + assert_matches_type(AsyncCursorPage[RunStep], step, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.runs.steps.with_raw_response.list( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.runs.steps.with_raw_response.list( + run_id="run_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -258,28 +288,30 @@ async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.runs.steps.with_streaming_response.list( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.runs.steps.with_streaming_response.list( + run_id="run_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - step = await response.parse() - assert_matches_type(AsyncCursorPage[RunStep], step, path=["response"]) + step = await response.parse() + assert_matches_type(AsyncCursorPage[RunStep], step, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.runs.steps.with_raw_response.list( - "string", - thread_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - await async_client.beta.threads.runs.steps.with_raw_response.list( - "", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.runs.steps.with_raw_response.list( + run_id="run_id", + thread_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.beta.threads.runs.steps.with_raw_response.list( + run_id="", + thread_id="thread_id", + ) diff --git a/tests/api_resources/beta/threads/test_messages.py b/tests/api_resources/beta/threads/test_messages.py index b5be32a421..7f57002f27 100644 --- a/tests/api_resources/beta/threads/test_messages.py +++ b/tests/api_resources/beta/threads/test_messages.py @@ -15,6 +15,8 @@ MessageDeleted, ) +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -23,44 +25,41 @@ class TestMessages: @parametrize def test_method_create(self, client: OpenAI) -> None: - message = client.beta.threads.messages.create( - "string", - content="string", - role="user", - ) + with pytest.warns(DeprecationWarning): + message = client.beta.threads.messages.create( + thread_id="thread_id", + content="string", + role="user", + ) + assert_matches_type(Message, message, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: OpenAI) -> None: - message = client.beta.threads.messages.create( - "string", - content="string", - role="user", - attachments=[ - { - "file_id": "string", - "tools": [{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - }, - { - "file_id": "string", - "tools": [{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - }, - { - "file_id": "string", - "tools": [{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - }, - ], - metadata={}, - ) + with pytest.warns(DeprecationWarning): + message = client.beta.threads.messages.create( + thread_id="thread_id", + content="string", + role="user", + attachments=[ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + metadata={"foo": "string"}, + ) + assert_matches_type(Message, message, path=["response"]) @parametrize def test_raw_response_create(self, client: OpenAI) -> None: - response = client.beta.threads.messages.with_raw_response.create( - "string", - content="string", - role="user", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.messages.with_raw_response.create( + thread_id="thread_id", + content="string", + role="user", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -69,42 +68,47 @@ def test_raw_response_create(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create(self, client: OpenAI) -> None: - with client.beta.threads.messages.with_streaming_response.create( - "string", - content="string", - role="user", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.messages.with_streaming_response.create( + thread_id="thread_id", + content="string", + role="user", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = response.parse() - assert_matches_type(Message, message, path=["response"]) + message = response.parse() + assert_matches_type(Message, message, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_create(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.messages.with_raw_response.create( - "", - content="string", - role="user", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.messages.with_raw_response.create( + thread_id="", + content="string", + role="user", + ) @parametrize def test_method_retrieve(self, client: OpenAI) -> None: - message = client.beta.threads.messages.retrieve( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + message = client.beta.threads.messages.retrieve( + message_id="message_id", + thread_id="thread_id", + ) + assert_matches_type(Message, message, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: OpenAI) -> None: - response = client.beta.threads.messages.with_raw_response.retrieve( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.messages.with_raw_response.retrieve( + message_id="message_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -113,55 +117,62 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: @parametrize def test_streaming_response_retrieve(self, client: OpenAI) -> None: - with client.beta.threads.messages.with_streaming_response.retrieve( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.messages.with_streaming_response.retrieve( + message_id="message_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = response.parse() - assert_matches_type(Message, message, path=["response"]) + message = response.parse() + assert_matches_type(Message, message, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_retrieve(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.messages.with_raw_response.retrieve( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.messages.with_raw_response.retrieve( + message_id="message_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): - client.beta.threads.messages.with_raw_response.retrieve( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): + client.beta.threads.messages.with_raw_response.retrieve( + message_id="", + thread_id="thread_id", + ) @parametrize def test_method_update(self, client: OpenAI) -> None: - message = client.beta.threads.messages.update( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + message = client.beta.threads.messages.update( + message_id="message_id", + thread_id="thread_id", + ) + assert_matches_type(Message, message, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: OpenAI) -> None: - message = client.beta.threads.messages.update( - "string", - thread_id="string", - metadata={}, - ) + with pytest.warns(DeprecationWarning): + message = client.beta.threads.messages.update( + message_id="message_id", + thread_id="thread_id", + metadata={"foo": "string"}, + ) + assert_matches_type(Message, message, path=["response"]) @parametrize def test_raw_response_update(self, client: OpenAI) -> None: - response = client.beta.threads.messages.with_raw_response.update( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.messages.with_raw_response.update( + message_id="message_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -170,56 +181,63 @@ def test_raw_response_update(self, client: OpenAI) -> None: @parametrize def test_streaming_response_update(self, client: OpenAI) -> None: - with client.beta.threads.messages.with_streaming_response.update( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.messages.with_streaming_response.update( + message_id="message_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = response.parse() - assert_matches_type(Message, message, path=["response"]) + message = response.parse() + assert_matches_type(Message, message, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_update(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.messages.with_raw_response.update( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.messages.with_raw_response.update( + message_id="message_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): - client.beta.threads.messages.with_raw_response.update( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): + client.beta.threads.messages.with_raw_response.update( + message_id="", + thread_id="thread_id", + ) @parametrize def test_method_list(self, client: OpenAI) -> None: - message = client.beta.threads.messages.list( - "string", - ) + with pytest.warns(DeprecationWarning): + message = client.beta.threads.messages.list( + thread_id="thread_id", + ) + assert_matches_type(SyncCursorPage[Message], message, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: OpenAI) -> None: - message = client.beta.threads.messages.list( - "string", - after="string", - before="string", - limit=0, - order="asc", - run_id="string", - ) + with pytest.warns(DeprecationWarning): + message = client.beta.threads.messages.list( + thread_id="thread_id", + after="after", + before="before", + limit=0, + order="asc", + run_id="run_id", + ) + assert_matches_type(SyncCursorPage[Message], message, path=["response"]) @parametrize def test_raw_response_list(self, client: OpenAI) -> None: - response = client.beta.threads.messages.with_raw_response.list( - "string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.messages.with_raw_response.list( + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -228,38 +246,43 @@ def test_raw_response_list(self, client: OpenAI) -> None: @parametrize def test_streaming_response_list(self, client: OpenAI) -> None: - with client.beta.threads.messages.with_streaming_response.list( - "string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.messages.with_streaming_response.list( + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = response.parse() - assert_matches_type(SyncCursorPage[Message], message, path=["response"]) + message = response.parse() + assert_matches_type(SyncCursorPage[Message], message, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_list(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.messages.with_raw_response.list( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.messages.with_raw_response.list( + thread_id="", + ) @parametrize def test_method_delete(self, client: OpenAI) -> None: - message = client.beta.threads.messages.delete( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + message = client.beta.threads.messages.delete( + message_id="message_id", + thread_id="thread_id", + ) + assert_matches_type(MessageDeleted, message, path=["response"]) @parametrize def test_raw_response_delete(self, client: OpenAI) -> None: - response = client.beta.threads.messages.with_raw_response.delete( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.messages.with_raw_response.delete( + message_id="message_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -268,76 +291,77 @@ def test_raw_response_delete(self, client: OpenAI) -> None: @parametrize def test_streaming_response_delete(self, client: OpenAI) -> None: - with client.beta.threads.messages.with_streaming_response.delete( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.messages.with_streaming_response.delete( + message_id="message_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = response.parse() - assert_matches_type(MessageDeleted, message, path=["response"]) + message = response.parse() + assert_matches_type(MessageDeleted, message, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_delete(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.messages.with_raw_response.delete( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.messages.with_raw_response.delete( + message_id="message_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): - client.beta.threads.messages.with_raw_response.delete( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): + client.beta.threads.messages.with_raw_response.delete( + message_id="", + thread_id="thread_id", + ) class TestAsyncMessages: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: - message = await async_client.beta.threads.messages.create( - "string", - content="string", - role="user", - ) + with pytest.warns(DeprecationWarning): + message = await async_client.beta.threads.messages.create( + thread_id="thread_id", + content="string", + role="user", + ) + assert_matches_type(Message, message, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: - message = await async_client.beta.threads.messages.create( - "string", - content="string", - role="user", - attachments=[ - { - "file_id": "string", - "tools": [{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - }, - { - "file_id": "string", - "tools": [{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - }, - { - "file_id": "string", - "tools": [{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - }, - ], - metadata={}, - ) + with pytest.warns(DeprecationWarning): + message = await async_client.beta.threads.messages.create( + thread_id="thread_id", + content="string", + role="user", + attachments=[ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + metadata={"foo": "string"}, + ) + assert_matches_type(Message, message, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.messages.with_raw_response.create( - "string", - content="string", - role="user", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.messages.with_raw_response.create( + thread_id="thread_id", + content="string", + role="user", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -346,42 +370,47 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.messages.with_streaming_response.create( - "string", - content="string", - role="user", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.messages.with_streaming_response.create( + thread_id="thread_id", + content="string", + role="user", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = await response.parse() - assert_matches_type(Message, message, path=["response"]) + message = await response.parse() + assert_matches_type(Message, message, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.messages.with_raw_response.create( - "", - content="string", - role="user", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.messages.with_raw_response.create( + thread_id="", + content="string", + role="user", + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: - message = await async_client.beta.threads.messages.retrieve( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + message = await async_client.beta.threads.messages.retrieve( + message_id="message_id", + thread_id="thread_id", + ) + assert_matches_type(Message, message, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.messages.with_raw_response.retrieve( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.messages.with_raw_response.retrieve( + message_id="message_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -390,55 +419,62 @@ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.messages.with_streaming_response.retrieve( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.messages.with_streaming_response.retrieve( + message_id="message_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = await response.parse() - assert_matches_type(Message, message, path=["response"]) + message = await response.parse() + assert_matches_type(Message, message, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.messages.with_raw_response.retrieve( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.messages.with_raw_response.retrieve( + message_id="message_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): - await async_client.beta.threads.messages.with_raw_response.retrieve( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): + await async_client.beta.threads.messages.with_raw_response.retrieve( + message_id="", + thread_id="thread_id", + ) @parametrize async def test_method_update(self, async_client: AsyncOpenAI) -> None: - message = await async_client.beta.threads.messages.update( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + message = await async_client.beta.threads.messages.update( + message_id="message_id", + thread_id="thread_id", + ) + assert_matches_type(Message, message, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: - message = await async_client.beta.threads.messages.update( - "string", - thread_id="string", - metadata={}, - ) + with pytest.warns(DeprecationWarning): + message = await async_client.beta.threads.messages.update( + message_id="message_id", + thread_id="thread_id", + metadata={"foo": "string"}, + ) + assert_matches_type(Message, message, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.messages.with_raw_response.update( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.messages.with_raw_response.update( + message_id="message_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -447,56 +483,63 @@ async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.messages.with_streaming_response.update( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.messages.with_streaming_response.update( + message_id="message_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = await response.parse() - assert_matches_type(Message, message, path=["response"]) + message = await response.parse() + assert_matches_type(Message, message, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.messages.with_raw_response.update( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.messages.with_raw_response.update( + message_id="message_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): - await async_client.beta.threads.messages.with_raw_response.update( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): + await async_client.beta.threads.messages.with_raw_response.update( + message_id="", + thread_id="thread_id", + ) @parametrize async def test_method_list(self, async_client: AsyncOpenAI) -> None: - message = await async_client.beta.threads.messages.list( - "string", - ) + with pytest.warns(DeprecationWarning): + message = await async_client.beta.threads.messages.list( + thread_id="thread_id", + ) + assert_matches_type(AsyncCursorPage[Message], message, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: - message = await async_client.beta.threads.messages.list( - "string", - after="string", - before="string", - limit=0, - order="asc", - run_id="string", - ) + with pytest.warns(DeprecationWarning): + message = await async_client.beta.threads.messages.list( + thread_id="thread_id", + after="after", + before="before", + limit=0, + order="asc", + run_id="run_id", + ) + assert_matches_type(AsyncCursorPage[Message], message, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.messages.with_raw_response.list( - "string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.messages.with_raw_response.list( + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -505,38 +548,43 @@ async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.messages.with_streaming_response.list( - "string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.messages.with_streaming_response.list( + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = await response.parse() - assert_matches_type(AsyncCursorPage[Message], message, path=["response"]) + message = await response.parse() + assert_matches_type(AsyncCursorPage[Message], message, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.messages.with_raw_response.list( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.messages.with_raw_response.list( + thread_id="", + ) @parametrize async def test_method_delete(self, async_client: AsyncOpenAI) -> None: - message = await async_client.beta.threads.messages.delete( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + message = await async_client.beta.threads.messages.delete( + message_id="message_id", + thread_id="thread_id", + ) + assert_matches_type(MessageDeleted, message, path=["response"]) @parametrize async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.messages.with_raw_response.delete( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.messages.with_raw_response.delete( + message_id="message_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -545,28 +593,30 @@ async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.messages.with_streaming_response.delete( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.messages.with_streaming_response.delete( + message_id="message_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = await response.parse() - assert_matches_type(MessageDeleted, message, path=["response"]) + message = await response.parse() + assert_matches_type(MessageDeleted, message, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.messages.with_raw_response.delete( - "string", - thread_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): - await async_client.beta.threads.messages.with_raw_response.delete( - "", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.messages.with_raw_response.delete( + message_id="message_id", + thread_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): + await async_client.beta.threads.messages.with_raw_response.delete( + message_id="", + thread_id="thread_id", + ) diff --git a/tests/api_resources/beta/threads/test_runs.py b/tests/api_resources/beta/threads/test_runs.py index c8d70f5f89..aa85871325 100644 --- a/tests/api_resources/beta/threads/test_runs.py +++ b/tests/api_resources/beta/threads/test_runs.py @@ -24,139 +24,63 @@ class TestRuns: @parametrize def test_method_create_overload_1(self, client: OpenAI) -> None: - run = client.beta.threads.runs.create( - "string", - assistant_id="string", - ) + with pytest.warns(DeprecationWarning): + run = client.beta.threads.runs.create( + thread_id="thread_id", + assistant_id="assistant_id", + ) + assert_matches_type(Run, run, path=["response"]) @parametrize def test_method_create_with_all_params_overload_1(self, client: OpenAI) -> None: - run = client.beta.threads.runs.create( - thread_id="thread_id", - assistant_id="assistant_id", - include=["step_details.tool_calls[*].file_search.results[*].content"], - additional_instructions="additional_instructions", - additional_messages=[ - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, + with pytest.warns(DeprecationWarning): + run = client.beta.threads.runs.create( + thread_id="thread_id", + assistant_id="assistant_id", + include=["step_details.tool_calls[*].file_search.results[*].content"], + additional_instructions="additional_instructions", + additional_messages=[ + { + "content": "string", + "role": "user", + "attachments": [ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + "metadata": {"foo": "string"}, + } + ], + instructions="instructions", + max_completion_tokens=256, + max_prompt_tokens=256, + metadata={"foo": "string"}, + model="gpt-5.4", + parallel_tool_calls=True, + reasoning_effort="none", + response_format="auto", + stream=False, + temperature=1, + tool_choice="none", + tools=[{"type": "code_interpreter"}], + top_p=1, + truncation_strategy={ + "type": "auto", + "last_messages": 1, }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - ], - instructions="string", - max_completion_tokens=256, - max_prompt_tokens=256, - metadata={}, - model="gpt-4o", - parallel_tool_calls=True, - response_format="auto", - stream=False, - temperature=1, - tool_choice="none", - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - truncation_strategy={ - "type": "auto", - "last_messages": 1, - }, - ) + ) + assert_matches_type(Run, run, path=["response"]) @parametrize def test_raw_response_create_overload_1(self, client: OpenAI) -> None: - response = client.beta.threads.runs.with_raw_response.create( - "string", - assistant_id="string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.runs.with_raw_response.create( + thread_id="thread_id", + assistant_id="assistant_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -165,163 +89,89 @@ def test_raw_response_create_overload_1(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create_overload_1(self, client: OpenAI) -> None: - with client.beta.threads.runs.with_streaming_response.create( - "string", - assistant_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.runs.with_streaming_response.create( + thread_id="thread_id", + assistant_id="assistant_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - run = response.parse() - assert_matches_type(Run, run, path=["response"]) + run = response.parse() + assert_matches_type(Run, run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_create_overload_1(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.runs.with_raw_response.create( - "", - assistant_id="string", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.runs.with_raw_response.create( + thread_id="", + assistant_id="assistant_id", + ) @parametrize def test_method_create_overload_2(self, client: OpenAI) -> None: - run_stream = client.beta.threads.runs.create( - "string", - assistant_id="string", - stream=True, - ) + with pytest.warns(DeprecationWarning): + run_stream = client.beta.threads.runs.create( + thread_id="thread_id", + assistant_id="assistant_id", + stream=True, + ) + run_stream.response.close() @parametrize def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: - run_stream = client.beta.threads.runs.create( - "string", - assistant_id="string", - stream=True, - include=["step_details.tool_calls[*].file_search.results[*].content"], - additional_instructions="additional_instructions", - additional_messages=[ - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, + with pytest.warns(DeprecationWarning): + run_stream = client.beta.threads.runs.create( + thread_id="thread_id", + assistant_id="assistant_id", + stream=True, + include=["step_details.tool_calls[*].file_search.results[*].content"], + additional_instructions="additional_instructions", + additional_messages=[ + { + "content": "string", + "role": "user", + "attachments": [ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + "metadata": {"foo": "string"}, + } + ], + instructions="instructions", + max_completion_tokens=256, + max_prompt_tokens=256, + metadata={"foo": "string"}, + model="gpt-5.4", + parallel_tool_calls=True, + reasoning_effort="none", + response_format="auto", + temperature=1, + tool_choice="none", + tools=[{"type": "code_interpreter"}], + top_p=1, + truncation_strategy={ + "type": "auto", + "last_messages": 1, }, - ], - instructions="string", - max_completion_tokens=256, - max_prompt_tokens=256, - metadata={}, - model="gpt-4o", - parallel_tool_calls=True, - response_format="auto", - temperature=1, - tool_choice="none", - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - truncation_strategy={ - "type": "auto", - "last_messages": 1, - }, - ) + ) + run_stream.response.close() @parametrize def test_raw_response_create_overload_2(self, client: OpenAI) -> None: - response = client.beta.threads.runs.with_raw_response.create( - "string", - assistant_id="string", - stream=True, - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.runs.with_raw_response.create( + thread_id="thread_id", + assistant_id="assistant_id", + stream=True, + ) assert response.http_request.headers.get("X-Stainless-Lang") == "python" stream = response.parse() @@ -329,42 +179,47 @@ def test_raw_response_create_overload_2(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create_overload_2(self, client: OpenAI) -> None: - with client.beta.threads.runs.with_streaming_response.create( - "string", - assistant_id="string", - stream=True, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.runs.with_streaming_response.create( + thread_id="thread_id", + assistant_id="assistant_id", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - stream = response.parse() - stream.close() + stream = response.parse() + stream.close() assert cast(Any, response.is_closed) is True @parametrize def test_path_params_create_overload_2(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.runs.with_raw_response.create( - "", - assistant_id="string", - stream=True, - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.runs.with_raw_response.create( + thread_id="", + assistant_id="assistant_id", + stream=True, + ) @parametrize def test_method_retrieve(self, client: OpenAI) -> None: - run = client.beta.threads.runs.retrieve( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + run = client.beta.threads.runs.retrieve( + run_id="run_id", + thread_id="thread_id", + ) + assert_matches_type(Run, run, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: OpenAI) -> None: - response = client.beta.threads.runs.with_raw_response.retrieve( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.runs.with_raw_response.retrieve( + run_id="run_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -373,55 +228,62 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: @parametrize def test_streaming_response_retrieve(self, client: OpenAI) -> None: - with client.beta.threads.runs.with_streaming_response.retrieve( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.runs.with_streaming_response.retrieve( + run_id="run_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - run = response.parse() - assert_matches_type(Run, run, path=["response"]) + run = response.parse() + assert_matches_type(Run, run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_retrieve(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.runs.with_raw_response.retrieve( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.runs.with_raw_response.retrieve( + run_id="run_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - client.beta.threads.runs.with_raw_response.retrieve( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.beta.threads.runs.with_raw_response.retrieve( + run_id="", + thread_id="thread_id", + ) @parametrize def test_method_update(self, client: OpenAI) -> None: - run = client.beta.threads.runs.update( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + run = client.beta.threads.runs.update( + run_id="run_id", + thread_id="thread_id", + ) + assert_matches_type(Run, run, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: OpenAI) -> None: - run = client.beta.threads.runs.update( - "string", - thread_id="string", - metadata={}, - ) + with pytest.warns(DeprecationWarning): + run = client.beta.threads.runs.update( + run_id="run_id", + thread_id="thread_id", + metadata={"foo": "string"}, + ) + assert_matches_type(Run, run, path=["response"]) @parametrize def test_raw_response_update(self, client: OpenAI) -> None: - response = client.beta.threads.runs.with_raw_response.update( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.runs.with_raw_response.update( + run_id="run_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -430,55 +292,62 @@ def test_raw_response_update(self, client: OpenAI) -> None: @parametrize def test_streaming_response_update(self, client: OpenAI) -> None: - with client.beta.threads.runs.with_streaming_response.update( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.runs.with_streaming_response.update( + run_id="run_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - run = response.parse() - assert_matches_type(Run, run, path=["response"]) + run = response.parse() + assert_matches_type(Run, run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_update(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.runs.with_raw_response.update( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.runs.with_raw_response.update( + run_id="run_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - client.beta.threads.runs.with_raw_response.update( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.beta.threads.runs.with_raw_response.update( + run_id="", + thread_id="thread_id", + ) @parametrize def test_method_list(self, client: OpenAI) -> None: - run = client.beta.threads.runs.list( - "string", - ) + with pytest.warns(DeprecationWarning): + run = client.beta.threads.runs.list( + thread_id="thread_id", + ) + assert_matches_type(SyncCursorPage[Run], run, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: OpenAI) -> None: - run = client.beta.threads.runs.list( - "string", - after="string", - before="string", - limit=0, - order="asc", - ) + with pytest.warns(DeprecationWarning): + run = client.beta.threads.runs.list( + thread_id="thread_id", + after="after", + before="before", + limit=0, + order="asc", + ) + assert_matches_type(SyncCursorPage[Run], run, path=["response"]) @parametrize def test_raw_response_list(self, client: OpenAI) -> None: - response = client.beta.threads.runs.with_raw_response.list( - "string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.runs.with_raw_response.list( + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -487,38 +356,43 @@ def test_raw_response_list(self, client: OpenAI) -> None: @parametrize def test_streaming_response_list(self, client: OpenAI) -> None: - with client.beta.threads.runs.with_streaming_response.list( - "string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.runs.with_streaming_response.list( + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - run = response.parse() - assert_matches_type(SyncCursorPage[Run], run, path=["response"]) + run = response.parse() + assert_matches_type(SyncCursorPage[Run], run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_list(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.runs.with_raw_response.list( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.runs.with_raw_response.list( + thread_id="", + ) @parametrize def test_method_cancel(self, client: OpenAI) -> None: - run = client.beta.threads.runs.cancel( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + run = client.beta.threads.runs.cancel( + run_id="run_id", + thread_id="thread_id", + ) + assert_matches_type(Run, run, path=["response"]) @parametrize def test_raw_response_cancel(self, client: OpenAI) -> None: - response = client.beta.threads.runs.with_raw_response.cancel( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.runs.with_raw_response.cancel( + run_id="run_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -527,71 +401,70 @@ def test_raw_response_cancel(self, client: OpenAI) -> None: @parametrize def test_streaming_response_cancel(self, client: OpenAI) -> None: - with client.beta.threads.runs.with_streaming_response.cancel( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.beta.threads.runs.with_streaming_response.cancel( + run_id="run_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - run = response.parse() - assert_matches_type(Run, run, path=["response"]) + run = response.parse() + assert_matches_type(Run, run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_cancel(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.runs.with_raw_response.cancel( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.runs.with_raw_response.cancel( + run_id="run_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - client.beta.threads.runs.with_raw_response.cancel( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.beta.threads.runs.with_raw_response.cancel( + run_id="", + thread_id="thread_id", + ) @parametrize def test_method_submit_tool_outputs_overload_1(self, client: OpenAI) -> None: - run = client.beta.threads.runs.submit_tool_outputs( - "string", - thread_id="string", - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + run = client.beta.threads.runs.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + tool_outputs=[{}], + ) + assert_matches_type(Run, run, path=["response"]) @parametrize def test_method_submit_tool_outputs_with_all_params_overload_1(self, client: OpenAI) -> None: - run = client.beta.threads.runs.submit_tool_outputs( - "string", - thread_id="string", - tool_outputs=[ - { - "output": "output", - "tool_call_id": "tool_call_id", - }, - { - "output": "output", - "tool_call_id": "tool_call_id", - }, - { - "output": "output", - "tool_call_id": "tool_call_id", - }, - ], - stream=False, - ) + with pytest.warns(DeprecationWarning): + run = client.beta.threads.runs.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + tool_outputs=[ + { + "output": "output", + "tool_call_id": "tool_call_id", + } + ], + stream=False, + ) + assert_matches_type(Run, run, path=["response"]) @parametrize def test_raw_response_submit_tool_outputs_overload_1(self, client: OpenAI) -> None: - response = client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "string", - thread_id="string", - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + tool_outputs=[{}], + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -600,53 +473,58 @@ def test_raw_response_submit_tool_outputs_overload_1(self, client: OpenAI) -> No @parametrize def test_streaming_response_submit_tool_outputs_overload_1(self, client: OpenAI) -> None: - with client.beta.threads.runs.with_streaming_response.submit_tool_outputs( - "string", - thread_id="string", - tool_outputs=[{}, {}, {}], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - run = response.parse() - assert_matches_type(Run, run, path=["response"]) + with pytest.warns(DeprecationWarning): + with client.beta.threads.runs.with_streaming_response.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + tool_outputs=[{}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(Run, run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_submit_tool_outputs_overload_1(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "string", - thread_id="", - tool_outputs=[{}, {}, {}], - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "", - thread_id="string", - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="run_id", + thread_id="", + tool_outputs=[{}], + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="", + thread_id="thread_id", + tool_outputs=[{}], + ) @parametrize def test_method_submit_tool_outputs_overload_2(self, client: OpenAI) -> None: - run_stream = client.beta.threads.runs.submit_tool_outputs( - "string", - thread_id="string", - stream=True, - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + run_stream = client.beta.threads.runs.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + stream=True, + tool_outputs=[{}], + ) + run_stream.response.close() @parametrize def test_raw_response_submit_tool_outputs_overload_2(self, client: OpenAI) -> None: - response = client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "string", - thread_id="string", - stream=True, - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + response = client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + stream=True, + tool_outputs=[{}], + ) assert response.http_request.headers.get("X-Stainless-Lang") == "python" stream = response.parse() @@ -654,177 +532,105 @@ def test_raw_response_submit_tool_outputs_overload_2(self, client: OpenAI) -> No @parametrize def test_streaming_response_submit_tool_outputs_overload_2(self, client: OpenAI) -> None: - with client.beta.threads.runs.with_streaming_response.submit_tool_outputs( - "string", - thread_id="string", - stream=True, - tool_outputs=[{}, {}, {}], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - stream = response.parse() - stream.close() + with pytest.warns(DeprecationWarning): + with client.beta.threads.runs.with_streaming_response.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + stream=True, + tool_outputs=[{}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() assert cast(Any, response.is_closed) is True @parametrize def test_path_params_submit_tool_outputs_overload_2(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "string", - thread_id="", - stream=True, - tool_outputs=[{}, {}, {}], - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "", - thread_id="string", - stream=True, - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="run_id", + thread_id="", + stream=True, + tool_outputs=[{}], + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="", + thread_id="thread_id", + stream=True, + tool_outputs=[{}], + ) class TestAsyncRuns: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None: - run = await async_client.beta.threads.runs.create( - "string", - assistant_id="string", - ) + with pytest.warns(DeprecationWarning): + run = await async_client.beta.threads.runs.create( + thread_id="thread_id", + assistant_id="assistant_id", + ) + assert_matches_type(Run, run, path=["response"]) @parametrize async def test_method_create_with_all_params_overload_1(self, async_client: AsyncOpenAI) -> None: - run = await async_client.beta.threads.runs.create( - thread_id="thread_id", - assistant_id="assistant_id", - include=["step_details.tool_calls[*].file_search.results[*].content"], - additional_instructions="additional_instructions", - additional_messages=[ - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, + with pytest.warns(DeprecationWarning): + run = await async_client.beta.threads.runs.create( + thread_id="thread_id", + assistant_id="assistant_id", + include=["step_details.tool_calls[*].file_search.results[*].content"], + additional_instructions="additional_instructions", + additional_messages=[ + { + "content": "string", + "role": "user", + "attachments": [ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + "metadata": {"foo": "string"}, + } + ], + instructions="instructions", + max_completion_tokens=256, + max_prompt_tokens=256, + metadata={"foo": "string"}, + model="gpt-5.4", + parallel_tool_calls=True, + reasoning_effort="none", + response_format="auto", + stream=False, + temperature=1, + tool_choice="none", + tools=[{"type": "code_interpreter"}], + top_p=1, + truncation_strategy={ + "type": "auto", + "last_messages": 1, }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - ], - instructions="string", - max_completion_tokens=256, - max_prompt_tokens=256, - metadata={}, - model="gpt-4o", - parallel_tool_calls=True, - response_format="auto", - stream=False, - temperature=1, - tool_choice="none", - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - truncation_strategy={ - "type": "auto", - "last_messages": 1, - }, - ) + ) + assert_matches_type(Run, run, path=["response"]) @parametrize async def test_raw_response_create_overload_1(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.runs.with_raw_response.create( - "string", - assistant_id="string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.runs.with_raw_response.create( + thread_id="thread_id", + assistant_id="assistant_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -833,163 +639,89 @@ async def test_raw_response_create_overload_1(self, async_client: AsyncOpenAI) - @parametrize async def test_streaming_response_create_overload_1(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.runs.with_streaming_response.create( - "string", - assistant_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.runs.with_streaming_response.create( + thread_id="thread_id", + assistant_id="assistant_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - run = await response.parse() - assert_matches_type(Run, run, path=["response"]) + run = await response.parse() + assert_matches_type(Run, run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_create_overload_1(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.create( - "", - assistant_id="string", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.create( + thread_id="", + assistant_id="assistant_id", + ) @parametrize async def test_method_create_overload_2(self, async_client: AsyncOpenAI) -> None: - run_stream = await async_client.beta.threads.runs.create( - "string", - assistant_id="string", - stream=True, - ) + with pytest.warns(DeprecationWarning): + run_stream = await async_client.beta.threads.runs.create( + thread_id="thread_id", + assistant_id="assistant_id", + stream=True, + ) + await run_stream.response.aclose() @parametrize async def test_method_create_with_all_params_overload_2(self, async_client: AsyncOpenAI) -> None: - run_stream = await async_client.beta.threads.runs.create( - "string", - assistant_id="string", - stream=True, - include=["step_details.tool_calls[*].file_search.results[*].content"], - additional_instructions="additional_instructions", - additional_messages=[ - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, - }, - { - "content": "string", - "role": "user", - "attachments": [ - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - { - "file_id": "string", - "tools": [ - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - {"type": "code_interpreter"}, - ], - }, - ], - "metadata": {}, + with pytest.warns(DeprecationWarning): + run_stream = await async_client.beta.threads.runs.create( + thread_id="thread_id", + assistant_id="assistant_id", + stream=True, + include=["step_details.tool_calls[*].file_search.results[*].content"], + additional_instructions="additional_instructions", + additional_messages=[ + { + "content": "string", + "role": "user", + "attachments": [ + { + "file_id": "file_id", + "tools": [{"type": "code_interpreter"}], + } + ], + "metadata": {"foo": "string"}, + } + ], + instructions="instructions", + max_completion_tokens=256, + max_prompt_tokens=256, + metadata={"foo": "string"}, + model="gpt-5.4", + parallel_tool_calls=True, + reasoning_effort="none", + response_format="auto", + temperature=1, + tool_choice="none", + tools=[{"type": "code_interpreter"}], + top_p=1, + truncation_strategy={ + "type": "auto", + "last_messages": 1, }, - ], - instructions="string", - max_completion_tokens=256, - max_prompt_tokens=256, - metadata={}, - model="gpt-4o", - parallel_tool_calls=True, - response_format="auto", - temperature=1, - tool_choice="none", - tools=[{"type": "code_interpreter"}, {"type": "code_interpreter"}, {"type": "code_interpreter"}], - top_p=1, - truncation_strategy={ - "type": "auto", - "last_messages": 1, - }, - ) + ) + await run_stream.response.aclose() @parametrize async def test_raw_response_create_overload_2(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.runs.with_raw_response.create( - "string", - assistant_id="string", - stream=True, - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.runs.with_raw_response.create( + thread_id="thread_id", + assistant_id="assistant_id", + stream=True, + ) assert response.http_request.headers.get("X-Stainless-Lang") == "python" stream = response.parse() @@ -997,42 +729,47 @@ async def test_raw_response_create_overload_2(self, async_client: AsyncOpenAI) - @parametrize async def test_streaming_response_create_overload_2(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.runs.with_streaming_response.create( - "string", - assistant_id="string", - stream=True, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.runs.with_streaming_response.create( + thread_id="thread_id", + assistant_id="assistant_id", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - stream = await response.parse() - await stream.close() + stream = await response.parse() + await stream.close() assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_create_overload_2(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.create( - "", - assistant_id="string", - stream=True, - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.create( + thread_id="", + assistant_id="assistant_id", + stream=True, + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: - run = await async_client.beta.threads.runs.retrieve( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + run = await async_client.beta.threads.runs.retrieve( + run_id="run_id", + thread_id="thread_id", + ) + assert_matches_type(Run, run, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.runs.with_raw_response.retrieve( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.runs.with_raw_response.retrieve( + run_id="run_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1041,55 +778,62 @@ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.runs.with_streaming_response.retrieve( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.runs.with_streaming_response.retrieve( + run_id="run_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - run = await response.parse() - assert_matches_type(Run, run, path=["response"]) + run = await response.parse() + assert_matches_type(Run, run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.retrieve( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.retrieve( + run_id="run_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.retrieve( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.retrieve( + run_id="", + thread_id="thread_id", + ) @parametrize async def test_method_update(self, async_client: AsyncOpenAI) -> None: - run = await async_client.beta.threads.runs.update( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + run = await async_client.beta.threads.runs.update( + run_id="run_id", + thread_id="thread_id", + ) + assert_matches_type(Run, run, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: - run = await async_client.beta.threads.runs.update( - "string", - thread_id="string", - metadata={}, - ) + with pytest.warns(DeprecationWarning): + run = await async_client.beta.threads.runs.update( + run_id="run_id", + thread_id="thread_id", + metadata={"foo": "string"}, + ) + assert_matches_type(Run, run, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.runs.with_raw_response.update( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.runs.with_raw_response.update( + run_id="run_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1098,55 +842,62 @@ async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.runs.with_streaming_response.update( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.runs.with_streaming_response.update( + run_id="run_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - run = await response.parse() - assert_matches_type(Run, run, path=["response"]) + run = await response.parse() + assert_matches_type(Run, run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.update( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.update( + run_id="run_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.update( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.update( + run_id="", + thread_id="thread_id", + ) @parametrize async def test_method_list(self, async_client: AsyncOpenAI) -> None: - run = await async_client.beta.threads.runs.list( - "string", - ) + with pytest.warns(DeprecationWarning): + run = await async_client.beta.threads.runs.list( + thread_id="thread_id", + ) + assert_matches_type(AsyncCursorPage[Run], run, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: - run = await async_client.beta.threads.runs.list( - "string", - after="string", - before="string", - limit=0, - order="asc", - ) + with pytest.warns(DeprecationWarning): + run = await async_client.beta.threads.runs.list( + thread_id="thread_id", + after="after", + before="before", + limit=0, + order="asc", + ) + assert_matches_type(AsyncCursorPage[Run], run, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.runs.with_raw_response.list( - "string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.runs.with_raw_response.list( + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1155,38 +906,43 @@ async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.runs.with_streaming_response.list( - "string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.runs.with_streaming_response.list( + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - run = await response.parse() - assert_matches_type(AsyncCursorPage[Run], run, path=["response"]) + run = await response.parse() + assert_matches_type(AsyncCursorPage[Run], run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.list( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.list( + thread_id="", + ) @parametrize async def test_method_cancel(self, async_client: AsyncOpenAI) -> None: - run = await async_client.beta.threads.runs.cancel( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + run = await async_client.beta.threads.runs.cancel( + run_id="run_id", + thread_id="thread_id", + ) + assert_matches_type(Run, run, path=["response"]) @parametrize async def test_raw_response_cancel(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.runs.with_raw_response.cancel( - "string", - thread_id="string", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.runs.with_raw_response.cancel( + run_id="run_id", + thread_id="thread_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1195,71 +951,70 @@ async def test_raw_response_cancel(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_cancel(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.runs.with_streaming_response.cancel( - "string", - thread_id="string", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.runs.with_streaming_response.cancel( + run_id="run_id", + thread_id="thread_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - run = await response.parse() - assert_matches_type(Run, run, path=["response"]) + run = await response.parse() + assert_matches_type(Run, run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_cancel(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.cancel( - "string", - thread_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.cancel( + run_id="run_id", + thread_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.cancel( - "", - thread_id="string", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.cancel( + run_id="", + thread_id="thread_id", + ) @parametrize async def test_method_submit_tool_outputs_overload_1(self, async_client: AsyncOpenAI) -> None: - run = await async_client.beta.threads.runs.submit_tool_outputs( - "string", - thread_id="string", - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + run = await async_client.beta.threads.runs.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + tool_outputs=[{}], + ) + assert_matches_type(Run, run, path=["response"]) @parametrize async def test_method_submit_tool_outputs_with_all_params_overload_1(self, async_client: AsyncOpenAI) -> None: - run = await async_client.beta.threads.runs.submit_tool_outputs( - "string", - thread_id="string", - tool_outputs=[ - { - "output": "output", - "tool_call_id": "tool_call_id", - }, - { - "output": "output", - "tool_call_id": "tool_call_id", - }, - { - "output": "output", - "tool_call_id": "tool_call_id", - }, - ], - stream=False, - ) + with pytest.warns(DeprecationWarning): + run = await async_client.beta.threads.runs.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + tool_outputs=[ + { + "output": "output", + "tool_call_id": "tool_call_id", + } + ], + stream=False, + ) + assert_matches_type(Run, run, path=["response"]) @parametrize async def test_raw_response_submit_tool_outputs_overload_1(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "string", - thread_id="string", - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + tool_outputs=[{}], + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1268,53 +1023,58 @@ async def test_raw_response_submit_tool_outputs_overload_1(self, async_client: A @parametrize async def test_streaming_response_submit_tool_outputs_overload_1(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.runs.with_streaming_response.submit_tool_outputs( - "string", - thread_id="string", - tool_outputs=[{}, {}, {}], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - run = await response.parse() - assert_matches_type(Run, run, path=["response"]) + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.runs.with_streaming_response.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + tool_outputs=[{}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(Run, run, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_submit_tool_outputs_overload_1(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "string", - thread_id="", - tool_outputs=[{}, {}, {}], - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "", - thread_id="string", - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="run_id", + thread_id="", + tool_outputs=[{}], + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="", + thread_id="thread_id", + tool_outputs=[{}], + ) @parametrize async def test_method_submit_tool_outputs_overload_2(self, async_client: AsyncOpenAI) -> None: - run_stream = await async_client.beta.threads.runs.submit_tool_outputs( - "string", - thread_id="string", - stream=True, - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + run_stream = await async_client.beta.threads.runs.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + stream=True, + tool_outputs=[{}], + ) + await run_stream.response.aclose() @parametrize async def test_raw_response_submit_tool_outputs_overload_2(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "string", - thread_id="string", - stream=True, - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + response = await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + stream=True, + tool_outputs=[{}], + ) assert response.http_request.headers.get("X-Stainless-Lang") == "python" stream = response.parse() @@ -1322,34 +1082,36 @@ async def test_raw_response_submit_tool_outputs_overload_2(self, async_client: A @parametrize async def test_streaming_response_submit_tool_outputs_overload_2(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.threads.runs.with_streaming_response.submit_tool_outputs( - "string", - thread_id="string", - stream=True, - tool_outputs=[{}, {}, {}], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - stream = await response.parse() - await stream.close() + with pytest.warns(DeprecationWarning): + async with async_client.beta.threads.runs.with_streaming_response.submit_tool_outputs( + run_id="run_id", + thread_id="thread_id", + stream=True, + tool_outputs=[{}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_submit_tool_outputs_overload_2(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "string", - thread_id="", - stream=True, - tool_outputs=[{}, {}, {}], - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): - await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( - "", - thread_id="string", - stream=True, - tool_outputs=[{}, {}, {}], - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `thread_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="run_id", + thread_id="", + stream=True, + tool_outputs=[{}], + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.beta.threads.runs.with_raw_response.submit_tool_outputs( + run_id="", + thread_id="thread_id", + stream=True, + tool_outputs=[{}], + ) diff --git a/tests/api_resources/chat/completions/__init__.py b/tests/api_resources/chat/completions/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/chat/completions/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/chat/completions/test_messages.py b/tests/api_resources/chat/completions/test_messages.py new file mode 100644 index 0000000000..4a4267e539 --- /dev/null +++ b/tests/api_resources/chat/completions/test_messages.py @@ -0,0 +1,121 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncCursorPage, AsyncCursorPage +from openai.types.chat import ChatCompletionStoreMessage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestMessages: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + message = client.chat.completions.messages.list( + completion_id="completion_id", + ) + assert_matches_type(SyncCursorPage[ChatCompletionStoreMessage], message, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + message = client.chat.completions.messages.list( + completion_id="completion_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncCursorPage[ChatCompletionStoreMessage], message, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.chat.completions.messages.with_raw_response.list( + completion_id="completion_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + message = response.parse() + assert_matches_type(SyncCursorPage[ChatCompletionStoreMessage], message, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.chat.completions.messages.with_streaming_response.list( + completion_id="completion_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = response.parse() + assert_matches_type(SyncCursorPage[ChatCompletionStoreMessage], message, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `completion_id` but received ''"): + client.chat.completions.messages.with_raw_response.list( + completion_id="", + ) + + +class TestAsyncMessages: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + message = await async_client.chat.completions.messages.list( + completion_id="completion_id", + ) + assert_matches_type(AsyncCursorPage[ChatCompletionStoreMessage], message, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + message = await async_client.chat.completions.messages.list( + completion_id="completion_id", + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncCursorPage[ChatCompletionStoreMessage], message, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.chat.completions.messages.with_raw_response.list( + completion_id="completion_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + message = response.parse() + assert_matches_type(AsyncCursorPage[ChatCompletionStoreMessage], message, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.chat.completions.messages.with_streaming_response.list( + completion_id="completion_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = await response.parse() + assert_matches_type(AsyncCursorPage[ChatCompletionStoreMessage], message, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `completion_id` but received ''"): + await async_client.chat.completions.messages.with_raw_response.list( + completion_id="", + ) diff --git a/tests/api_resources/chat/test_completions.py b/tests/api_resources/chat/test_completions.py index c44703a434..3cba000ae6 100644 --- a/tests/api_resources/chat/test_completions.py +++ b/tests/api_resources/chat/test_completions.py @@ -10,8 +10,10 @@ from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type +from openai.pagination import SyncCursorPage, AsyncCursorPage from openai.types.chat import ( ChatCompletion, + ChatCompletionDeleted, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -26,10 +28,10 @@ def test_method_create_overload_1(self, client: OpenAI) -> None: messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", ) assert_matches_type(ChatCompletion, completion, path=["response"]) @@ -39,11 +41,15 @@ def test_method_create_with_all_params_overload_1(self, client: OpenAI) -> None: messages=[ { "content": "string", - "role": "system", - "name": "string", + "role": "developer", + "name": "name", } ], - model="gpt-4o", + model="gpt-5.4", + audio={ + "format": "wav", + "voice": "alloy", + }, frequency_penalty=-2, function_call="none", functions=[ @@ -57,15 +63,30 @@ def test_method_create_with_all_params_overload_1(self, client: OpenAI) -> None: logprobs=True, max_completion_tokens=0, max_tokens=0, + metadata={"foo": "string"}, + modalities=["text"], + moderation={"model": "model"}, n=1, parallel_tool_calls=True, + prediction={ + "content": "string", + "type": "content", + }, presence_penalty=-2, + prompt_cache_key="prompt-cache-key-1234", + prompt_cache_retention="in_memory", + reasoning_effort="none", response_format={"type": "text"}, + safety_identifier="safety-identifier-1234", seed=-9007199254740991, service_tier="auto", - stop="string", + stop="\n", + store=True, stream=False, - stream_options={"include_usage": True}, + stream_options={ + "include_obfuscation": True, + "include_usage": True, + }, temperature=1, tool_choice="none", tools=[ @@ -77,29 +98,24 @@ def test_method_create_with_all_params_overload_1(self, client: OpenAI) -> None: "strict": True, }, "type": "function", - }, - { - "function": { - "name": "name", - "description": "description", - "parameters": {"foo": "bar"}, - "strict": True, - }, - "type": "function", - }, - { - "function": { - "name": "name", - "description": "description", - "parameters": {"foo": "bar"}, - "strict": True, - }, - "type": "function", - }, + } ], top_logprobs=0, top_p=1, user="user-1234", + verbosity="low", + web_search_options={ + "search_context_size": "low", + "user_location": { + "approximate": { + "city": "city", + "country": "country", + "region": "region", + "timezone": "timezone", + }, + "type": "approximate", + }, + }, ) assert_matches_type(ChatCompletion, completion, path=["response"]) @@ -109,10 +125,10 @@ def test_raw_response_create_overload_1(self, client: OpenAI) -> None: messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", ) assert response.is_closed is True @@ -126,10 +142,10 @@ def test_streaming_response_create_overload_1(self, client: OpenAI) -> None: messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -145,10 +161,10 @@ def test_method_create_overload_2(self, client: OpenAI) -> None: messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", stream=True, ) completion_stream.response.close() @@ -159,12 +175,16 @@ def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: messages=[ { "content": "string", - "role": "system", - "name": "string", + "role": "developer", + "name": "name", } ], - model="gpt-4o", + model="gpt-5.4", stream=True, + audio={ + "format": "wav", + "voice": "alloy", + }, frequency_penalty=-2, function_call="none", functions=[ @@ -178,14 +198,29 @@ def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: logprobs=True, max_completion_tokens=0, max_tokens=0, + metadata={"foo": "string"}, + modalities=["text"], + moderation={"model": "model"}, n=1, parallel_tool_calls=True, + prediction={ + "content": "string", + "type": "content", + }, presence_penalty=-2, + prompt_cache_key="prompt-cache-key-1234", + prompt_cache_retention="in_memory", + reasoning_effort="none", response_format={"type": "text"}, + safety_identifier="safety-identifier-1234", seed=-9007199254740991, service_tier="auto", - stop="string", - stream_options={"include_usage": True}, + stop="\n", + store=True, + stream_options={ + "include_obfuscation": True, + "include_usage": True, + }, temperature=1, tool_choice="none", tools=[ @@ -197,29 +232,24 @@ def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: "strict": True, }, "type": "function", - }, - { - "function": { - "name": "name", - "description": "description", - "parameters": {"foo": "bar"}, - "strict": True, - }, - "type": "function", - }, - { - "function": { - "name": "name", - "description": "description", - "parameters": {"foo": "bar"}, - "strict": True, - }, - "type": "function", - }, + } ], top_logprobs=0, top_p=1, user="user-1234", + verbosity="low", + web_search_options={ + "search_context_size": "low", + "user_location": { + "approximate": { + "city": "city", + "country": "country", + "region": "region", + "timezone": "timezone", + }, + "type": "approximate", + }, + }, ) completion_stream.response.close() @@ -229,10 +259,10 @@ def test_raw_response_create_overload_2(self, client: OpenAI) -> None: messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", stream=True, ) @@ -246,10 +276,10 @@ def test_streaming_response_create_overload_2(self, client: OpenAI) -> None: messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", stream=True, ) as response: assert not response.is_closed @@ -260,6 +290,160 @@ def test_streaming_response_create_overload_2(self, client: OpenAI) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + completion = client.chat.completions.retrieve( + "completion_id", + ) + assert_matches_type(ChatCompletion, completion, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.chat.completions.with_raw_response.retrieve( + "completion_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + completion = response.parse() + assert_matches_type(ChatCompletion, completion, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.chat.completions.with_streaming_response.retrieve( + "completion_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = response.parse() + assert_matches_type(ChatCompletion, completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `completion_id` but received ''"): + client.chat.completions.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + completion = client.chat.completions.update( + completion_id="completion_id", + metadata={"foo": "string"}, + ) + assert_matches_type(ChatCompletion, completion, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.chat.completions.with_raw_response.update( + completion_id="completion_id", + metadata={"foo": "string"}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + completion = response.parse() + assert_matches_type(ChatCompletion, completion, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.chat.completions.with_streaming_response.update( + completion_id="completion_id", + metadata={"foo": "string"}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = response.parse() + assert_matches_type(ChatCompletion, completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `completion_id` but received ''"): + client.chat.completions.with_raw_response.update( + completion_id="", + metadata={"foo": "string"}, + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + completion = client.chat.completions.list() + assert_matches_type(SyncCursorPage[ChatCompletion], completion, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + completion = client.chat.completions.list( + after="after", + limit=0, + metadata={"foo": "string"}, + model="model", + order="asc", + ) + assert_matches_type(SyncCursorPage[ChatCompletion], completion, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.chat.completions.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + completion = response.parse() + assert_matches_type(SyncCursorPage[ChatCompletion], completion, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.chat.completions.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = response.parse() + assert_matches_type(SyncCursorPage[ChatCompletion], completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + completion = client.chat.completions.delete( + "completion_id", + ) + assert_matches_type(ChatCompletionDeleted, completion, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.chat.completions.with_raw_response.delete( + "completion_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + completion = response.parse() + assert_matches_type(ChatCompletionDeleted, completion, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.chat.completions.with_streaming_response.delete( + "completion_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = response.parse() + assert_matches_type(ChatCompletionDeleted, completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `completion_id` but received ''"): + client.chat.completions.with_raw_response.delete( + "", + ) + @parametrize def test_method_create_disallows_pydantic(self, client: OpenAI) -> None: class MyModel(pydantic.BaseModel): @@ -279,7 +463,9 @@ class MyModel(pydantic.BaseModel): class TestAsyncCompletions: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None: @@ -287,10 +473,10 @@ async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", ) assert_matches_type(ChatCompletion, completion, path=["response"]) @@ -300,11 +486,15 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn messages=[ { "content": "string", - "role": "system", - "name": "string", + "role": "developer", + "name": "name", } ], - model="gpt-4o", + model="gpt-5.4", + audio={ + "format": "wav", + "voice": "alloy", + }, frequency_penalty=-2, function_call="none", functions=[ @@ -318,15 +508,30 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn logprobs=True, max_completion_tokens=0, max_tokens=0, + metadata={"foo": "string"}, + modalities=["text"], + moderation={"model": "model"}, n=1, parallel_tool_calls=True, + prediction={ + "content": "string", + "type": "content", + }, presence_penalty=-2, + prompt_cache_key="prompt-cache-key-1234", + prompt_cache_retention="in_memory", + reasoning_effort="none", response_format={"type": "text"}, + safety_identifier="safety-identifier-1234", seed=-9007199254740991, service_tier="auto", - stop="string", + stop="\n", + store=True, stream=False, - stream_options={"include_usage": True}, + stream_options={ + "include_obfuscation": True, + "include_usage": True, + }, temperature=1, tool_choice="none", tools=[ @@ -338,29 +543,24 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn "strict": True, }, "type": "function", - }, - { - "function": { - "name": "name", - "description": "description", - "parameters": {"foo": "bar"}, - "strict": True, - }, - "type": "function", - }, - { - "function": { - "name": "name", - "description": "description", - "parameters": {"foo": "bar"}, - "strict": True, - }, - "type": "function", - }, + } ], top_logprobs=0, top_p=1, user="user-1234", + verbosity="low", + web_search_options={ + "search_context_size": "low", + "user_location": { + "approximate": { + "city": "city", + "country": "country", + "region": "region", + "timezone": "timezone", + }, + "type": "approximate", + }, + }, ) assert_matches_type(ChatCompletion, completion, path=["response"]) @@ -370,10 +570,10 @@ async def test_raw_response_create_overload_1(self, async_client: AsyncOpenAI) - messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", ) assert response.is_closed is True @@ -387,10 +587,10 @@ async def test_streaming_response_create_overload_1(self, async_client: AsyncOpe messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -406,10 +606,10 @@ async def test_method_create_overload_2(self, async_client: AsyncOpenAI) -> None messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", stream=True, ) await completion_stream.response.aclose() @@ -420,12 +620,16 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn messages=[ { "content": "string", - "role": "system", - "name": "string", + "role": "developer", + "name": "name", } ], - model="gpt-4o", + model="gpt-5.4", stream=True, + audio={ + "format": "wav", + "voice": "alloy", + }, frequency_penalty=-2, function_call="none", functions=[ @@ -439,14 +643,29 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn logprobs=True, max_completion_tokens=0, max_tokens=0, + metadata={"foo": "string"}, + modalities=["text"], + moderation={"model": "model"}, n=1, parallel_tool_calls=True, + prediction={ + "content": "string", + "type": "content", + }, presence_penalty=-2, + prompt_cache_key="prompt-cache-key-1234", + prompt_cache_retention="in_memory", + reasoning_effort="none", response_format={"type": "text"}, + safety_identifier="safety-identifier-1234", seed=-9007199254740991, service_tier="auto", - stop="string", - stream_options={"include_usage": True}, + stop="\n", + store=True, + stream_options={ + "include_obfuscation": True, + "include_usage": True, + }, temperature=1, tool_choice="none", tools=[ @@ -458,29 +677,24 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn "strict": True, }, "type": "function", - }, - { - "function": { - "name": "name", - "description": "description", - "parameters": {"foo": "bar"}, - "strict": True, - }, - "type": "function", - }, - { - "function": { - "name": "name", - "description": "description", - "parameters": {"foo": "bar"}, - "strict": True, - }, - "type": "function", - }, + } ], top_logprobs=0, top_p=1, user="user-1234", + verbosity="low", + web_search_options={ + "search_context_size": "low", + "user_location": { + "approximate": { + "city": "city", + "country": "country", + "region": "region", + "timezone": "timezone", + }, + "type": "approximate", + }, + }, ) await completion_stream.response.aclose() @@ -490,10 +704,10 @@ async def test_raw_response_create_overload_2(self, async_client: AsyncOpenAI) - messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", stream=True, ) @@ -507,10 +721,10 @@ async def test_streaming_response_create_overload_2(self, async_client: AsyncOpe messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", stream=True, ) as response: assert not response.is_closed @@ -521,6 +735,160 @@ async def test_streaming_response_create_overload_2(self, async_client: AsyncOpe assert cast(Any, response.is_closed) is True + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + completion = await async_client.chat.completions.retrieve( + "completion_id", + ) + assert_matches_type(ChatCompletion, completion, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.chat.completions.with_raw_response.retrieve( + "completion_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + completion = response.parse() + assert_matches_type(ChatCompletion, completion, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.chat.completions.with_streaming_response.retrieve( + "completion_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = await response.parse() + assert_matches_type(ChatCompletion, completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `completion_id` but received ''"): + await async_client.chat.completions.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + completion = await async_client.chat.completions.update( + completion_id="completion_id", + metadata={"foo": "string"}, + ) + assert_matches_type(ChatCompletion, completion, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.chat.completions.with_raw_response.update( + completion_id="completion_id", + metadata={"foo": "string"}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + completion = response.parse() + assert_matches_type(ChatCompletion, completion, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.chat.completions.with_streaming_response.update( + completion_id="completion_id", + metadata={"foo": "string"}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = await response.parse() + assert_matches_type(ChatCompletion, completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `completion_id` but received ''"): + await async_client.chat.completions.with_raw_response.update( + completion_id="", + metadata={"foo": "string"}, + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + completion = await async_client.chat.completions.list() + assert_matches_type(AsyncCursorPage[ChatCompletion], completion, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + completion = await async_client.chat.completions.list( + after="after", + limit=0, + metadata={"foo": "string"}, + model="model", + order="asc", + ) + assert_matches_type(AsyncCursorPage[ChatCompletion], completion, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.chat.completions.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + completion = response.parse() + assert_matches_type(AsyncCursorPage[ChatCompletion], completion, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.chat.completions.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = await response.parse() + assert_matches_type(AsyncCursorPage[ChatCompletion], completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + completion = await async_client.chat.completions.delete( + "completion_id", + ) + assert_matches_type(ChatCompletionDeleted, completion, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.chat.completions.with_raw_response.delete( + "completion_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + completion = response.parse() + assert_matches_type(ChatCompletionDeleted, completion, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.chat.completions.with_streaming_response.delete( + "completion_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = await response.parse() + assert_matches_type(ChatCompletionDeleted, completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `completion_id` but received ''"): + await async_client.chat.completions.with_raw_response.delete( + "", + ) + @parametrize async def test_method_create_disallows_pydantic(self, async_client: AsyncOpenAI) -> None: class MyModel(pydantic.BaseModel): diff --git a/tests/api_resources/containers/__init__.py b/tests/api_resources/containers/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/containers/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/containers/files/__init__.py b/tests/api_resources/containers/files/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/containers/files/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/containers/files/test_content.py b/tests/api_resources/containers/files/test_content.py new file mode 100644 index 0000000000..67fcdca36c --- /dev/null +++ b/tests/api_resources/containers/files/test_content.py @@ -0,0 +1,154 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import httpx +import pytest +from respx import MockRouter + +import openai._legacy_response as _legacy_response +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type + +# pyright: reportDeprecated=false + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestContent: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + content = client.containers.files.content.retrieve( + file_id="file_id", + container_id="container_id", + ) + assert isinstance(content, _legacy_response.HttpxBinaryResponseContent) + assert content.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + response = client.containers.files.content.with_raw_response.retrieve( + file_id="file_id", + container_id="container_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + content = response.parse() + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + with client.containers.files.content.with_streaming_response.retrieve( + file_id="file_id", + container_id="container_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + content = response.parse() + assert_matches_type(bytes, content, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + client.containers.files.content.with_raw_response.retrieve( + file_id="file_id", + container_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.containers.files.content.with_raw_response.retrieve( + file_id="", + container_id="container_id", + ) + + +class TestAsyncContent: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + content = await async_client.containers.files.content.retrieve( + file_id="file_id", + container_id="container_id", + ) + assert isinstance(content, _legacy_response.HttpxBinaryResponseContent) + assert content.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + response = await async_client.containers.files.content.with_raw_response.retrieve( + file_id="file_id", + container_id="container_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + content = response.parse() + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/containers/container_id/files/file_id/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + async with async_client.containers.files.content.with_streaming_response.retrieve( + file_id="file_id", + container_id="container_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + content = await response.parse() + assert_matches_type(bytes, content, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + await async_client.containers.files.content.with_raw_response.retrieve( + file_id="file_id", + container_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.containers.files.content.with_raw_response.retrieve( + file_id="", + container_id="container_id", + ) diff --git a/tests/api_resources/beta/vector_stores/test_files.py b/tests/api_resources/containers/test_files.py similarity index 51% rename from tests/api_resources/beta/vector_stores/test_files.py rename to tests/api_resources/containers/test_files.py index 36622e699b..9d47785894 100644 --- a/tests/api_resources/beta/vector_stores/test_files.py +++ b/tests/api_resources/containers/test_files.py @@ -10,9 +10,10 @@ from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type from openai.pagination import SyncCursorPage, AsyncCursorPage -from openai.types.beta.vector_stores import ( - VectorStoreFile, - VectorStoreFileDeleted, +from openai.types.containers import ( + FileListResponse, + FileCreateResponse, + FileRetrieveResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -23,398 +24,388 @@ class TestFiles: @parametrize def test_method_create(self, client: OpenAI) -> None: - file = client.beta.vector_stores.files.create( - "vs_abc123", - file_id="string", + file = client.containers.files.create( + container_id="container_id", ) - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileCreateResponse, file, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: OpenAI) -> None: - file = client.beta.vector_stores.files.create( - "vs_abc123", - file_id="string", - chunking_strategy={"type": "auto"}, + file = client.containers.files.create( + container_id="container_id", + file=b"Example data", + file_id="file_id", ) - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileCreateResponse, file, path=["response"]) @parametrize def test_raw_response_create(self, client: OpenAI) -> None: - response = client.beta.vector_stores.files.with_raw_response.create( - "vs_abc123", - file_id="string", + response = client.containers.files.with_raw_response.create( + container_id="container_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileCreateResponse, file, path=["response"]) @parametrize def test_streaming_response_create(self, client: OpenAI) -> None: - with client.beta.vector_stores.files.with_streaming_response.create( - "vs_abc123", - file_id="string", + with client.containers.files.with_streaming_response.create( + container_id="container_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileCreateResponse, file, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_create(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.files.with_raw_response.create( - "", - file_id="string", + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + client.containers.files.with_raw_response.create( + container_id="", ) @parametrize def test_method_retrieve(self, client: OpenAI) -> None: - file = client.beta.vector_stores.files.retrieve( - "file-abc123", - vector_store_id="vs_abc123", + file = client.containers.files.retrieve( + file_id="file_id", + container_id="container_id", ) - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileRetrieveResponse, file, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: OpenAI) -> None: - response = client.beta.vector_stores.files.with_raw_response.retrieve( - "file-abc123", - vector_store_id="vs_abc123", + response = client.containers.files.with_raw_response.retrieve( + file_id="file_id", + container_id="container_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileRetrieveResponse, file, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: OpenAI) -> None: - with client.beta.vector_stores.files.with_streaming_response.retrieve( - "file-abc123", - vector_store_id="vs_abc123", + with client.containers.files.with_streaming_response.retrieve( + file_id="file_id", + container_id="container_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileRetrieveResponse, file, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_retrieve(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.files.with_raw_response.retrieve( - "file-abc123", - vector_store_id="", + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + client.containers.files.with_raw_response.retrieve( + file_id="file_id", + container_id="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): - client.beta.vector_stores.files.with_raw_response.retrieve( - "", - vector_store_id="vs_abc123", + client.containers.files.with_raw_response.retrieve( + file_id="", + container_id="container_id", ) @parametrize def test_method_list(self, client: OpenAI) -> None: - file = client.beta.vector_stores.files.list( - "string", + file = client.containers.files.list( + container_id="container_id", ) - assert_matches_type(SyncCursorPage[VectorStoreFile], file, path=["response"]) + assert_matches_type(SyncCursorPage[FileListResponse], file, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: OpenAI) -> None: - file = client.beta.vector_stores.files.list( - "string", - after="string", - before="string", - filter="in_progress", + file = client.containers.files.list( + container_id="container_id", + after="after", limit=0, order="asc", ) - assert_matches_type(SyncCursorPage[VectorStoreFile], file, path=["response"]) + assert_matches_type(SyncCursorPage[FileListResponse], file, path=["response"]) @parametrize def test_raw_response_list(self, client: OpenAI) -> None: - response = client.beta.vector_stores.files.with_raw_response.list( - "string", + response = client.containers.files.with_raw_response.list( + container_id="container_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(SyncCursorPage[VectorStoreFile], file, path=["response"]) + assert_matches_type(SyncCursorPage[FileListResponse], file, path=["response"]) @parametrize def test_streaming_response_list(self, client: OpenAI) -> None: - with client.beta.vector_stores.files.with_streaming_response.list( - "string", + with client.containers.files.with_streaming_response.list( + container_id="container_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(SyncCursorPage[VectorStoreFile], file, path=["response"]) + assert_matches_type(SyncCursorPage[FileListResponse], file, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_list(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.files.with_raw_response.list( - "", + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + client.containers.files.with_raw_response.list( + container_id="", ) @parametrize def test_method_delete(self, client: OpenAI) -> None: - file = client.beta.vector_stores.files.delete( - "string", - vector_store_id="string", + file = client.containers.files.delete( + file_id="file_id", + container_id="container_id", ) - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + assert file is None @parametrize def test_raw_response_delete(self, client: OpenAI) -> None: - response = client.beta.vector_stores.files.with_raw_response.delete( - "string", - vector_store_id="string", + response = client.containers.files.with_raw_response.delete( + file_id="file_id", + container_id="container_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + assert file is None @parametrize def test_streaming_response_delete(self, client: OpenAI) -> None: - with client.beta.vector_stores.files.with_streaming_response.delete( - "string", - vector_store_id="string", + with client.containers.files.with_streaming_response.delete( + file_id="file_id", + container_id="container_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + assert file is None assert cast(Any, response.is_closed) is True @parametrize def test_path_params_delete(self, client: OpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.files.with_raw_response.delete( - "string", - vector_store_id="", + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + client.containers.files.with_raw_response.delete( + file_id="file_id", + container_id="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): - client.beta.vector_stores.files.with_raw_response.delete( - "", - vector_store_id="string", + client.containers.files.with_raw_response.delete( + file_id="", + container_id="container_id", ) class TestAsyncFiles: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: - file = await async_client.beta.vector_stores.files.create( - "vs_abc123", - file_id="string", + file = await async_client.containers.files.create( + container_id="container_id", ) - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileCreateResponse, file, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: - file = await async_client.beta.vector_stores.files.create( - "vs_abc123", - file_id="string", - chunking_strategy={"type": "auto"}, + file = await async_client.containers.files.create( + container_id="container_id", + file=b"Example data", + file_id="file_id", ) - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileCreateResponse, file, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.files.with_raw_response.create( - "vs_abc123", - file_id="string", + response = await async_client.containers.files.with_raw_response.create( + container_id="container_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileCreateResponse, file, path=["response"]) @parametrize async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.files.with_streaming_response.create( - "vs_abc123", - file_id="string", + async with async_client.containers.files.with_streaming_response.create( + container_id="container_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = await response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileCreateResponse, file, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.files.with_raw_response.create( - "", - file_id="string", + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + await async_client.containers.files.with_raw_response.create( + container_id="", ) @parametrize async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: - file = await async_client.beta.vector_stores.files.retrieve( - "file-abc123", - vector_store_id="vs_abc123", + file = await async_client.containers.files.retrieve( + file_id="file_id", + container_id="container_id", ) - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileRetrieveResponse, file, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.files.with_raw_response.retrieve( - "file-abc123", - vector_store_id="vs_abc123", + response = await async_client.containers.files.with_raw_response.retrieve( + file_id="file_id", + container_id="container_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileRetrieveResponse, file, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.files.with_streaming_response.retrieve( - "file-abc123", - vector_store_id="vs_abc123", + async with async_client.containers.files.with_streaming_response.retrieve( + file_id="file_id", + container_id="container_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = await response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) + assert_matches_type(FileRetrieveResponse, file, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.files.with_raw_response.retrieve( - "file-abc123", - vector_store_id="", + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + await async_client.containers.files.with_raw_response.retrieve( + file_id="file_id", + container_id="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): - await async_client.beta.vector_stores.files.with_raw_response.retrieve( - "", - vector_store_id="vs_abc123", + await async_client.containers.files.with_raw_response.retrieve( + file_id="", + container_id="container_id", ) @parametrize async def test_method_list(self, async_client: AsyncOpenAI) -> None: - file = await async_client.beta.vector_stores.files.list( - "string", + file = await async_client.containers.files.list( + container_id="container_id", ) - assert_matches_type(AsyncCursorPage[VectorStoreFile], file, path=["response"]) + assert_matches_type(AsyncCursorPage[FileListResponse], file, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: - file = await async_client.beta.vector_stores.files.list( - "string", - after="string", - before="string", - filter="in_progress", + file = await async_client.containers.files.list( + container_id="container_id", + after="after", limit=0, order="asc", ) - assert_matches_type(AsyncCursorPage[VectorStoreFile], file, path=["response"]) + assert_matches_type(AsyncCursorPage[FileListResponse], file, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.files.with_raw_response.list( - "string", + response = await async_client.containers.files.with_raw_response.list( + container_id="container_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(AsyncCursorPage[VectorStoreFile], file, path=["response"]) + assert_matches_type(AsyncCursorPage[FileListResponse], file, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.files.with_streaming_response.list( - "string", + async with async_client.containers.files.with_streaming_response.list( + container_id="container_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = await response.parse() - assert_matches_type(AsyncCursorPage[VectorStoreFile], file, path=["response"]) + assert_matches_type(AsyncCursorPage[FileListResponse], file, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.files.with_raw_response.list( - "", + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + await async_client.containers.files.with_raw_response.list( + container_id="", ) @parametrize async def test_method_delete(self, async_client: AsyncOpenAI) -> None: - file = await async_client.beta.vector_stores.files.delete( - "string", - vector_store_id="string", + file = await async_client.containers.files.delete( + file_id="file_id", + container_id="container_id", ) - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + assert file is None @parametrize async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.files.with_raw_response.delete( - "string", - vector_store_id="string", + response = await async_client.containers.files.with_raw_response.delete( + file_id="file_id", + container_id="container_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + assert file is None @parametrize async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.files.with_streaming_response.delete( - "string", - vector_store_id="string", + async with async_client.containers.files.with_streaming_response.delete( + file_id="file_id", + container_id="container_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = await response.parse() - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + assert file is None assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.files.with_raw_response.delete( - "string", - vector_store_id="", + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + await async_client.containers.files.with_raw_response.delete( + file_id="file_id", + container_id="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): - await async_client.beta.vector_stores.files.with_raw_response.delete( - "", - vector_store_id="string", + await async_client.containers.files.with_raw_response.delete( + file_id="", + container_id="container_id", ) diff --git a/tests/api_resources/conversations/__init__.py b/tests/api_resources/conversations/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/conversations/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/conversations/test_items.py b/tests/api_resources/conversations/test_items.py new file mode 100644 index 0000000000..aca9568410 --- /dev/null +++ b/tests/api_resources/conversations/test_items.py @@ -0,0 +1,501 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.conversations import ( + Conversation, + ConversationItem, + ConversationItemList, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestItems: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + item = client.conversations.items.create( + conversation_id="conv_123", + items=[ + { + "content": "string", + "role": "user", + "type": "message", + } + ], + ) + assert_matches_type(ConversationItemList, item, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + item = client.conversations.items.create( + conversation_id="conv_123", + items=[ + { + "content": "string", + "role": "user", + "phase": "commentary", + "type": "message", + } + ], + include=["file_search_call.results"], + ) + assert_matches_type(ConversationItemList, item, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.conversations.items.with_raw_response.create( + conversation_id="conv_123", + items=[ + { + "content": "string", + "role": "user", + "type": "message", + } + ], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + item = response.parse() + assert_matches_type(ConversationItemList, item, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.conversations.items.with_streaming_response.create( + conversation_id="conv_123", + items=[ + { + "content": "string", + "role": "user", + "type": "message", + } + ], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + item = response.parse() + assert_matches_type(ConversationItemList, item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + client.conversations.items.with_raw_response.create( + conversation_id="", + items=[ + { + "content": "string", + "role": "user", + "type": "message", + } + ], + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + item = client.conversations.items.retrieve( + item_id="msg_abc", + conversation_id="conv_123", + ) + assert_matches_type(ConversationItem, item, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: OpenAI) -> None: + item = client.conversations.items.retrieve( + item_id="msg_abc", + conversation_id="conv_123", + include=["file_search_call.results"], + ) + assert_matches_type(ConversationItem, item, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.conversations.items.with_raw_response.retrieve( + item_id="msg_abc", + conversation_id="conv_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + item = response.parse() + assert_matches_type(ConversationItem, item, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.conversations.items.with_streaming_response.retrieve( + item_id="msg_abc", + conversation_id="conv_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + item = response.parse() + assert_matches_type(ConversationItem, item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + client.conversations.items.with_raw_response.retrieve( + item_id="msg_abc", + conversation_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `item_id` but received ''"): + client.conversations.items.with_raw_response.retrieve( + item_id="", + conversation_id="conv_123", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + item = client.conversations.items.list( + conversation_id="conv_123", + ) + assert_matches_type(SyncConversationCursorPage[ConversationItem], item, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + item = client.conversations.items.list( + conversation_id="conv_123", + after="after", + include=["file_search_call.results"], + limit=0, + order="asc", + ) + assert_matches_type(SyncConversationCursorPage[ConversationItem], item, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.conversations.items.with_raw_response.list( + conversation_id="conv_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + item = response.parse() + assert_matches_type(SyncConversationCursorPage[ConversationItem], item, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.conversations.items.with_streaming_response.list( + conversation_id="conv_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + item = response.parse() + assert_matches_type(SyncConversationCursorPage[ConversationItem], item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + client.conversations.items.with_raw_response.list( + conversation_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + item = client.conversations.items.delete( + item_id="msg_abc", + conversation_id="conv_123", + ) + assert_matches_type(Conversation, item, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.conversations.items.with_raw_response.delete( + item_id="msg_abc", + conversation_id="conv_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + item = response.parse() + assert_matches_type(Conversation, item, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.conversations.items.with_streaming_response.delete( + item_id="msg_abc", + conversation_id="conv_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + item = response.parse() + assert_matches_type(Conversation, item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + client.conversations.items.with_raw_response.delete( + item_id="msg_abc", + conversation_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `item_id` but received ''"): + client.conversations.items.with_raw_response.delete( + item_id="", + conversation_id="conv_123", + ) + + +class TestAsyncItems: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + item = await async_client.conversations.items.create( + conversation_id="conv_123", + items=[ + { + "content": "string", + "role": "user", + "type": "message", + } + ], + ) + assert_matches_type(ConversationItemList, item, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + item = await async_client.conversations.items.create( + conversation_id="conv_123", + items=[ + { + "content": "string", + "role": "user", + "phase": "commentary", + "type": "message", + } + ], + include=["file_search_call.results"], + ) + assert_matches_type(ConversationItemList, item, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.conversations.items.with_raw_response.create( + conversation_id="conv_123", + items=[ + { + "content": "string", + "role": "user", + "type": "message", + } + ], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + item = response.parse() + assert_matches_type(ConversationItemList, item, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.conversations.items.with_streaming_response.create( + conversation_id="conv_123", + items=[ + { + "content": "string", + "role": "user", + "type": "message", + } + ], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + item = await response.parse() + assert_matches_type(ConversationItemList, item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + await async_client.conversations.items.with_raw_response.create( + conversation_id="", + items=[ + { + "content": "string", + "role": "user", + "type": "message", + } + ], + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + item = await async_client.conversations.items.retrieve( + item_id="msg_abc", + conversation_id="conv_123", + ) + assert_matches_type(ConversationItem, item, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncOpenAI) -> None: + item = await async_client.conversations.items.retrieve( + item_id="msg_abc", + conversation_id="conv_123", + include=["file_search_call.results"], + ) + assert_matches_type(ConversationItem, item, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.conversations.items.with_raw_response.retrieve( + item_id="msg_abc", + conversation_id="conv_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + item = response.parse() + assert_matches_type(ConversationItem, item, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.conversations.items.with_streaming_response.retrieve( + item_id="msg_abc", + conversation_id="conv_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + item = await response.parse() + assert_matches_type(ConversationItem, item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + await async_client.conversations.items.with_raw_response.retrieve( + item_id="msg_abc", + conversation_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `item_id` but received ''"): + await async_client.conversations.items.with_raw_response.retrieve( + item_id="", + conversation_id="conv_123", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + item = await async_client.conversations.items.list( + conversation_id="conv_123", + ) + assert_matches_type(AsyncConversationCursorPage[ConversationItem], item, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + item = await async_client.conversations.items.list( + conversation_id="conv_123", + after="after", + include=["file_search_call.results"], + limit=0, + order="asc", + ) + assert_matches_type(AsyncConversationCursorPage[ConversationItem], item, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.conversations.items.with_raw_response.list( + conversation_id="conv_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + item = response.parse() + assert_matches_type(AsyncConversationCursorPage[ConversationItem], item, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.conversations.items.with_streaming_response.list( + conversation_id="conv_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + item = await response.parse() + assert_matches_type(AsyncConversationCursorPage[ConversationItem], item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + await async_client.conversations.items.with_raw_response.list( + conversation_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + item = await async_client.conversations.items.delete( + item_id="msg_abc", + conversation_id="conv_123", + ) + assert_matches_type(Conversation, item, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.conversations.items.with_raw_response.delete( + item_id="msg_abc", + conversation_id="conv_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + item = response.parse() + assert_matches_type(Conversation, item, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.conversations.items.with_streaming_response.delete( + item_id="msg_abc", + conversation_id="conv_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + item = await response.parse() + assert_matches_type(Conversation, item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + await async_client.conversations.items.with_raw_response.delete( + item_id="msg_abc", + conversation_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `item_id` but received ''"): + await async_client.conversations.items.with_raw_response.delete( + item_id="", + conversation_id="conv_123", + ) diff --git a/tests/api_resources/evals/__init__.py b/tests/api_resources/evals/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/evals/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/evals/runs/__init__.py b/tests/api_resources/evals/runs/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/evals/runs/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/evals/runs/test_output_items.py b/tests/api_resources/evals/runs/test_output_items.py new file mode 100644 index 0000000000..673867ac42 --- /dev/null +++ b/tests/api_resources/evals/runs/test_output_items.py @@ -0,0 +1,265 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncCursorPage, AsyncCursorPage +from openai.types.evals.runs import OutputItemListResponse, OutputItemRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestOutputItems: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + output_item = client.evals.runs.output_items.retrieve( + output_item_id="output_item_id", + eval_id="eval_id", + run_id="run_id", + ) + assert_matches_type(OutputItemRetrieveResponse, output_item, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.evals.runs.output_items.with_raw_response.retrieve( + output_item_id="output_item_id", + eval_id="eval_id", + run_id="run_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + output_item = response.parse() + assert_matches_type(OutputItemRetrieveResponse, output_item, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.evals.runs.output_items.with_streaming_response.retrieve( + output_item_id="output_item_id", + eval_id="eval_id", + run_id="run_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + output_item = response.parse() + assert_matches_type(OutputItemRetrieveResponse, output_item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + client.evals.runs.output_items.with_raw_response.retrieve( + output_item_id="output_item_id", + eval_id="", + run_id="run_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.evals.runs.output_items.with_raw_response.retrieve( + output_item_id="output_item_id", + eval_id="eval_id", + run_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `output_item_id` but received ''"): + client.evals.runs.output_items.with_raw_response.retrieve( + output_item_id="", + eval_id="eval_id", + run_id="run_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + output_item = client.evals.runs.output_items.list( + run_id="run_id", + eval_id="eval_id", + ) + assert_matches_type(SyncCursorPage[OutputItemListResponse], output_item, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + output_item = client.evals.runs.output_items.list( + run_id="run_id", + eval_id="eval_id", + after="after", + limit=0, + order="asc", + status="fail", + ) + assert_matches_type(SyncCursorPage[OutputItemListResponse], output_item, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.evals.runs.output_items.with_raw_response.list( + run_id="run_id", + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + output_item = response.parse() + assert_matches_type(SyncCursorPage[OutputItemListResponse], output_item, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.evals.runs.output_items.with_streaming_response.list( + run_id="run_id", + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + output_item = response.parse() + assert_matches_type(SyncCursorPage[OutputItemListResponse], output_item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + client.evals.runs.output_items.with_raw_response.list( + run_id="run_id", + eval_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.evals.runs.output_items.with_raw_response.list( + run_id="", + eval_id="eval_id", + ) + + +class TestAsyncOutputItems: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + output_item = await async_client.evals.runs.output_items.retrieve( + output_item_id="output_item_id", + eval_id="eval_id", + run_id="run_id", + ) + assert_matches_type(OutputItemRetrieveResponse, output_item, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.runs.output_items.with_raw_response.retrieve( + output_item_id="output_item_id", + eval_id="eval_id", + run_id="run_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + output_item = response.parse() + assert_matches_type(OutputItemRetrieveResponse, output_item, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.runs.output_items.with_streaming_response.retrieve( + output_item_id="output_item_id", + eval_id="eval_id", + run_id="run_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + output_item = await response.parse() + assert_matches_type(OutputItemRetrieveResponse, output_item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + await async_client.evals.runs.output_items.with_raw_response.retrieve( + output_item_id="output_item_id", + eval_id="", + run_id="run_id", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.evals.runs.output_items.with_raw_response.retrieve( + output_item_id="output_item_id", + eval_id="eval_id", + run_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `output_item_id` but received ''"): + await async_client.evals.runs.output_items.with_raw_response.retrieve( + output_item_id="", + eval_id="eval_id", + run_id="run_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + output_item = await async_client.evals.runs.output_items.list( + run_id="run_id", + eval_id="eval_id", + ) + assert_matches_type(AsyncCursorPage[OutputItemListResponse], output_item, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + output_item = await async_client.evals.runs.output_items.list( + run_id="run_id", + eval_id="eval_id", + after="after", + limit=0, + order="asc", + status="fail", + ) + assert_matches_type(AsyncCursorPage[OutputItemListResponse], output_item, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.runs.output_items.with_raw_response.list( + run_id="run_id", + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + output_item = response.parse() + assert_matches_type(AsyncCursorPage[OutputItemListResponse], output_item, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.runs.output_items.with_streaming_response.list( + run_id="run_id", + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + output_item = await response.parse() + assert_matches_type(AsyncCursorPage[OutputItemListResponse], output_item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + await async_client.evals.runs.output_items.with_raw_response.list( + run_id="run_id", + eval_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.evals.runs.output_items.with_raw_response.list( + run_id="", + eval_id="eval_id", + ) diff --git a/tests/api_resources/evals/test_runs.py b/tests/api_resources/evals/test_runs.py new file mode 100644 index 0000000000..1367cb4bab --- /dev/null +++ b/tests/api_resources/evals/test_runs.py @@ -0,0 +1,591 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncCursorPage, AsyncCursorPage +from openai.types.evals import ( + RunListResponse, + RunCancelResponse, + RunCreateResponse, + RunDeleteResponse, + RunRetrieveResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRuns: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + run = client.evals.runs.create( + eval_id="eval_id", + data_source={ + "source": { + "content": [{"item": {"foo": "bar"}}], + "type": "file_content", + }, + "type": "jsonl", + }, + ) + assert_matches_type(RunCreateResponse, run, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + run = client.evals.runs.create( + eval_id="eval_id", + data_source={ + "source": { + "content": [ + { + "item": {"foo": "bar"}, + "sample": {"foo": "bar"}, + } + ], + "type": "file_content", + }, + "type": "jsonl", + }, + metadata={"foo": "string"}, + name="name", + ) + assert_matches_type(RunCreateResponse, run, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.evals.runs.with_raw_response.create( + eval_id="eval_id", + data_source={ + "source": { + "content": [{"item": {"foo": "bar"}}], + "type": "file_content", + }, + "type": "jsonl", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(RunCreateResponse, run, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.evals.runs.with_streaming_response.create( + eval_id="eval_id", + data_source={ + "source": { + "content": [{"item": {"foo": "bar"}}], + "type": "file_content", + }, + "type": "jsonl", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(RunCreateResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + client.evals.runs.with_raw_response.create( + eval_id="", + data_source={ + "source": { + "content": [{"item": {"foo": "bar"}}], + "type": "file_content", + }, + "type": "jsonl", + }, + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + run = client.evals.runs.retrieve( + run_id="run_id", + eval_id="eval_id", + ) + assert_matches_type(RunRetrieveResponse, run, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.evals.runs.with_raw_response.retrieve( + run_id="run_id", + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(RunRetrieveResponse, run, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.evals.runs.with_streaming_response.retrieve( + run_id="run_id", + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(RunRetrieveResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + client.evals.runs.with_raw_response.retrieve( + run_id="run_id", + eval_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.evals.runs.with_raw_response.retrieve( + run_id="", + eval_id="eval_id", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + run = client.evals.runs.list( + eval_id="eval_id", + ) + assert_matches_type(SyncCursorPage[RunListResponse], run, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + run = client.evals.runs.list( + eval_id="eval_id", + after="after", + limit=0, + order="asc", + status="queued", + ) + assert_matches_type(SyncCursorPage[RunListResponse], run, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.evals.runs.with_raw_response.list( + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(SyncCursorPage[RunListResponse], run, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.evals.runs.with_streaming_response.list( + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(SyncCursorPage[RunListResponse], run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + client.evals.runs.with_raw_response.list( + eval_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + run = client.evals.runs.delete( + run_id="run_id", + eval_id="eval_id", + ) + assert_matches_type(RunDeleteResponse, run, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.evals.runs.with_raw_response.delete( + run_id="run_id", + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(RunDeleteResponse, run, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.evals.runs.with_streaming_response.delete( + run_id="run_id", + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(RunDeleteResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + client.evals.runs.with_raw_response.delete( + run_id="run_id", + eval_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.evals.runs.with_raw_response.delete( + run_id="", + eval_id="eval_id", + ) + + @parametrize + def test_method_cancel(self, client: OpenAI) -> None: + run = client.evals.runs.cancel( + run_id="run_id", + eval_id="eval_id", + ) + assert_matches_type(RunCancelResponse, run, path=["response"]) + + @parametrize + def test_raw_response_cancel(self, client: OpenAI) -> None: + response = client.evals.runs.with_raw_response.cancel( + run_id="run_id", + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(RunCancelResponse, run, path=["response"]) + + @parametrize + def test_streaming_response_cancel(self, client: OpenAI) -> None: + with client.evals.runs.with_streaming_response.cancel( + run_id="run_id", + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(RunCancelResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_cancel(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + client.evals.runs.with_raw_response.cancel( + run_id="run_id", + eval_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.evals.runs.with_raw_response.cancel( + run_id="", + eval_id="eval_id", + ) + + +class TestAsyncRuns: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + run = await async_client.evals.runs.create( + eval_id="eval_id", + data_source={ + "source": { + "content": [{"item": {"foo": "bar"}}], + "type": "file_content", + }, + "type": "jsonl", + }, + ) + assert_matches_type(RunCreateResponse, run, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + run = await async_client.evals.runs.create( + eval_id="eval_id", + data_source={ + "source": { + "content": [ + { + "item": {"foo": "bar"}, + "sample": {"foo": "bar"}, + } + ], + "type": "file_content", + }, + "type": "jsonl", + }, + metadata={"foo": "string"}, + name="name", + ) + assert_matches_type(RunCreateResponse, run, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.runs.with_raw_response.create( + eval_id="eval_id", + data_source={ + "source": { + "content": [{"item": {"foo": "bar"}}], + "type": "file_content", + }, + "type": "jsonl", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(RunCreateResponse, run, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.runs.with_streaming_response.create( + eval_id="eval_id", + data_source={ + "source": { + "content": [{"item": {"foo": "bar"}}], + "type": "file_content", + }, + "type": "jsonl", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(RunCreateResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + await async_client.evals.runs.with_raw_response.create( + eval_id="", + data_source={ + "source": { + "content": [{"item": {"foo": "bar"}}], + "type": "file_content", + }, + "type": "jsonl", + }, + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + run = await async_client.evals.runs.retrieve( + run_id="run_id", + eval_id="eval_id", + ) + assert_matches_type(RunRetrieveResponse, run, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.runs.with_raw_response.retrieve( + run_id="run_id", + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(RunRetrieveResponse, run, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.runs.with_streaming_response.retrieve( + run_id="run_id", + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(RunRetrieveResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + await async_client.evals.runs.with_raw_response.retrieve( + run_id="run_id", + eval_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.evals.runs.with_raw_response.retrieve( + run_id="", + eval_id="eval_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + run = await async_client.evals.runs.list( + eval_id="eval_id", + ) + assert_matches_type(AsyncCursorPage[RunListResponse], run, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + run = await async_client.evals.runs.list( + eval_id="eval_id", + after="after", + limit=0, + order="asc", + status="queued", + ) + assert_matches_type(AsyncCursorPage[RunListResponse], run, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.runs.with_raw_response.list( + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(AsyncCursorPage[RunListResponse], run, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.runs.with_streaming_response.list( + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(AsyncCursorPage[RunListResponse], run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + await async_client.evals.runs.with_raw_response.list( + eval_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + run = await async_client.evals.runs.delete( + run_id="run_id", + eval_id="eval_id", + ) + assert_matches_type(RunDeleteResponse, run, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.runs.with_raw_response.delete( + run_id="run_id", + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(RunDeleteResponse, run, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.runs.with_streaming_response.delete( + run_id="run_id", + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(RunDeleteResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + await async_client.evals.runs.with_raw_response.delete( + run_id="run_id", + eval_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.evals.runs.with_raw_response.delete( + run_id="", + eval_id="eval_id", + ) + + @parametrize + async def test_method_cancel(self, async_client: AsyncOpenAI) -> None: + run = await async_client.evals.runs.cancel( + run_id="run_id", + eval_id="eval_id", + ) + assert_matches_type(RunCancelResponse, run, path=["response"]) + + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.runs.with_raw_response.cancel( + run_id="run_id", + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(RunCancelResponse, run, path=["response"]) + + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.runs.with_streaming_response.cancel( + run_id="run_id", + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(RunCancelResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_cancel(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + await async_client.evals.runs.with_raw_response.cancel( + run_id="run_id", + eval_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.evals.runs.with_raw_response.cancel( + run_id="", + eval_id="eval_id", + ) diff --git a/tests/api_resources/fine_tuning/alpha/__init__.py b/tests/api_resources/fine_tuning/alpha/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/fine_tuning/alpha/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/fine_tuning/alpha/test_graders.py b/tests/api_resources/fine_tuning/alpha/test_graders.py new file mode 100644 index 0000000000..4a237114b6 --- /dev/null +++ b/tests/api_resources/fine_tuning/alpha/test_graders.py @@ -0,0 +1,285 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types.fine_tuning.alpha import ( + GraderRunResponse, + GraderValidateResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestGraders: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_run(self, client: OpenAI) -> None: + grader = client.fine_tuning.alpha.graders.run( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + model_sample="model_sample", + ) + assert_matches_type(GraderRunResponse, grader, path=["response"]) + + @parametrize + def test_method_run_with_all_params(self, client: OpenAI) -> None: + grader = client.fine_tuning.alpha.graders.run( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + model_sample="model_sample", + item={}, + ) + assert_matches_type(GraderRunResponse, grader, path=["response"]) + + @parametrize + def test_raw_response_run(self, client: OpenAI) -> None: + response = client.fine_tuning.alpha.graders.with_raw_response.run( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + model_sample="model_sample", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + grader = response.parse() + assert_matches_type(GraderRunResponse, grader, path=["response"]) + + @parametrize + def test_streaming_response_run(self, client: OpenAI) -> None: + with client.fine_tuning.alpha.graders.with_streaming_response.run( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + model_sample="model_sample", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + grader = response.parse() + assert_matches_type(GraderRunResponse, grader, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_validate(self, client: OpenAI) -> None: + grader = client.fine_tuning.alpha.graders.validate( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + ) + assert_matches_type(GraderValidateResponse, grader, path=["response"]) + + @parametrize + def test_method_validate_with_all_params(self, client: OpenAI) -> None: + grader = client.fine_tuning.alpha.graders.validate( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + ) + assert_matches_type(GraderValidateResponse, grader, path=["response"]) + + @parametrize + def test_raw_response_validate(self, client: OpenAI) -> None: + response = client.fine_tuning.alpha.graders.with_raw_response.validate( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + grader = response.parse() + assert_matches_type(GraderValidateResponse, grader, path=["response"]) + + @parametrize + def test_streaming_response_validate(self, client: OpenAI) -> None: + with client.fine_tuning.alpha.graders.with_streaming_response.validate( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + grader = response.parse() + assert_matches_type(GraderValidateResponse, grader, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncGraders: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_run(self, async_client: AsyncOpenAI) -> None: + grader = await async_client.fine_tuning.alpha.graders.run( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + model_sample="model_sample", + ) + assert_matches_type(GraderRunResponse, grader, path=["response"]) + + @parametrize + async def test_method_run_with_all_params(self, async_client: AsyncOpenAI) -> None: + grader = await async_client.fine_tuning.alpha.graders.run( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + model_sample="model_sample", + item={}, + ) + assert_matches_type(GraderRunResponse, grader, path=["response"]) + + @parametrize + async def test_raw_response_run(self, async_client: AsyncOpenAI) -> None: + response = await async_client.fine_tuning.alpha.graders.with_raw_response.run( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + model_sample="model_sample", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + grader = response.parse() + assert_matches_type(GraderRunResponse, grader, path=["response"]) + + @parametrize + async def test_streaming_response_run(self, async_client: AsyncOpenAI) -> None: + async with async_client.fine_tuning.alpha.graders.with_streaming_response.run( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + model_sample="model_sample", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + grader = await response.parse() + assert_matches_type(GraderRunResponse, grader, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_validate(self, async_client: AsyncOpenAI) -> None: + grader = await async_client.fine_tuning.alpha.graders.validate( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + ) + assert_matches_type(GraderValidateResponse, grader, path=["response"]) + + @parametrize + async def test_method_validate_with_all_params(self, async_client: AsyncOpenAI) -> None: + grader = await async_client.fine_tuning.alpha.graders.validate( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + ) + assert_matches_type(GraderValidateResponse, grader, path=["response"]) + + @parametrize + async def test_raw_response_validate(self, async_client: AsyncOpenAI) -> None: + response = await async_client.fine_tuning.alpha.graders.with_raw_response.validate( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + grader = response.parse() + assert_matches_type(GraderValidateResponse, grader, path=["response"]) + + @parametrize + async def test_streaming_response_validate(self, async_client: AsyncOpenAI) -> None: + async with async_client.fine_tuning.alpha.graders.with_streaming_response.validate( + grader={ + "input": "input", + "name": "name", + "operation": "eq", + "reference": "reference", + "type": "string_check", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + grader = await response.parse() + assert_matches_type(GraderValidateResponse, grader, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/fine_tuning/checkpoints/__init__.py b/tests/api_resources/fine_tuning/checkpoints/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/fine_tuning/checkpoints/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/fine_tuning/checkpoints/test_permissions.py b/tests/api_resources/fine_tuning/checkpoints/test_permissions.py new file mode 100644 index 0000000000..a3118fc838 --- /dev/null +++ b/tests/api_resources/fine_tuning/checkpoints/test_permissions.py @@ -0,0 +1,438 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncPage, AsyncPage, SyncConversationCursorPage, AsyncConversationCursorPage +from openai.types.fine_tuning.checkpoints import ( + PermissionListResponse, + PermissionCreateResponse, + PermissionDeleteResponse, + PermissionRetrieveResponse, +) + +# pyright: reportDeprecated=false + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestPermissions: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + permission = client.fine_tuning.checkpoints.permissions.create( + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + project_ids=["string"], + ) + assert_matches_type(SyncPage[PermissionCreateResponse], permission, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.fine_tuning.checkpoints.permissions.with_raw_response.create( + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + project_ids=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + permission = response.parse() + assert_matches_type(SyncPage[PermissionCreateResponse], permission, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.fine_tuning.checkpoints.permissions.with_streaming_response.create( + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + project_ids=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + permission = response.parse() + assert_matches_type(SyncPage[PermissionCreateResponse], permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `fine_tuned_model_checkpoint` but received ''" + ): + client.fine_tuning.checkpoints.permissions.with_raw_response.create( + fine_tuned_model_checkpoint="", + project_ids=["string"], + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + with pytest.warns(DeprecationWarning): + permission = client.fine_tuning.checkpoints.permissions.retrieve( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + + assert_matches_type(PermissionRetrieveResponse, permission, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: OpenAI) -> None: + with pytest.warns(DeprecationWarning): + permission = client.fine_tuning.checkpoints.permissions.retrieve( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + after="after", + limit=0, + order="ascending", + project_id="project_id", + ) + + assert_matches_type(PermissionRetrieveResponse, permission, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + with pytest.warns(DeprecationWarning): + response = client.fine_tuning.checkpoints.permissions.with_raw_response.retrieve( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + permission = response.parse() + assert_matches_type(PermissionRetrieveResponse, permission, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with pytest.warns(DeprecationWarning): + with client.fine_tuning.checkpoints.permissions.with_streaming_response.retrieve( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + permission = response.parse() + assert_matches_type(PermissionRetrieveResponse, permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.warns(DeprecationWarning): + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `fine_tuned_model_checkpoint` but received ''" + ): + client.fine_tuning.checkpoints.permissions.with_raw_response.retrieve( + fine_tuned_model_checkpoint="", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + permission = client.fine_tuning.checkpoints.permissions.list( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + assert_matches_type(SyncConversationCursorPage[PermissionListResponse], permission, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + permission = client.fine_tuning.checkpoints.permissions.list( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + after="after", + limit=0, + order="ascending", + project_id="project_id", + ) + assert_matches_type(SyncConversationCursorPage[PermissionListResponse], permission, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.fine_tuning.checkpoints.permissions.with_raw_response.list( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + permission = response.parse() + assert_matches_type(SyncConversationCursorPage[PermissionListResponse], permission, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.fine_tuning.checkpoints.permissions.with_streaming_response.list( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + permission = response.parse() + assert_matches_type(SyncConversationCursorPage[PermissionListResponse], permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `fine_tuned_model_checkpoint` but received ''" + ): + client.fine_tuning.checkpoints.permissions.with_raw_response.list( + fine_tuned_model_checkpoint="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + permission = client.fine_tuning.checkpoints.permissions.delete( + permission_id="cp_zc4Q7MP6XxulcVzj4MZdwsAB", + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + ) + assert_matches_type(PermissionDeleteResponse, permission, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.fine_tuning.checkpoints.permissions.with_raw_response.delete( + permission_id="cp_zc4Q7MP6XxulcVzj4MZdwsAB", + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + permission = response.parse() + assert_matches_type(PermissionDeleteResponse, permission, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.fine_tuning.checkpoints.permissions.with_streaming_response.delete( + permission_id="cp_zc4Q7MP6XxulcVzj4MZdwsAB", + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + permission = response.parse() + assert_matches_type(PermissionDeleteResponse, permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `fine_tuned_model_checkpoint` but received ''" + ): + client.fine_tuning.checkpoints.permissions.with_raw_response.delete( + permission_id="cp_zc4Q7MP6XxulcVzj4MZdwsAB", + fine_tuned_model_checkpoint="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `permission_id` but received ''"): + client.fine_tuning.checkpoints.permissions.with_raw_response.delete( + permission_id="", + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + ) + + +class TestAsyncPermissions: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + permission = await async_client.fine_tuning.checkpoints.permissions.create( + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + project_ids=["string"], + ) + assert_matches_type(AsyncPage[PermissionCreateResponse], permission, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.fine_tuning.checkpoints.permissions.with_raw_response.create( + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + project_ids=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + permission = response.parse() + assert_matches_type(AsyncPage[PermissionCreateResponse], permission, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.fine_tuning.checkpoints.permissions.with_streaming_response.create( + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + project_ids=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + permission = await response.parse() + assert_matches_type(AsyncPage[PermissionCreateResponse], permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `fine_tuned_model_checkpoint` but received ''" + ): + await async_client.fine_tuning.checkpoints.permissions.with_raw_response.create( + fine_tuned_model_checkpoint="", + project_ids=["string"], + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.warns(DeprecationWarning): + permission = await async_client.fine_tuning.checkpoints.permissions.retrieve( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + + assert_matches_type(PermissionRetrieveResponse, permission, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncOpenAI) -> None: + with pytest.warns(DeprecationWarning): + permission = await async_client.fine_tuning.checkpoints.permissions.retrieve( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + after="after", + limit=0, + order="ascending", + project_id="project_id", + ) + + assert_matches_type(PermissionRetrieveResponse, permission, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.warns(DeprecationWarning): + response = await async_client.fine_tuning.checkpoints.permissions.with_raw_response.retrieve( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + permission = response.parse() + assert_matches_type(PermissionRetrieveResponse, permission, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.warns(DeprecationWarning): + async with async_client.fine_tuning.checkpoints.permissions.with_streaming_response.retrieve( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + permission = await response.parse() + assert_matches_type(PermissionRetrieveResponse, permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.warns(DeprecationWarning): + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `fine_tuned_model_checkpoint` but received ''" + ): + await async_client.fine_tuning.checkpoints.permissions.with_raw_response.retrieve( + fine_tuned_model_checkpoint="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + permission = await async_client.fine_tuning.checkpoints.permissions.list( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + assert_matches_type(AsyncConversationCursorPage[PermissionListResponse], permission, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + permission = await async_client.fine_tuning.checkpoints.permissions.list( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + after="after", + limit=0, + order="ascending", + project_id="project_id", + ) + assert_matches_type(AsyncConversationCursorPage[PermissionListResponse], permission, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.fine_tuning.checkpoints.permissions.with_raw_response.list( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + permission = response.parse() + assert_matches_type(AsyncConversationCursorPage[PermissionListResponse], permission, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.fine_tuning.checkpoints.permissions.with_streaming_response.list( + fine_tuned_model_checkpoint="ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + permission = await response.parse() + assert_matches_type(AsyncConversationCursorPage[PermissionListResponse], permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `fine_tuned_model_checkpoint` but received ''" + ): + await async_client.fine_tuning.checkpoints.permissions.with_raw_response.list( + fine_tuned_model_checkpoint="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + permission = await async_client.fine_tuning.checkpoints.permissions.delete( + permission_id="cp_zc4Q7MP6XxulcVzj4MZdwsAB", + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + ) + assert_matches_type(PermissionDeleteResponse, permission, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.fine_tuning.checkpoints.permissions.with_raw_response.delete( + permission_id="cp_zc4Q7MP6XxulcVzj4MZdwsAB", + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + permission = response.parse() + assert_matches_type(PermissionDeleteResponse, permission, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.fine_tuning.checkpoints.permissions.with_streaming_response.delete( + permission_id="cp_zc4Q7MP6XxulcVzj4MZdwsAB", + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + permission = await response.parse() + assert_matches_type(PermissionDeleteResponse, permission, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `fine_tuned_model_checkpoint` but received ''" + ): + await async_client.fine_tuning.checkpoints.permissions.with_raw_response.delete( + permission_id="cp_zc4Q7MP6XxulcVzj4MZdwsAB", + fine_tuned_model_checkpoint="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `permission_id` but received ''"): + await async_client.fine_tuning.checkpoints.permissions.with_raw_response.delete( + permission_id="", + fine_tuned_model_checkpoint="ft:gpt-4o-mini-2024-07-18:org:weather:B7R9VjQd", + ) diff --git a/tests/api_resources/fine_tuning/jobs/test_checkpoints.py b/tests/api_resources/fine_tuning/jobs/test_checkpoints.py index 915d5c6f63..bb11529263 100644 --- a/tests/api_resources/fine_tuning/jobs/test_checkpoints.py +++ b/tests/api_resources/fine_tuning/jobs/test_checkpoints.py @@ -67,7 +67,9 @@ def test_path_params_list(self, client: OpenAI) -> None: class TestAsyncCheckpoints: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOpenAI) -> None: diff --git a/tests/api_resources/fine_tuning/test_jobs.py b/tests/api_resources/fine_tuning/test_jobs.py index d1ad611219..8a35255885 100644 --- a/tests/api_resources/fine_tuning/test_jobs.py +++ b/tests/api_resources/fine_tuning/test_jobs.py @@ -46,28 +46,47 @@ def test_method_create_with_all_params(self, client: OpenAI) -> None: "project": "my-wandb-project", "entity": "entity", "name": "name", - "tags": ["custom-tag", "custom-tag", "custom-tag"], + "tags": ["custom-tag"], }, + } + ], + metadata={"foo": "string"}, + method={ + "type": "supervised", + "dpo": { + "hyperparameters": { + "batch_size": "auto", + "beta": "auto", + "learning_rate_multiplier": "auto", + "n_epochs": "auto", + } }, - { - "type": "wandb", - "wandb": { - "project": "my-wandb-project", - "entity": "entity", + "reinforcement": { + "grader": { + "input": "input", "name": "name", - "tags": ["custom-tag", "custom-tag", "custom-tag"], + "operation": "eq", + "reference": "reference", + "type": "string_check", }, - }, - { - "type": "wandb", - "wandb": { - "project": "my-wandb-project", - "entity": "entity", - "name": "name", - "tags": ["custom-tag", "custom-tag", "custom-tag"], + "hyperparameters": { + "batch_size": "auto", + "compute_multiplier": "auto", + "eval_interval": "auto", + "eval_samples": "auto", + "learning_rate_multiplier": "auto", + "n_epochs": "auto", + "reasoning_effort": "default", }, }, - ], + "supervised": { + "hyperparameters": { + "batch_size": "auto", + "learning_rate_multiplier": "auto", + "n_epochs": "auto", + } + }, + }, seed=42, suffix="x", validation_file="file-abc123", @@ -148,6 +167,7 @@ def test_method_list_with_all_params(self, client: OpenAI) -> None: job = client.fine_tuning.jobs.list( after="string", limit=0, + metadata={"foo": "string"}, ) assert_matches_type(SyncCursorPage[FineTuningJob], job, path=["response"]) @@ -256,9 +276,87 @@ def test_path_params_list_events(self, client: OpenAI) -> None: "", ) + @parametrize + def test_method_pause(self, client: OpenAI) -> None: + job = client.fine_tuning.jobs.pause( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + assert_matches_type(FineTuningJob, job, path=["response"]) + + @parametrize + def test_raw_response_pause(self, client: OpenAI) -> None: + response = client.fine_tuning.jobs.with_raw_response.pause( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + job = response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + @parametrize + def test_streaming_response_pause(self, client: OpenAI) -> None: + with client.fine_tuning.jobs.with_streaming_response.pause( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_pause(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `fine_tuning_job_id` but received ''"): + client.fine_tuning.jobs.with_raw_response.pause( + "", + ) + + @parametrize + def test_method_resume(self, client: OpenAI) -> None: + job = client.fine_tuning.jobs.resume( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + assert_matches_type(FineTuningJob, job, path=["response"]) + + @parametrize + def test_raw_response_resume(self, client: OpenAI) -> None: + response = client.fine_tuning.jobs.with_raw_response.resume( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + job = response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + @parametrize + def test_streaming_response_resume(self, client: OpenAI) -> None: + with client.fine_tuning.jobs.with_streaming_response.resume( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_resume(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `fine_tuning_job_id` but received ''"): + client.fine_tuning.jobs.with_raw_response.resume( + "", + ) + class TestAsyncJobs: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: @@ -285,28 +383,47 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> "project": "my-wandb-project", "entity": "entity", "name": "name", - "tags": ["custom-tag", "custom-tag", "custom-tag"], + "tags": ["custom-tag"], }, + } + ], + metadata={"foo": "string"}, + method={ + "type": "supervised", + "dpo": { + "hyperparameters": { + "batch_size": "auto", + "beta": "auto", + "learning_rate_multiplier": "auto", + "n_epochs": "auto", + } }, - { - "type": "wandb", - "wandb": { - "project": "my-wandb-project", - "entity": "entity", + "reinforcement": { + "grader": { + "input": "input", "name": "name", - "tags": ["custom-tag", "custom-tag", "custom-tag"], + "operation": "eq", + "reference": "reference", + "type": "string_check", }, - }, - { - "type": "wandb", - "wandb": { - "project": "my-wandb-project", - "entity": "entity", - "name": "name", - "tags": ["custom-tag", "custom-tag", "custom-tag"], + "hyperparameters": { + "batch_size": "auto", + "compute_multiplier": "auto", + "eval_interval": "auto", + "eval_samples": "auto", + "learning_rate_multiplier": "auto", + "n_epochs": "auto", + "reasoning_effort": "default", }, }, - ], + "supervised": { + "hyperparameters": { + "batch_size": "auto", + "learning_rate_multiplier": "auto", + "n_epochs": "auto", + } + }, + }, seed=42, suffix="x", validation_file="file-abc123", @@ -387,6 +504,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> N job = await async_client.fine_tuning.jobs.list( after="string", limit=0, + metadata={"foo": "string"}, ) assert_matches_type(AsyncCursorPage[FineTuningJob], job, path=["response"]) @@ -494,3 +612,79 @@ async def test_path_params_list_events(self, async_client: AsyncOpenAI) -> None: await async_client.fine_tuning.jobs.with_raw_response.list_events( "", ) + + @parametrize + async def test_method_pause(self, async_client: AsyncOpenAI) -> None: + job = await async_client.fine_tuning.jobs.pause( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + assert_matches_type(FineTuningJob, job, path=["response"]) + + @parametrize + async def test_raw_response_pause(self, async_client: AsyncOpenAI) -> None: + response = await async_client.fine_tuning.jobs.with_raw_response.pause( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + job = response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + @parametrize + async def test_streaming_response_pause(self, async_client: AsyncOpenAI) -> None: + async with async_client.fine_tuning.jobs.with_streaming_response.pause( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = await response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_pause(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `fine_tuning_job_id` but received ''"): + await async_client.fine_tuning.jobs.with_raw_response.pause( + "", + ) + + @parametrize + async def test_method_resume(self, async_client: AsyncOpenAI) -> None: + job = await async_client.fine_tuning.jobs.resume( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + assert_matches_type(FineTuningJob, job, path=["response"]) + + @parametrize + async def test_raw_response_resume(self, async_client: AsyncOpenAI) -> None: + response = await async_client.fine_tuning.jobs.with_raw_response.resume( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + job = response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + @parametrize + async def test_streaming_response_resume(self, async_client: AsyncOpenAI) -> None: + async with async_client.fine_tuning.jobs.with_streaming_response.resume( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = await response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_resume(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `fine_tuning_job_id` but received ''"): + await async_client.fine_tuning.jobs.with_raw_response.resume( + "", + ) diff --git a/tests/api_resources/realtime/__init__.py b/tests/api_resources/realtime/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/realtime/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/realtime/test_calls.py b/tests/api_resources/realtime/test_calls.py new file mode 100644 index 0000000000..3d87a77f3b --- /dev/null +++ b/tests/api_resources/realtime/test_calls.py @@ -0,0 +1,704 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import httpx +import pytest +from respx import MockRouter + +import openai._legacy_response as _legacy_response +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type + +# pyright: reportDeprecated=false + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCalls: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_create(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + call = client.realtime.calls.create( + sdp="sdp", + ) + assert isinstance(call, _legacy_response.HttpxBinaryResponseContent) + assert call.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_create_with_all_params(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + call = client.realtime.calls.create( + sdp="sdp", + session={ + "type": "realtime", + "audio": { + "input": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "noise_reduction": {"type": "near_field"}, + "transcription": { + "delay": "minimal", + "language": "language", + "model": "whisper-1", + "prompt": "prompt", + }, + "turn_detection": { + "type": "server_vad", + "create_response": True, + "idle_timeout_ms": 5000, + "interrupt_response": True, + "prefix_padding_ms": 0, + "silence_duration_ms": 0, + "threshold": 0, + }, + }, + "output": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "speed": 0.25, + "voice": "alloy", + }, + }, + "include": ["item.input_audio_transcription.logprobs"], + "instructions": "instructions", + "max_output_tokens": "inf", + "model": "gpt-realtime", + "output_modalities": ["text"], + "parallel_tool_calls": True, + "prompt": { + "id": "id", + "variables": {"foo": "string"}, + "version": "version", + }, + "reasoning": {"effort": "minimal"}, + "tool_choice": "none", + "tools": [ + { + "description": "description", + "name": "name", + "parameters": {}, + "type": "function", + } + ], + "tracing": "auto", + "truncation": "auto", + }, + ) + assert isinstance(call, _legacy_response.HttpxBinaryResponseContent) + assert call.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_create(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + response = client.realtime.calls.with_raw_response.create( + sdp="sdp", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + call = response.parse() + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, call, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_create(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + with client.realtime.calls.with_streaming_response.create( + sdp="sdp", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + call = response.parse() + assert_matches_type(bytes, call, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_accept(self, client: OpenAI) -> None: + call = client.realtime.calls.accept( + call_id="call_id", + type="realtime", + ) + assert call is None + + @parametrize + def test_method_accept_with_all_params(self, client: OpenAI) -> None: + call = client.realtime.calls.accept( + call_id="call_id", + type="realtime", + audio={ + "input": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "noise_reduction": {"type": "near_field"}, + "transcription": { + "delay": "minimal", + "language": "language", + "model": "whisper-1", + "prompt": "prompt", + }, + "turn_detection": { + "type": "server_vad", + "create_response": True, + "idle_timeout_ms": 5000, + "interrupt_response": True, + "prefix_padding_ms": 0, + "silence_duration_ms": 0, + "threshold": 0, + }, + }, + "output": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "speed": 0.25, + "voice": "alloy", + }, + }, + include=["item.input_audio_transcription.logprobs"], + instructions="instructions", + max_output_tokens="inf", + model="gpt-realtime", + output_modalities=["text"], + parallel_tool_calls=True, + prompt={ + "id": "id", + "variables": {"foo": "string"}, + "version": "version", + }, + reasoning={"effort": "minimal"}, + tool_choice="none", + tools=[ + { + "description": "description", + "name": "name", + "parameters": {}, + "type": "function", + } + ], + tracing="auto", + truncation="auto", + ) + assert call is None + + @parametrize + def test_raw_response_accept(self, client: OpenAI) -> None: + response = client.realtime.calls.with_raw_response.accept( + call_id="call_id", + type="realtime", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + call = response.parse() + assert call is None + + @parametrize + def test_streaming_response_accept(self, client: OpenAI) -> None: + with client.realtime.calls.with_streaming_response.accept( + call_id="call_id", + type="realtime", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + call = response.parse() + assert call is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_accept(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"): + client.realtime.calls.with_raw_response.accept( + call_id="", + type="realtime", + ) + + @parametrize + def test_method_hangup(self, client: OpenAI) -> None: + call = client.realtime.calls.hangup( + "call_id", + ) + assert call is None + + @parametrize + def test_raw_response_hangup(self, client: OpenAI) -> None: + response = client.realtime.calls.with_raw_response.hangup( + "call_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + call = response.parse() + assert call is None + + @parametrize + def test_streaming_response_hangup(self, client: OpenAI) -> None: + with client.realtime.calls.with_streaming_response.hangup( + "call_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + call = response.parse() + assert call is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_hangup(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"): + client.realtime.calls.with_raw_response.hangup( + "", + ) + + @parametrize + def test_method_refer(self, client: OpenAI) -> None: + call = client.realtime.calls.refer( + call_id="call_id", + target_uri="tel:+14155550123", + ) + assert call is None + + @parametrize + def test_raw_response_refer(self, client: OpenAI) -> None: + response = client.realtime.calls.with_raw_response.refer( + call_id="call_id", + target_uri="tel:+14155550123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + call = response.parse() + assert call is None + + @parametrize + def test_streaming_response_refer(self, client: OpenAI) -> None: + with client.realtime.calls.with_streaming_response.refer( + call_id="call_id", + target_uri="tel:+14155550123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + call = response.parse() + assert call is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_refer(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"): + client.realtime.calls.with_raw_response.refer( + call_id="", + target_uri="tel:+14155550123", + ) + + @parametrize + def test_method_reject(self, client: OpenAI) -> None: + call = client.realtime.calls.reject( + call_id="call_id", + ) + assert call is None + + @parametrize + def test_method_reject_with_all_params(self, client: OpenAI) -> None: + call = client.realtime.calls.reject( + call_id="call_id", + status_code=486, + ) + assert call is None + + @parametrize + def test_raw_response_reject(self, client: OpenAI) -> None: + response = client.realtime.calls.with_raw_response.reject( + call_id="call_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + call = response.parse() + assert call is None + + @parametrize + def test_streaming_response_reject(self, client: OpenAI) -> None: + with client.realtime.calls.with_streaming_response.reject( + call_id="call_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + call = response.parse() + assert call is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_reject(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"): + client.realtime.calls.with_raw_response.reject( + call_id="", + ) + + +class TestAsyncCalls: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + call = await async_client.realtime.calls.create( + sdp="sdp", + ) + assert isinstance(call, _legacy_response.HttpxBinaryResponseContent) + assert call.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + call = await async_client.realtime.calls.create( + sdp="sdp", + session={ + "type": "realtime", + "audio": { + "input": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "noise_reduction": {"type": "near_field"}, + "transcription": { + "delay": "minimal", + "language": "language", + "model": "whisper-1", + "prompt": "prompt", + }, + "turn_detection": { + "type": "server_vad", + "create_response": True, + "idle_timeout_ms": 5000, + "interrupt_response": True, + "prefix_padding_ms": 0, + "silence_duration_ms": 0, + "threshold": 0, + }, + }, + "output": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "speed": 0.25, + "voice": "alloy", + }, + }, + "include": ["item.input_audio_transcription.logprobs"], + "instructions": "instructions", + "max_output_tokens": "inf", + "model": "gpt-realtime", + "output_modalities": ["text"], + "parallel_tool_calls": True, + "prompt": { + "id": "id", + "variables": {"foo": "string"}, + "version": "version", + }, + "reasoning": {"effort": "minimal"}, + "tool_choice": "none", + "tools": [ + { + "description": "description", + "name": "name", + "parameters": {}, + "type": "function", + } + ], + "tracing": "auto", + "truncation": "auto", + }, + ) + assert isinstance(call, _legacy_response.HttpxBinaryResponseContent) + assert call.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + response = await async_client.realtime.calls.with_raw_response.create( + sdp="sdp", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + call = response.parse() + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, call, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + async with async_client.realtime.calls.with_streaming_response.create( + sdp="sdp", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + call = await response.parse() + assert_matches_type(bytes, call, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_accept(self, async_client: AsyncOpenAI) -> None: + call = await async_client.realtime.calls.accept( + call_id="call_id", + type="realtime", + ) + assert call is None + + @parametrize + async def test_method_accept_with_all_params(self, async_client: AsyncOpenAI) -> None: + call = await async_client.realtime.calls.accept( + call_id="call_id", + type="realtime", + audio={ + "input": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "noise_reduction": {"type": "near_field"}, + "transcription": { + "delay": "minimal", + "language": "language", + "model": "whisper-1", + "prompt": "prompt", + }, + "turn_detection": { + "type": "server_vad", + "create_response": True, + "idle_timeout_ms": 5000, + "interrupt_response": True, + "prefix_padding_ms": 0, + "silence_duration_ms": 0, + "threshold": 0, + }, + }, + "output": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "speed": 0.25, + "voice": "alloy", + }, + }, + include=["item.input_audio_transcription.logprobs"], + instructions="instructions", + max_output_tokens="inf", + model="gpt-realtime", + output_modalities=["text"], + parallel_tool_calls=True, + prompt={ + "id": "id", + "variables": {"foo": "string"}, + "version": "version", + }, + reasoning={"effort": "minimal"}, + tool_choice="none", + tools=[ + { + "description": "description", + "name": "name", + "parameters": {}, + "type": "function", + } + ], + tracing="auto", + truncation="auto", + ) + assert call is None + + @parametrize + async def test_raw_response_accept(self, async_client: AsyncOpenAI) -> None: + response = await async_client.realtime.calls.with_raw_response.accept( + call_id="call_id", + type="realtime", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + call = response.parse() + assert call is None + + @parametrize + async def test_streaming_response_accept(self, async_client: AsyncOpenAI) -> None: + async with async_client.realtime.calls.with_streaming_response.accept( + call_id="call_id", + type="realtime", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + call = await response.parse() + assert call is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_accept(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"): + await async_client.realtime.calls.with_raw_response.accept( + call_id="", + type="realtime", + ) + + @parametrize + async def test_method_hangup(self, async_client: AsyncOpenAI) -> None: + call = await async_client.realtime.calls.hangup( + "call_id", + ) + assert call is None + + @parametrize + async def test_raw_response_hangup(self, async_client: AsyncOpenAI) -> None: + response = await async_client.realtime.calls.with_raw_response.hangup( + "call_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + call = response.parse() + assert call is None + + @parametrize + async def test_streaming_response_hangup(self, async_client: AsyncOpenAI) -> None: + async with async_client.realtime.calls.with_streaming_response.hangup( + "call_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + call = await response.parse() + assert call is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_hangup(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"): + await async_client.realtime.calls.with_raw_response.hangup( + "", + ) + + @parametrize + async def test_method_refer(self, async_client: AsyncOpenAI) -> None: + call = await async_client.realtime.calls.refer( + call_id="call_id", + target_uri="tel:+14155550123", + ) + assert call is None + + @parametrize + async def test_raw_response_refer(self, async_client: AsyncOpenAI) -> None: + response = await async_client.realtime.calls.with_raw_response.refer( + call_id="call_id", + target_uri="tel:+14155550123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + call = response.parse() + assert call is None + + @parametrize + async def test_streaming_response_refer(self, async_client: AsyncOpenAI) -> None: + async with async_client.realtime.calls.with_streaming_response.refer( + call_id="call_id", + target_uri="tel:+14155550123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + call = await response.parse() + assert call is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_refer(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"): + await async_client.realtime.calls.with_raw_response.refer( + call_id="", + target_uri="tel:+14155550123", + ) + + @parametrize + async def test_method_reject(self, async_client: AsyncOpenAI) -> None: + call = await async_client.realtime.calls.reject( + call_id="call_id", + ) + assert call is None + + @parametrize + async def test_method_reject_with_all_params(self, async_client: AsyncOpenAI) -> None: + call = await async_client.realtime.calls.reject( + call_id="call_id", + status_code=486, + ) + assert call is None + + @parametrize + async def test_raw_response_reject(self, async_client: AsyncOpenAI) -> None: + response = await async_client.realtime.calls.with_raw_response.reject( + call_id="call_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + call = response.parse() + assert call is None + + @parametrize + async def test_streaming_response_reject(self, async_client: AsyncOpenAI) -> None: + async with async_client.realtime.calls.with_streaming_response.reject( + call_id="call_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + call = await response.parse() + assert call is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_reject(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"): + await async_client.realtime.calls.with_raw_response.reject( + call_id="", + ) diff --git a/tests/api_resources/realtime/test_client_secrets.py b/tests/api_resources/realtime/test_client_secrets.py new file mode 100644 index 0000000000..c8ec8f3d3b --- /dev/null +++ b/tests/api_resources/realtime/test_client_secrets.py @@ -0,0 +1,210 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types.realtime import ClientSecretCreateResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestClientSecrets: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + client_secret = client.realtime.client_secrets.create() + assert_matches_type(ClientSecretCreateResponse, client_secret, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + client_secret = client.realtime.client_secrets.create( + expires_after={ + "anchor": "created_at", + "seconds": 10, + }, + session={ + "type": "realtime", + "audio": { + "input": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "noise_reduction": {"type": "near_field"}, + "transcription": { + "delay": "minimal", + "language": "language", + "model": "whisper-1", + "prompt": "prompt", + }, + "turn_detection": { + "type": "server_vad", + "create_response": True, + "idle_timeout_ms": 5000, + "interrupt_response": True, + "prefix_padding_ms": 0, + "silence_duration_ms": 0, + "threshold": 0, + }, + }, + "output": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "speed": 0.25, + "voice": "alloy", + }, + }, + "include": ["item.input_audio_transcription.logprobs"], + "instructions": "instructions", + "max_output_tokens": "inf", + "model": "gpt-realtime", + "output_modalities": ["text"], + "parallel_tool_calls": True, + "prompt": { + "id": "id", + "variables": {"foo": "string"}, + "version": "version", + }, + "reasoning": {"effort": "minimal"}, + "tool_choice": "none", + "tools": [ + { + "description": "description", + "name": "name", + "parameters": {}, + "type": "function", + } + ], + "tracing": "auto", + "truncation": "auto", + }, + ) + assert_matches_type(ClientSecretCreateResponse, client_secret, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.realtime.client_secrets.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + client_secret = response.parse() + assert_matches_type(ClientSecretCreateResponse, client_secret, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.realtime.client_secrets.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + client_secret = response.parse() + assert_matches_type(ClientSecretCreateResponse, client_secret, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncClientSecrets: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + client_secret = await async_client.realtime.client_secrets.create() + assert_matches_type(ClientSecretCreateResponse, client_secret, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + client_secret = await async_client.realtime.client_secrets.create( + expires_after={ + "anchor": "created_at", + "seconds": 10, + }, + session={ + "type": "realtime", + "audio": { + "input": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "noise_reduction": {"type": "near_field"}, + "transcription": { + "delay": "minimal", + "language": "language", + "model": "whisper-1", + "prompt": "prompt", + }, + "turn_detection": { + "type": "server_vad", + "create_response": True, + "idle_timeout_ms": 5000, + "interrupt_response": True, + "prefix_padding_ms": 0, + "silence_duration_ms": 0, + "threshold": 0, + }, + }, + "output": { + "format": { + "rate": 24000, + "type": "audio/pcm", + }, + "speed": 0.25, + "voice": "alloy", + }, + }, + "include": ["item.input_audio_transcription.logprobs"], + "instructions": "instructions", + "max_output_tokens": "inf", + "model": "gpt-realtime", + "output_modalities": ["text"], + "parallel_tool_calls": True, + "prompt": { + "id": "id", + "variables": {"foo": "string"}, + "version": "version", + }, + "reasoning": {"effort": "minimal"}, + "tool_choice": "none", + "tools": [ + { + "description": "description", + "name": "name", + "parameters": {}, + "type": "function", + } + ], + "tracing": "auto", + "truncation": "auto", + }, + ) + assert_matches_type(ClientSecretCreateResponse, client_secret, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.realtime.client_secrets.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + client_secret = response.parse() + assert_matches_type(ClientSecretCreateResponse, client_secret, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.realtime.client_secrets.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + client_secret = await response.parse() + assert_matches_type(ClientSecretCreateResponse, client_secret, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/responses/__init__.py b/tests/api_resources/responses/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/responses/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/responses/test_input_items.py b/tests/api_resources/responses/test_input_items.py new file mode 100644 index 0000000000..ed6fddf33a --- /dev/null +++ b/tests/api_resources/responses/test_input_items.py @@ -0,0 +1,123 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncCursorPage, AsyncCursorPage +from openai.types.responses import ResponseItem + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestInputItems: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + input_item = client.responses.input_items.list( + response_id="response_id", + ) + assert_matches_type(SyncCursorPage[ResponseItem], input_item, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + input_item = client.responses.input_items.list( + response_id="response_id", + after="after", + include=["file_search_call.results"], + limit=0, + order="asc", + ) + assert_matches_type(SyncCursorPage[ResponseItem], input_item, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.responses.input_items.with_raw_response.list( + response_id="response_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + input_item = response.parse() + assert_matches_type(SyncCursorPage[ResponseItem], input_item, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.responses.input_items.with_streaming_response.list( + response_id="response_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + input_item = response.parse() + assert_matches_type(SyncCursorPage[ResponseItem], input_item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `response_id` but received ''"): + client.responses.input_items.with_raw_response.list( + response_id="", + ) + + +class TestAsyncInputItems: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + input_item = await async_client.responses.input_items.list( + response_id="response_id", + ) + assert_matches_type(AsyncCursorPage[ResponseItem], input_item, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + input_item = await async_client.responses.input_items.list( + response_id="response_id", + after="after", + include=["file_search_call.results"], + limit=0, + order="asc", + ) + assert_matches_type(AsyncCursorPage[ResponseItem], input_item, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.input_items.with_raw_response.list( + response_id="response_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + input_item = response.parse() + assert_matches_type(AsyncCursorPage[ResponseItem], input_item, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.responses.input_items.with_streaming_response.list( + response_id="response_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + input_item = await response.parse() + assert_matches_type(AsyncCursorPage[ResponseItem], input_item, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `response_id` but received ''"): + await async_client.responses.input_items.with_raw_response.list( + response_id="", + ) diff --git a/tests/api_resources/responses/test_input_tokens.py b/tests/api_resources/responses/test_input_tokens.py new file mode 100644 index 0000000000..04c0bb698e --- /dev/null +++ b/tests/api_resources/responses/test_input_tokens.py @@ -0,0 +1,142 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types.responses import InputTokenCountResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestInputTokens: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_count(self, client: OpenAI) -> None: + input_token = client.responses.input_tokens.count() + assert_matches_type(InputTokenCountResponse, input_token, path=["response"]) + + @parametrize + def test_method_count_with_all_params(self, client: OpenAI) -> None: + input_token = client.responses.input_tokens.count( + conversation="string", + input="string", + instructions="instructions", + model="model", + parallel_tool_calls=True, + personality="friendly", + previous_response_id="resp_123", + reasoning={ + "effort": "none", + "generate_summary": "auto", + "summary": "auto", + }, + text={ + "format": {"type": "text"}, + "verbosity": "low", + }, + tool_choice="none", + tools=[ + { + "name": "name", + "parameters": {"foo": "bar"}, + "strict": True, + "type": "function", + "defer_loading": True, + "description": "description", + } + ], + truncation="auto", + ) + assert_matches_type(InputTokenCountResponse, input_token, path=["response"]) + + @parametrize + def test_raw_response_count(self, client: OpenAI) -> None: + response = client.responses.input_tokens.with_raw_response.count() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + input_token = response.parse() + assert_matches_type(InputTokenCountResponse, input_token, path=["response"]) + + @parametrize + def test_streaming_response_count(self, client: OpenAI) -> None: + with client.responses.input_tokens.with_streaming_response.count() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + input_token = response.parse() + assert_matches_type(InputTokenCountResponse, input_token, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncInputTokens: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_count(self, async_client: AsyncOpenAI) -> None: + input_token = await async_client.responses.input_tokens.count() + assert_matches_type(InputTokenCountResponse, input_token, path=["response"]) + + @parametrize + async def test_method_count_with_all_params(self, async_client: AsyncOpenAI) -> None: + input_token = await async_client.responses.input_tokens.count( + conversation="string", + input="string", + instructions="instructions", + model="model", + parallel_tool_calls=True, + personality="friendly", + previous_response_id="resp_123", + reasoning={ + "effort": "none", + "generate_summary": "auto", + "summary": "auto", + }, + text={ + "format": {"type": "text"}, + "verbosity": "low", + }, + tool_choice="none", + tools=[ + { + "name": "name", + "parameters": {"foo": "bar"}, + "strict": True, + "type": "function", + "defer_loading": True, + "description": "description", + } + ], + truncation="auto", + ) + assert_matches_type(InputTokenCountResponse, input_token, path=["response"]) + + @parametrize + async def test_raw_response_count(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.input_tokens.with_raw_response.count() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + input_token = response.parse() + assert_matches_type(InputTokenCountResponse, input_token, path=["response"]) + + @parametrize + async def test_streaming_response_count(self, async_client: AsyncOpenAI) -> None: + async with async_client.responses.input_tokens.with_streaming_response.count() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + input_token = await response.parse() + assert_matches_type(InputTokenCountResponse, input_token, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/skills/__init__.py b/tests/api_resources/skills/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/skills/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/skills/test_content.py b/tests/api_resources/skills/test_content.py new file mode 100644 index 0000000000..f93e6fc015 --- /dev/null +++ b/tests/api_resources/skills/test_content.py @@ -0,0 +1,122 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import httpx +import pytest +from respx import MockRouter + +import openai._legacy_response as _legacy_response +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type + +# pyright: reportDeprecated=false + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestContent: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + content = client.skills.content.retrieve( + "skill_123", + ) + assert isinstance(content, _legacy_response.HttpxBinaryResponseContent) + assert content.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + response = client.skills.content.with_raw_response.retrieve( + "skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + content = response.parse() + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + with client.skills.content.with_streaming_response.retrieve( + "skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + content = response.parse() + assert_matches_type(bytes, content, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + client.skills.content.with_raw_response.retrieve( + "", + ) + + +class TestAsyncContent: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + content = await async_client.skills.content.retrieve( + "skill_123", + ) + assert isinstance(content, _legacy_response.HttpxBinaryResponseContent) + assert content.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + response = await async_client.skills.content.with_raw_response.retrieve( + "skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + content = response.parse() + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + async with async_client.skills.content.with_streaming_response.retrieve( + "skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + content = await response.parse() + assert_matches_type(bytes, content, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + await async_client.skills.content.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/skills/test_versions.py b/tests/api_resources/skills/test_versions.py new file mode 100644 index 0000000000..40c807354a --- /dev/null +++ b/tests/api_resources/skills/test_versions.py @@ -0,0 +1,407 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncCursorPage, AsyncCursorPage +from openai.types.skills import SkillVersion, DeletedSkillVersion + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestVersions: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + version = client.skills.versions.create( + skill_id="skill_123", + ) + assert_matches_type(SkillVersion, version, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + version = client.skills.versions.create( + skill_id="skill_123", + default=True, + files=[b"Example data"], + ) + assert_matches_type(SkillVersion, version, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.skills.versions.with_raw_response.create( + skill_id="skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + version = response.parse() + assert_matches_type(SkillVersion, version, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.skills.versions.with_streaming_response.create( + skill_id="skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + version = response.parse() + assert_matches_type(SkillVersion, version, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + client.skills.versions.with_raw_response.create( + skill_id="", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + version = client.skills.versions.retrieve( + version="version", + skill_id="skill_123", + ) + assert_matches_type(SkillVersion, version, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.skills.versions.with_raw_response.retrieve( + version="version", + skill_id="skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + version = response.parse() + assert_matches_type(SkillVersion, version, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.skills.versions.with_streaming_response.retrieve( + version="version", + skill_id="skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + version = response.parse() + assert_matches_type(SkillVersion, version, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + client.skills.versions.with_raw_response.retrieve( + version="version", + skill_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `version` but received ''"): + client.skills.versions.with_raw_response.retrieve( + version="", + skill_id="skill_123", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + version = client.skills.versions.list( + skill_id="skill_123", + ) + assert_matches_type(SyncCursorPage[SkillVersion], version, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + version = client.skills.versions.list( + skill_id="skill_123", + after="skillver_123", + limit=0, + order="asc", + ) + assert_matches_type(SyncCursorPage[SkillVersion], version, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.skills.versions.with_raw_response.list( + skill_id="skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + version = response.parse() + assert_matches_type(SyncCursorPage[SkillVersion], version, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.skills.versions.with_streaming_response.list( + skill_id="skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + version = response.parse() + assert_matches_type(SyncCursorPage[SkillVersion], version, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + client.skills.versions.with_raw_response.list( + skill_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + version = client.skills.versions.delete( + version="version", + skill_id="skill_123", + ) + assert_matches_type(DeletedSkillVersion, version, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.skills.versions.with_raw_response.delete( + version="version", + skill_id="skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + version = response.parse() + assert_matches_type(DeletedSkillVersion, version, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.skills.versions.with_streaming_response.delete( + version="version", + skill_id="skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + version = response.parse() + assert_matches_type(DeletedSkillVersion, version, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + client.skills.versions.with_raw_response.delete( + version="version", + skill_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `version` but received ''"): + client.skills.versions.with_raw_response.delete( + version="", + skill_id="skill_123", + ) + + +class TestAsyncVersions: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + version = await async_client.skills.versions.create( + skill_id="skill_123", + ) + assert_matches_type(SkillVersion, version, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + version = await async_client.skills.versions.create( + skill_id="skill_123", + default=True, + files=[b"Example data"], + ) + assert_matches_type(SkillVersion, version, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.skills.versions.with_raw_response.create( + skill_id="skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + version = response.parse() + assert_matches_type(SkillVersion, version, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.skills.versions.with_streaming_response.create( + skill_id="skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + version = await response.parse() + assert_matches_type(SkillVersion, version, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + await async_client.skills.versions.with_raw_response.create( + skill_id="", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + version = await async_client.skills.versions.retrieve( + version="version", + skill_id="skill_123", + ) + assert_matches_type(SkillVersion, version, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.skills.versions.with_raw_response.retrieve( + version="version", + skill_id="skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + version = response.parse() + assert_matches_type(SkillVersion, version, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.skills.versions.with_streaming_response.retrieve( + version="version", + skill_id="skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + version = await response.parse() + assert_matches_type(SkillVersion, version, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + await async_client.skills.versions.with_raw_response.retrieve( + version="version", + skill_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `version` but received ''"): + await async_client.skills.versions.with_raw_response.retrieve( + version="", + skill_id="skill_123", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + version = await async_client.skills.versions.list( + skill_id="skill_123", + ) + assert_matches_type(AsyncCursorPage[SkillVersion], version, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + version = await async_client.skills.versions.list( + skill_id="skill_123", + after="skillver_123", + limit=0, + order="asc", + ) + assert_matches_type(AsyncCursorPage[SkillVersion], version, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.skills.versions.with_raw_response.list( + skill_id="skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + version = response.parse() + assert_matches_type(AsyncCursorPage[SkillVersion], version, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.skills.versions.with_streaming_response.list( + skill_id="skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + version = await response.parse() + assert_matches_type(AsyncCursorPage[SkillVersion], version, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + await async_client.skills.versions.with_raw_response.list( + skill_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + version = await async_client.skills.versions.delete( + version="version", + skill_id="skill_123", + ) + assert_matches_type(DeletedSkillVersion, version, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.skills.versions.with_raw_response.delete( + version="version", + skill_id="skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + version = response.parse() + assert_matches_type(DeletedSkillVersion, version, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.skills.versions.with_streaming_response.delete( + version="version", + skill_id="skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + version = await response.parse() + assert_matches_type(DeletedSkillVersion, version, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + await async_client.skills.versions.with_raw_response.delete( + version="version", + skill_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `version` but received ''"): + await async_client.skills.versions.with_raw_response.delete( + version="", + skill_id="skill_123", + ) diff --git a/tests/api_resources/skills/versions/__init__.py b/tests/api_resources/skills/versions/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/skills/versions/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/skills/versions/test_content.py b/tests/api_resources/skills/versions/test_content.py new file mode 100644 index 0000000000..3d3e19519e --- /dev/null +++ b/tests/api_resources/skills/versions/test_content.py @@ -0,0 +1,154 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import httpx +import pytest +from respx import MockRouter + +import openai._legacy_response as _legacy_response +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type + +# pyright: reportDeprecated=false + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestContent: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + content = client.skills.versions.content.retrieve( + version="version", + skill_id="skill_123", + ) + assert isinstance(content, _legacy_response.HttpxBinaryResponseContent) + assert content.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + response = client.skills.versions.content.with_raw_response.retrieve( + version="version", + skill_id="skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + content = response.parse() + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_retrieve(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + with client.skills.versions.content.with_streaming_response.retrieve( + version="version", + skill_id="skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + content = response.parse() + assert_matches_type(bytes, content, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + client.skills.versions.content.with_raw_response.retrieve( + version="version", + skill_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `version` but received ''"): + client.skills.versions.content.with_raw_response.retrieve( + version="", + skill_id="skill_123", + ) + + +class TestAsyncContent: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + content = await async_client.skills.versions.content.retrieve( + version="version", + skill_id="skill_123", + ) + assert isinstance(content, _legacy_response.HttpxBinaryResponseContent) + assert content.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + response = await async_client.skills.versions.content.with_raw_response.retrieve( + version="version", + skill_id="skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + content = response.parse() + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, content, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/skills/skill_123/versions/version/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + async with async_client.skills.versions.content.with_streaming_response.retrieve( + version="version", + skill_id="skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + content = await response.parse() + assert_matches_type(bytes, content, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + await async_client.skills.versions.content.with_raw_response.retrieve( + version="version", + skill_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `version` but received ''"): + await async_client.skills.versions.content.with_raw_response.retrieve( + version="", + skill_id="skill_123", + ) diff --git a/tests/api_resources/test_batches.py b/tests/api_resources/test_batches.py index 6f9b598e61..95b94c4846 100644 --- a/tests/api_resources/test_batches.py +++ b/tests/api_resources/test_batches.py @@ -22,7 +22,7 @@ class TestBatches: def test_method_create(self, client: OpenAI) -> None: batch = client.batches.create( completion_window="24h", - endpoint="/v1/chat/completions", + endpoint="/v1/responses", input_file_id="string", ) assert_matches_type(Batch, batch, path=["response"]) @@ -31,9 +31,13 @@ def test_method_create(self, client: OpenAI) -> None: def test_method_create_with_all_params(self, client: OpenAI) -> None: batch = client.batches.create( completion_window="24h", - endpoint="/v1/chat/completions", + endpoint="/v1/responses", input_file_id="string", metadata={"foo": "string"}, + output_expires_after={ + "anchor": "created_at", + "seconds": 3600, + }, ) assert_matches_type(Batch, batch, path=["response"]) @@ -41,7 +45,7 @@ def test_method_create_with_all_params(self, client: OpenAI) -> None: def test_raw_response_create(self, client: OpenAI) -> None: response = client.batches.with_raw_response.create( completion_window="24h", - endpoint="/v1/chat/completions", + endpoint="/v1/responses", input_file_id="string", ) @@ -54,7 +58,7 @@ def test_raw_response_create(self, client: OpenAI) -> None: def test_streaming_response_create(self, client: OpenAI) -> None: with client.batches.with_streaming_response.create( completion_window="24h", - endpoint="/v1/chat/completions", + endpoint="/v1/responses", input_file_id="string", ) as response: assert not response.is_closed @@ -176,13 +180,15 @@ def test_path_params_cancel(self, client: OpenAI) -> None: class TestAsyncBatches: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: batch = await async_client.batches.create( completion_window="24h", - endpoint="/v1/chat/completions", + endpoint="/v1/responses", input_file_id="string", ) assert_matches_type(Batch, batch, path=["response"]) @@ -191,9 +197,13 @@ async def test_method_create(self, async_client: AsyncOpenAI) -> None: async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: batch = await async_client.batches.create( completion_window="24h", - endpoint="/v1/chat/completions", + endpoint="/v1/responses", input_file_id="string", metadata={"foo": "string"}, + output_expires_after={ + "anchor": "created_at", + "seconds": 3600, + }, ) assert_matches_type(Batch, batch, path=["response"]) @@ -201,7 +211,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: response = await async_client.batches.with_raw_response.create( completion_window="24h", - endpoint="/v1/chat/completions", + endpoint="/v1/responses", input_file_id="string", ) @@ -214,7 +224,7 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: async with async_client.batches.with_streaming_response.create( completion_window="24h", - endpoint="/v1/chat/completions", + endpoint="/v1/responses", input_file_id="string", ) as response: assert not response.is_closed diff --git a/tests/api_resources/test_completions.py b/tests/api_resources/test_completions.py index ad2679cabe..d0081aaca2 100644 --- a/tests/api_resources/test_completions.py +++ b/tests/api_resources/test_completions.py @@ -20,7 +20,7 @@ class TestCompletions: @parametrize def test_method_create_overload_1(self, client: OpenAI) -> None: completion = client.completions.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", ) assert_matches_type(Completion, completion, path=["response"]) @@ -28,7 +28,7 @@ def test_method_create_overload_1(self, client: OpenAI) -> None: @parametrize def test_method_create_with_all_params_overload_1(self, client: OpenAI) -> None: completion = client.completions.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", best_of=0, echo=True, @@ -38,10 +38,13 @@ def test_method_create_with_all_params_overload_1(self, client: OpenAI) -> None: max_tokens=16, n=1, presence_penalty=-2, - seed=-9007199254740991, + seed=0, stop="\n", stream=False, - stream_options={"include_usage": True}, + stream_options={ + "include_obfuscation": True, + "include_usage": True, + }, suffix="test.", temperature=1, top_p=1, @@ -52,7 +55,7 @@ def test_method_create_with_all_params_overload_1(self, client: OpenAI) -> None: @parametrize def test_raw_response_create_overload_1(self, client: OpenAI) -> None: response = client.completions.with_raw_response.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", ) @@ -64,7 +67,7 @@ def test_raw_response_create_overload_1(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create_overload_1(self, client: OpenAI) -> None: with client.completions.with_streaming_response.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", ) as response: assert not response.is_closed @@ -78,7 +81,7 @@ def test_streaming_response_create_overload_1(self, client: OpenAI) -> None: @parametrize def test_method_create_overload_2(self, client: OpenAI) -> None: completion_stream = client.completions.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", stream=True, ) @@ -87,7 +90,7 @@ def test_method_create_overload_2(self, client: OpenAI) -> None: @parametrize def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: completion_stream = client.completions.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", stream=True, best_of=0, @@ -98,9 +101,12 @@ def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: max_tokens=16, n=1, presence_penalty=-2, - seed=-9007199254740991, + seed=0, stop="\n", - stream_options={"include_usage": True}, + stream_options={ + "include_obfuscation": True, + "include_usage": True, + }, suffix="test.", temperature=1, top_p=1, @@ -111,7 +117,7 @@ def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: @parametrize def test_raw_response_create_overload_2(self, client: OpenAI) -> None: response = client.completions.with_raw_response.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", stream=True, ) @@ -123,7 +129,7 @@ def test_raw_response_create_overload_2(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create_overload_2(self, client: OpenAI) -> None: with client.completions.with_streaming_response.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", stream=True, ) as response: @@ -137,12 +143,14 @@ def test_streaming_response_create_overload_2(self, client: OpenAI) -> None: class TestAsyncCompletions: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None: completion = await async_client.completions.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", ) assert_matches_type(Completion, completion, path=["response"]) @@ -150,7 +158,7 @@ async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None @parametrize async def test_method_create_with_all_params_overload_1(self, async_client: AsyncOpenAI) -> None: completion = await async_client.completions.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", best_of=0, echo=True, @@ -160,10 +168,13 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn max_tokens=16, n=1, presence_penalty=-2, - seed=-9007199254740991, + seed=0, stop="\n", stream=False, - stream_options={"include_usage": True}, + stream_options={ + "include_obfuscation": True, + "include_usage": True, + }, suffix="test.", temperature=1, top_p=1, @@ -174,7 +185,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn @parametrize async def test_raw_response_create_overload_1(self, async_client: AsyncOpenAI) -> None: response = await async_client.completions.with_raw_response.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", ) @@ -186,7 +197,7 @@ async def test_raw_response_create_overload_1(self, async_client: AsyncOpenAI) - @parametrize async def test_streaming_response_create_overload_1(self, async_client: AsyncOpenAI) -> None: async with async_client.completions.with_streaming_response.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", ) as response: assert not response.is_closed @@ -200,7 +211,7 @@ async def test_streaming_response_create_overload_1(self, async_client: AsyncOpe @parametrize async def test_method_create_overload_2(self, async_client: AsyncOpenAI) -> None: completion_stream = await async_client.completions.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", stream=True, ) @@ -209,7 +220,7 @@ async def test_method_create_overload_2(self, async_client: AsyncOpenAI) -> None @parametrize async def test_method_create_with_all_params_overload_2(self, async_client: AsyncOpenAI) -> None: completion_stream = await async_client.completions.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", stream=True, best_of=0, @@ -220,9 +231,12 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn max_tokens=16, n=1, presence_penalty=-2, - seed=-9007199254740991, + seed=0, stop="\n", - stream_options={"include_usage": True}, + stream_options={ + "include_obfuscation": True, + "include_usage": True, + }, suffix="test.", temperature=1, top_p=1, @@ -233,7 +247,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn @parametrize async def test_raw_response_create_overload_2(self, async_client: AsyncOpenAI) -> None: response = await async_client.completions.with_raw_response.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", stream=True, ) @@ -245,7 +259,7 @@ async def test_raw_response_create_overload_2(self, async_client: AsyncOpenAI) - @parametrize async def test_streaming_response_create_overload_2(self, async_client: AsyncOpenAI) -> None: async with async_client.completions.with_streaming_response.create( - model="string", + model="gpt-3.5-turbo-instruct", prompt="This is a test.", stream=True, ) as response: diff --git a/tests/api_resources/test_containers.py b/tests/api_resources/test_containers.py new file mode 100644 index 0000000000..321d7778b0 --- /dev/null +++ b/tests/api_resources/test_containers.py @@ -0,0 +1,355 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types import ( + ContainerListResponse, + ContainerCreateResponse, + ContainerRetrieveResponse, +) +from openai.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestContainers: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + container = client.containers.create( + name="name", + ) + assert_matches_type(ContainerCreateResponse, container, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + container = client.containers.create( + name="name", + expires_after={ + "anchor": "last_active_at", + "minutes": 0, + }, + file_ids=["string"], + memory_limit="1g", + network_policy={"type": "disabled"}, + skills=[ + { + "skill_id": "x", + "type": "skill_reference", + "version": "version", + } + ], + ) + assert_matches_type(ContainerCreateResponse, container, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.containers.with_raw_response.create( + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + container = response.parse() + assert_matches_type(ContainerCreateResponse, container, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.containers.with_streaming_response.create( + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + container = response.parse() + assert_matches_type(ContainerCreateResponse, container, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + container = client.containers.retrieve( + "container_id", + ) + assert_matches_type(ContainerRetrieveResponse, container, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.containers.with_raw_response.retrieve( + "container_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + container = response.parse() + assert_matches_type(ContainerRetrieveResponse, container, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.containers.with_streaming_response.retrieve( + "container_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + container = response.parse() + assert_matches_type(ContainerRetrieveResponse, container, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + client.containers.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + container = client.containers.list() + assert_matches_type(SyncCursorPage[ContainerListResponse], container, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + container = client.containers.list( + after="after", + limit=0, + name="name", + order="asc", + ) + assert_matches_type(SyncCursorPage[ContainerListResponse], container, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.containers.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + container = response.parse() + assert_matches_type(SyncCursorPage[ContainerListResponse], container, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.containers.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + container = response.parse() + assert_matches_type(SyncCursorPage[ContainerListResponse], container, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + container = client.containers.delete( + "container_id", + ) + assert container is None + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.containers.with_raw_response.delete( + "container_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + container = response.parse() + assert container is None + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.containers.with_streaming_response.delete( + "container_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + container = response.parse() + assert container is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + client.containers.with_raw_response.delete( + "", + ) + + +class TestAsyncContainers: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + container = await async_client.containers.create( + name="name", + ) + assert_matches_type(ContainerCreateResponse, container, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + container = await async_client.containers.create( + name="name", + expires_after={ + "anchor": "last_active_at", + "minutes": 0, + }, + file_ids=["string"], + memory_limit="1g", + network_policy={"type": "disabled"}, + skills=[ + { + "skill_id": "x", + "type": "skill_reference", + "version": "version", + } + ], + ) + assert_matches_type(ContainerCreateResponse, container, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.containers.with_raw_response.create( + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + container = response.parse() + assert_matches_type(ContainerCreateResponse, container, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.containers.with_streaming_response.create( + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + container = await response.parse() + assert_matches_type(ContainerCreateResponse, container, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + container = await async_client.containers.retrieve( + "container_id", + ) + assert_matches_type(ContainerRetrieveResponse, container, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.containers.with_raw_response.retrieve( + "container_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + container = response.parse() + assert_matches_type(ContainerRetrieveResponse, container, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.containers.with_streaming_response.retrieve( + "container_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + container = await response.parse() + assert_matches_type(ContainerRetrieveResponse, container, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + await async_client.containers.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + container = await async_client.containers.list() + assert_matches_type(AsyncCursorPage[ContainerListResponse], container, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + container = await async_client.containers.list( + after="after", + limit=0, + name="name", + order="asc", + ) + assert_matches_type(AsyncCursorPage[ContainerListResponse], container, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.containers.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + container = response.parse() + assert_matches_type(AsyncCursorPage[ContainerListResponse], container, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.containers.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + container = await response.parse() + assert_matches_type(AsyncCursorPage[ContainerListResponse], container, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + container = await async_client.containers.delete( + "container_id", + ) + assert container is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.containers.with_raw_response.delete( + "container_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + container = response.parse() + assert container is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.containers.with_streaming_response.delete( + "container_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + container = await response.parse() + assert container is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `container_id` but received ''"): + await async_client.containers.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_conversations.py b/tests/api_resources/test_conversations.py new file mode 100644 index 0000000000..a451d339e8 --- /dev/null +++ b/tests/api_resources/test_conversations.py @@ -0,0 +1,343 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types.conversations import ( + Conversation, + ConversationDeletedResource, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestConversations: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + conversation = client.conversations.create() + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + conversation = client.conversations.create( + items=[ + { + "content": "string", + "role": "user", + "phase": "commentary", + "type": "message", + } + ], + metadata={"foo": "string"}, + ) + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.conversations.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversation = response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.conversations.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversation = response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + conversation = client.conversations.retrieve( + "conv_123", + ) + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.conversations.with_raw_response.retrieve( + "conv_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversation = response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.conversations.with_streaming_response.retrieve( + "conv_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversation = response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + client.conversations.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + conversation = client.conversations.update( + conversation_id="conv_123", + metadata={"foo": "string"}, + ) + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.conversations.with_raw_response.update( + conversation_id="conv_123", + metadata={"foo": "string"}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversation = response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.conversations.with_streaming_response.update( + conversation_id="conv_123", + metadata={"foo": "string"}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversation = response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + client.conversations.with_raw_response.update( + conversation_id="", + metadata={"foo": "string"}, + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + conversation = client.conversations.delete( + "conv_123", + ) + assert_matches_type(ConversationDeletedResource, conversation, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.conversations.with_raw_response.delete( + "conv_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversation = response.parse() + assert_matches_type(ConversationDeletedResource, conversation, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.conversations.with_streaming_response.delete( + "conv_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversation = response.parse() + assert_matches_type(ConversationDeletedResource, conversation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + client.conversations.with_raw_response.delete( + "", + ) + + +class TestAsyncConversations: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + conversation = await async_client.conversations.create() + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + conversation = await async_client.conversations.create( + items=[ + { + "content": "string", + "role": "user", + "phase": "commentary", + "type": "message", + } + ], + metadata={"foo": "string"}, + ) + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.conversations.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversation = response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.conversations.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversation = await response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + conversation = await async_client.conversations.retrieve( + "conv_123", + ) + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.conversations.with_raw_response.retrieve( + "conv_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversation = response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.conversations.with_streaming_response.retrieve( + "conv_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversation = await response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + await async_client.conversations.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + conversation = await async_client.conversations.update( + conversation_id="conv_123", + metadata={"foo": "string"}, + ) + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.conversations.with_raw_response.update( + conversation_id="conv_123", + metadata={"foo": "string"}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversation = response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.conversations.with_streaming_response.update( + conversation_id="conv_123", + metadata={"foo": "string"}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversation = await response.parse() + assert_matches_type(Conversation, conversation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + await async_client.conversations.with_raw_response.update( + conversation_id="", + metadata={"foo": "string"}, + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + conversation = await async_client.conversations.delete( + "conv_123", + ) + assert_matches_type(ConversationDeletedResource, conversation, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.conversations.with_raw_response.delete( + "conv_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversation = response.parse() + assert_matches_type(ConversationDeletedResource, conversation, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.conversations.with_streaming_response.delete( + "conv_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversation = await response.parse() + assert_matches_type(ConversationDeletedResource, conversation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"): + await async_client.conversations.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_embeddings.py b/tests/api_resources/test_embeddings.py index e75545b4e2..ce6e213d59 100644 --- a/tests/api_resources/test_embeddings.py +++ b/tests/api_resources/test_embeddings.py @@ -64,7 +64,9 @@ def test_streaming_response_create(self, client: OpenAI) -> None: class TestAsyncEmbeddings: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: diff --git a/tests/api_resources/test_evals.py b/tests/api_resources/test_evals.py new file mode 100644 index 0000000000..473a4711ca --- /dev/null +++ b/tests/api_resources/test_evals.py @@ -0,0 +1,573 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types import ( + EvalListResponse, + EvalCreateResponse, + EvalDeleteResponse, + EvalUpdateResponse, + EvalRetrieveResponse, +) +from openai.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEvals: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + eval = client.evals.create( + data_source_config={ + "item_schema": {"foo": "bar"}, + "type": "custom", + }, + testing_criteria=[ + { + "input": [ + { + "content": "content", + "role": "role", + } + ], + "labels": ["string"], + "model": "model", + "name": "name", + "passing_labels": ["string"], + "type": "label_model", + } + ], + ) + assert_matches_type(EvalCreateResponse, eval, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + eval = client.evals.create( + data_source_config={ + "item_schema": {"foo": "bar"}, + "type": "custom", + "include_sample_schema": True, + }, + testing_criteria=[ + { + "input": [ + { + "content": "content", + "role": "role", + } + ], + "labels": ["string"], + "model": "model", + "name": "name", + "passing_labels": ["string"], + "type": "label_model", + } + ], + metadata={"foo": "string"}, + name="name", + ) + assert_matches_type(EvalCreateResponse, eval, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.evals.with_raw_response.create( + data_source_config={ + "item_schema": {"foo": "bar"}, + "type": "custom", + }, + testing_criteria=[ + { + "input": [ + { + "content": "content", + "role": "role", + } + ], + "labels": ["string"], + "model": "model", + "name": "name", + "passing_labels": ["string"], + "type": "label_model", + } + ], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(EvalCreateResponse, eval, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.evals.with_streaming_response.create( + data_source_config={ + "item_schema": {"foo": "bar"}, + "type": "custom", + }, + testing_criteria=[ + { + "input": [ + { + "content": "content", + "role": "role", + } + ], + "labels": ["string"], + "model": "model", + "name": "name", + "passing_labels": ["string"], + "type": "label_model", + } + ], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = response.parse() + assert_matches_type(EvalCreateResponse, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + eval = client.evals.retrieve( + "eval_id", + ) + assert_matches_type(EvalRetrieveResponse, eval, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.evals.with_raw_response.retrieve( + "eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(EvalRetrieveResponse, eval, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.evals.with_streaming_response.retrieve( + "eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = response.parse() + assert_matches_type(EvalRetrieveResponse, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + client.evals.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + eval = client.evals.update( + eval_id="eval_id", + ) + assert_matches_type(EvalUpdateResponse, eval, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: OpenAI) -> None: + eval = client.evals.update( + eval_id="eval_id", + metadata={"foo": "string"}, + name="name", + ) + assert_matches_type(EvalUpdateResponse, eval, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.evals.with_raw_response.update( + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(EvalUpdateResponse, eval, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.evals.with_streaming_response.update( + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = response.parse() + assert_matches_type(EvalUpdateResponse, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + client.evals.with_raw_response.update( + eval_id="", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + eval = client.evals.list() + assert_matches_type(SyncCursorPage[EvalListResponse], eval, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + eval = client.evals.list( + after="after", + limit=0, + order="asc", + order_by="created_at", + ) + assert_matches_type(SyncCursorPage[EvalListResponse], eval, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.evals.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(SyncCursorPage[EvalListResponse], eval, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.evals.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = response.parse() + assert_matches_type(SyncCursorPage[EvalListResponse], eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + eval = client.evals.delete( + "eval_id", + ) + assert_matches_type(EvalDeleteResponse, eval, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.evals.with_raw_response.delete( + "eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(EvalDeleteResponse, eval, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.evals.with_streaming_response.delete( + "eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = response.parse() + assert_matches_type(EvalDeleteResponse, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + client.evals.with_raw_response.delete( + "", + ) + + +class TestAsyncEvals: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + eval = await async_client.evals.create( + data_source_config={ + "item_schema": {"foo": "bar"}, + "type": "custom", + }, + testing_criteria=[ + { + "input": [ + { + "content": "content", + "role": "role", + } + ], + "labels": ["string"], + "model": "model", + "name": "name", + "passing_labels": ["string"], + "type": "label_model", + } + ], + ) + assert_matches_type(EvalCreateResponse, eval, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + eval = await async_client.evals.create( + data_source_config={ + "item_schema": {"foo": "bar"}, + "type": "custom", + "include_sample_schema": True, + }, + testing_criteria=[ + { + "input": [ + { + "content": "content", + "role": "role", + } + ], + "labels": ["string"], + "model": "model", + "name": "name", + "passing_labels": ["string"], + "type": "label_model", + } + ], + metadata={"foo": "string"}, + name="name", + ) + assert_matches_type(EvalCreateResponse, eval, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.with_raw_response.create( + data_source_config={ + "item_schema": {"foo": "bar"}, + "type": "custom", + }, + testing_criteria=[ + { + "input": [ + { + "content": "content", + "role": "role", + } + ], + "labels": ["string"], + "model": "model", + "name": "name", + "passing_labels": ["string"], + "type": "label_model", + } + ], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(EvalCreateResponse, eval, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.with_streaming_response.create( + data_source_config={ + "item_schema": {"foo": "bar"}, + "type": "custom", + }, + testing_criteria=[ + { + "input": [ + { + "content": "content", + "role": "role", + } + ], + "labels": ["string"], + "model": "model", + "name": "name", + "passing_labels": ["string"], + "type": "label_model", + } + ], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = await response.parse() + assert_matches_type(EvalCreateResponse, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + eval = await async_client.evals.retrieve( + "eval_id", + ) + assert_matches_type(EvalRetrieveResponse, eval, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.with_raw_response.retrieve( + "eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(EvalRetrieveResponse, eval, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.with_streaming_response.retrieve( + "eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = await response.parse() + assert_matches_type(EvalRetrieveResponse, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + await async_client.evals.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + eval = await async_client.evals.update( + eval_id="eval_id", + ) + assert_matches_type(EvalUpdateResponse, eval, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: + eval = await async_client.evals.update( + eval_id="eval_id", + metadata={"foo": "string"}, + name="name", + ) + assert_matches_type(EvalUpdateResponse, eval, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.with_raw_response.update( + eval_id="eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(EvalUpdateResponse, eval, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.with_streaming_response.update( + eval_id="eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = await response.parse() + assert_matches_type(EvalUpdateResponse, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + await async_client.evals.with_raw_response.update( + eval_id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + eval = await async_client.evals.list() + assert_matches_type(AsyncCursorPage[EvalListResponse], eval, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + eval = await async_client.evals.list( + after="after", + limit=0, + order="asc", + order_by="created_at", + ) + assert_matches_type(AsyncCursorPage[EvalListResponse], eval, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(AsyncCursorPage[EvalListResponse], eval, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = await response.parse() + assert_matches_type(AsyncCursorPage[EvalListResponse], eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + eval = await async_client.evals.delete( + "eval_id", + ) + assert_matches_type(EvalDeleteResponse, eval, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.evals.with_raw_response.delete( + "eval_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(EvalDeleteResponse, eval, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.evals.with_streaming_response.delete( + "eval_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = await response.parse() + assert_matches_type(EvalDeleteResponse, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_id` but received ''"): + await async_client.evals.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py index 882f0ddbe7..940ac97022 100644 --- a/tests/api_resources/test_files.py +++ b/tests/api_resources/test_files.py @@ -13,7 +13,7 @@ from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type from openai.types import FileObject, FileDeleted -from openai.pagination import SyncPage, AsyncPage +from openai.pagination import SyncCursorPage, AsyncCursorPage # pyright: reportDeprecated=false @@ -26,15 +26,27 @@ class TestFiles: @parametrize def test_method_create(self, client: OpenAI) -> None: file = client.files.create( - file=b"raw file contents", + file=b"Example data", purpose="assistants", ) assert_matches_type(FileObject, file, path=["response"]) + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + file = client.files.create( + file=b"Example data", + purpose="assistants", + expires_after={ + "anchor": "created_at", + "seconds": 3600, + }, + ) + assert_matches_type(FileObject, file, path=["response"]) + @parametrize def test_raw_response_create(self, client: OpenAI) -> None: response = client.files.with_raw_response.create( - file=b"raw file contents", + file=b"Example data", purpose="assistants", ) @@ -46,7 +58,7 @@ def test_raw_response_create(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create(self, client: OpenAI) -> None: with client.files.with_streaming_response.create( - file=b"raw file contents", + file=b"Example data", purpose="assistants", ) as response: assert not response.is_closed @@ -98,14 +110,17 @@ def test_path_params_retrieve(self, client: OpenAI) -> None: @parametrize def test_method_list(self, client: OpenAI) -> None: file = client.files.list() - assert_matches_type(SyncPage[FileObject], file, path=["response"]) + assert_matches_type(SyncCursorPage[FileObject], file, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: OpenAI) -> None: file = client.files.list( - purpose="string", + after="after", + limit=0, + order="asc", + purpose="purpose", ) - assert_matches_type(SyncPage[FileObject], file, path=["response"]) + assert_matches_type(SyncCursorPage[FileObject], file, path=["response"]) @parametrize def test_raw_response_list(self, client: OpenAI) -> None: @@ -114,7 +129,7 @@ def test_raw_response_list(self, client: OpenAI) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(SyncPage[FileObject], file, path=["response"]) + assert_matches_type(SyncCursorPage[FileObject], file, path=["response"]) @parametrize def test_streaming_response_list(self, client: OpenAI) -> None: @@ -123,7 +138,7 @@ def test_streaming_response_list(self, client: OpenAI) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(SyncPage[FileObject], file, path=["response"]) + assert_matches_type(SyncCursorPage[FileObject], file, path=["response"]) assert cast(Any, response.is_closed) is True @@ -257,20 +272,34 @@ def test_path_params_retrieve_content(self, client: OpenAI) -> None: class TestAsyncFiles: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: file = await async_client.files.create( - file=b"raw file contents", + file=b"Example data", + purpose="assistants", + ) + assert_matches_type(FileObject, file, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + file = await async_client.files.create( + file=b"Example data", purpose="assistants", + expires_after={ + "anchor": "created_at", + "seconds": 3600, + }, ) assert_matches_type(FileObject, file, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: response = await async_client.files.with_raw_response.create( - file=b"raw file contents", + file=b"Example data", purpose="assistants", ) @@ -282,7 +311,7 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: async with async_client.files.with_streaming_response.create( - file=b"raw file contents", + file=b"Example data", purpose="assistants", ) as response: assert not response.is_closed @@ -334,14 +363,17 @@ async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_method_list(self, async_client: AsyncOpenAI) -> None: file = await async_client.files.list() - assert_matches_type(AsyncPage[FileObject], file, path=["response"]) + assert_matches_type(AsyncCursorPage[FileObject], file, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: file = await async_client.files.list( - purpose="string", + after="after", + limit=0, + order="asc", + purpose="purpose", ) - assert_matches_type(AsyncPage[FileObject], file, path=["response"]) + assert_matches_type(AsyncCursorPage[FileObject], file, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: @@ -350,7 +382,7 @@ async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(AsyncPage[FileObject], file, path=["response"]) + assert_matches_type(AsyncCursorPage[FileObject], file, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: @@ -359,7 +391,7 @@ async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = await response.parse() - assert_matches_type(AsyncPage[FileObject], file, path=["response"]) + assert_matches_type(AsyncCursorPage[FileObject], file, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_images.py b/tests/api_resources/test_images.py index 9bc9719bc5..fa8054c01d 100644 --- a/tests/api_resources/test_images.py +++ b/tests/api_resources/test_images.py @@ -20,18 +20,18 @@ class TestImages: @parametrize def test_method_create_variation(self, client: OpenAI) -> None: image = client.images.create_variation( - image=b"raw file contents", + image=b"Example data", ) assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize def test_method_create_variation_with_all_params(self, client: OpenAI) -> None: image = client.images.create_variation( - image=b"raw file contents", - model="dall-e-2", + image=b"Example data", + model="gpt-image-1", n=1, response_format="url", - size="256x256", + size="1024x1024", user="user-1234", ) assert_matches_type(ImagesResponse, image, path=["response"]) @@ -39,7 +39,7 @@ def test_method_create_variation_with_all_params(self, client: OpenAI) -> None: @parametrize def test_raw_response_create_variation(self, client: OpenAI) -> None: response = client.images.with_raw_response.create_variation( - image=b"raw file contents", + image=b"Example data", ) assert response.is_closed is True @@ -50,7 +50,7 @@ def test_raw_response_create_variation(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create_variation(self, client: OpenAI) -> None: with client.images.with_streaming_response.create_variation( - image=b"raw file contents", + image=b"Example data", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -61,31 +61,38 @@ def test_streaming_response_create_variation(self, client: OpenAI) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_edit(self, client: OpenAI) -> None: + def test_method_edit_overload_1(self, client: OpenAI) -> None: image = client.images.edit( - image=b"raw file contents", + image=b"Example data", prompt="A cute baby sea otter wearing a beret", ) assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - def test_method_edit_with_all_params(self, client: OpenAI) -> None: + def test_method_edit_with_all_params_overload_1(self, client: OpenAI) -> None: image = client.images.edit( - image=b"raw file contents", + image=b"Example data", prompt="A cute baby sea otter wearing a beret", - mask=b"raw file contents", - model="dall-e-2", + background="transparent", + input_fidelity="high", + mask=b"Example data", + model="gpt-image-2", n=1, + output_compression=100, + output_format="png", + partial_images=1, + quality="high", response_format="url", size="256x256", + stream=False, user="user-1234", ) assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - def test_raw_response_edit(self, client: OpenAI) -> None: + def test_raw_response_edit_overload_1(self, client: OpenAI) -> None: response = client.images.with_raw_response.edit( - image=b"raw file contents", + image=b"Example data", prompt="A cute baby sea otter wearing a beret", ) @@ -95,9 +102,9 @@ def test_raw_response_edit(self, client: OpenAI) -> None: assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - def test_streaming_response_edit(self, client: OpenAI) -> None: + def test_streaming_response_edit_overload_1(self, client: OpenAI) -> None: with client.images.with_streaming_response.edit( - image=b"raw file contents", + image=b"Example data", prompt="A cute baby sea otter wearing a beret", ) as response: assert not response.is_closed @@ -109,28 +116,91 @@ def test_streaming_response_edit(self, client: OpenAI) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_generate(self, client: OpenAI) -> None: + def test_method_edit_overload_2(self, client: OpenAI) -> None: + image_stream = client.images.edit( + image=b"Example data", + prompt="A cute baby sea otter wearing a beret", + stream=True, + ) + image_stream.response.close() + + @parametrize + def test_method_edit_with_all_params_overload_2(self, client: OpenAI) -> None: + image_stream = client.images.edit( + image=b"Example data", + prompt="A cute baby sea otter wearing a beret", + stream=True, + background="transparent", + input_fidelity="high", + mask=b"Example data", + model="gpt-image-2", + n=1, + output_compression=100, + output_format="png", + partial_images=1, + quality="high", + response_format="url", + size="256x256", + user="user-1234", + ) + image_stream.response.close() + + @parametrize + def test_raw_response_edit_overload_2(self, client: OpenAI) -> None: + response = client.images.with_raw_response.edit( + image=b"Example data", + prompt="A cute baby sea otter wearing a beret", + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @parametrize + def test_streaming_response_edit_overload_2(self, client: OpenAI) -> None: + with client.images.with_streaming_response.edit( + image=b"Example data", + prompt="A cute baby sea otter wearing a beret", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_generate_overload_1(self, client: OpenAI) -> None: image = client.images.generate( prompt="A cute baby sea otter", ) assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - def test_method_generate_with_all_params(self, client: OpenAI) -> None: + def test_method_generate_with_all_params_overload_1(self, client: OpenAI) -> None: image = client.images.generate( prompt="A cute baby sea otter", - model="dall-e-3", + background="transparent", + model="gpt-image-2", + moderation="low", n=1, - quality="standard", + output_compression=100, + output_format="png", + partial_images=1, + quality="medium", response_format="url", - size="256x256", + size="auto", + stream=False, style="vivid", user="user-1234", ) assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - def test_raw_response_generate(self, client: OpenAI) -> None: + def test_raw_response_generate_overload_1(self, client: OpenAI) -> None: response = client.images.with_raw_response.generate( prompt="A cute baby sea otter", ) @@ -141,7 +211,7 @@ def test_raw_response_generate(self, client: OpenAI) -> None: assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - def test_streaming_response_generate(self, client: OpenAI) -> None: + def test_streaming_response_generate_overload_1(self, client: OpenAI) -> None: with client.images.with_streaming_response.generate( prompt="A cute baby sea otter", ) as response: @@ -153,25 +223,80 @@ def test_streaming_response_generate(self, client: OpenAI) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_generate_overload_2(self, client: OpenAI) -> None: + image_stream = client.images.generate( + prompt="A cute baby sea otter", + stream=True, + ) + image_stream.response.close() + + @parametrize + def test_method_generate_with_all_params_overload_2(self, client: OpenAI) -> None: + image_stream = client.images.generate( + prompt="A cute baby sea otter", + stream=True, + background="transparent", + model="gpt-image-2", + moderation="low", + n=1, + output_compression=100, + output_format="png", + partial_images=1, + quality="medium", + response_format="url", + size="auto", + style="vivid", + user="user-1234", + ) + image_stream.response.close() + + @parametrize + def test_raw_response_generate_overload_2(self, client: OpenAI) -> None: + response = client.images.with_raw_response.generate( + prompt="A cute baby sea otter", + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @parametrize + def test_streaming_response_generate_overload_2(self, client: OpenAI) -> None: + with client.images.with_streaming_response.generate( + prompt="A cute baby sea otter", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True + class TestAsyncImages: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create_variation(self, async_client: AsyncOpenAI) -> None: image = await async_client.images.create_variation( - image=b"raw file contents", + image=b"Example data", ) assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize async def test_method_create_variation_with_all_params(self, async_client: AsyncOpenAI) -> None: image = await async_client.images.create_variation( - image=b"raw file contents", - model="dall-e-2", + image=b"Example data", + model="gpt-image-1", n=1, response_format="url", - size="256x256", + size="1024x1024", user="user-1234", ) assert_matches_type(ImagesResponse, image, path=["response"]) @@ -179,7 +304,7 @@ async def test_method_create_variation_with_all_params(self, async_client: Async @parametrize async def test_raw_response_create_variation(self, async_client: AsyncOpenAI) -> None: response = await async_client.images.with_raw_response.create_variation( - image=b"raw file contents", + image=b"Example data", ) assert response.is_closed is True @@ -190,7 +315,7 @@ async def test_raw_response_create_variation(self, async_client: AsyncOpenAI) -> @parametrize async def test_streaming_response_create_variation(self, async_client: AsyncOpenAI) -> None: async with async_client.images.with_streaming_response.create_variation( - image=b"raw file contents", + image=b"Example data", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -201,31 +326,38 @@ async def test_streaming_response_create_variation(self, async_client: AsyncOpen assert cast(Any, response.is_closed) is True @parametrize - async def test_method_edit(self, async_client: AsyncOpenAI) -> None: + async def test_method_edit_overload_1(self, async_client: AsyncOpenAI) -> None: image = await async_client.images.edit( - image=b"raw file contents", + image=b"Example data", prompt="A cute baby sea otter wearing a beret", ) assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - async def test_method_edit_with_all_params(self, async_client: AsyncOpenAI) -> None: + async def test_method_edit_with_all_params_overload_1(self, async_client: AsyncOpenAI) -> None: image = await async_client.images.edit( - image=b"raw file contents", + image=b"Example data", prompt="A cute baby sea otter wearing a beret", - mask=b"raw file contents", - model="dall-e-2", + background="transparent", + input_fidelity="high", + mask=b"Example data", + model="gpt-image-2", n=1, + output_compression=100, + output_format="png", + partial_images=1, + quality="high", response_format="url", size="256x256", + stream=False, user="user-1234", ) assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - async def test_raw_response_edit(self, async_client: AsyncOpenAI) -> None: + async def test_raw_response_edit_overload_1(self, async_client: AsyncOpenAI) -> None: response = await async_client.images.with_raw_response.edit( - image=b"raw file contents", + image=b"Example data", prompt="A cute baby sea otter wearing a beret", ) @@ -235,9 +367,9 @@ async def test_raw_response_edit(self, async_client: AsyncOpenAI) -> None: assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - async def test_streaming_response_edit(self, async_client: AsyncOpenAI) -> None: + async def test_streaming_response_edit_overload_1(self, async_client: AsyncOpenAI) -> None: async with async_client.images.with_streaming_response.edit( - image=b"raw file contents", + image=b"Example data", prompt="A cute baby sea otter wearing a beret", ) as response: assert not response.is_closed @@ -249,28 +381,91 @@ async def test_streaming_response_edit(self, async_client: AsyncOpenAI) -> None: assert cast(Any, response.is_closed) is True @parametrize - async def test_method_generate(self, async_client: AsyncOpenAI) -> None: + async def test_method_edit_overload_2(self, async_client: AsyncOpenAI) -> None: + image_stream = await async_client.images.edit( + image=b"Example data", + prompt="A cute baby sea otter wearing a beret", + stream=True, + ) + await image_stream.response.aclose() + + @parametrize + async def test_method_edit_with_all_params_overload_2(self, async_client: AsyncOpenAI) -> None: + image_stream = await async_client.images.edit( + image=b"Example data", + prompt="A cute baby sea otter wearing a beret", + stream=True, + background="transparent", + input_fidelity="high", + mask=b"Example data", + model="gpt-image-2", + n=1, + output_compression=100, + output_format="png", + partial_images=1, + quality="high", + response_format="url", + size="256x256", + user="user-1234", + ) + await image_stream.response.aclose() + + @parametrize + async def test_raw_response_edit_overload_2(self, async_client: AsyncOpenAI) -> None: + response = await async_client.images.with_raw_response.edit( + image=b"Example data", + prompt="A cute baby sea otter wearing a beret", + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + await stream.close() + + @parametrize + async def test_streaming_response_edit_overload_2(self, async_client: AsyncOpenAI) -> None: + async with async_client.images.with_streaming_response.edit( + image=b"Example data", + prompt="A cute baby sea otter wearing a beret", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_generate_overload_1(self, async_client: AsyncOpenAI) -> None: image = await async_client.images.generate( prompt="A cute baby sea otter", ) assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - async def test_method_generate_with_all_params(self, async_client: AsyncOpenAI) -> None: + async def test_method_generate_with_all_params_overload_1(self, async_client: AsyncOpenAI) -> None: image = await async_client.images.generate( prompt="A cute baby sea otter", - model="dall-e-3", + background="transparent", + model="gpt-image-2", + moderation="low", n=1, - quality="standard", + output_compression=100, + output_format="png", + partial_images=1, + quality="medium", response_format="url", - size="256x256", + size="auto", + stream=False, style="vivid", user="user-1234", ) assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - async def test_raw_response_generate(self, async_client: AsyncOpenAI) -> None: + async def test_raw_response_generate_overload_1(self, async_client: AsyncOpenAI) -> None: response = await async_client.images.with_raw_response.generate( prompt="A cute baby sea otter", ) @@ -281,7 +476,7 @@ async def test_raw_response_generate(self, async_client: AsyncOpenAI) -> None: assert_matches_type(ImagesResponse, image, path=["response"]) @parametrize - async def test_streaming_response_generate(self, async_client: AsyncOpenAI) -> None: + async def test_streaming_response_generate_overload_1(self, async_client: AsyncOpenAI) -> None: async with async_client.images.with_streaming_response.generate( prompt="A cute baby sea otter", ) as response: @@ -292,3 +487,56 @@ async def test_streaming_response_generate(self, async_client: AsyncOpenAI) -> N assert_matches_type(ImagesResponse, image, path=["response"]) assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_generate_overload_2(self, async_client: AsyncOpenAI) -> None: + image_stream = await async_client.images.generate( + prompt="A cute baby sea otter", + stream=True, + ) + await image_stream.response.aclose() + + @parametrize + async def test_method_generate_with_all_params_overload_2(self, async_client: AsyncOpenAI) -> None: + image_stream = await async_client.images.generate( + prompt="A cute baby sea otter", + stream=True, + background="transparent", + model="gpt-image-2", + moderation="low", + n=1, + output_compression=100, + output_format="png", + partial_images=1, + quality="medium", + response_format="url", + size="auto", + style="vivid", + user="user-1234", + ) + await image_stream.response.aclose() + + @parametrize + async def test_raw_response_generate_overload_2(self, async_client: AsyncOpenAI) -> None: + response = await async_client.images.with_raw_response.generate( + prompt="A cute baby sea otter", + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + await stream.close() + + @parametrize + async def test_streaming_response_generate_overload_2(self, async_client: AsyncOpenAI) -> None: + async with async_client.images.with_streaming_response.generate( + prompt="A cute baby sea otter", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_models.py b/tests/api_resources/test_models.py index 8791507c3e..cf70871ade 100644 --- a/tests/api_resources/test_models.py +++ b/tests/api_resources/test_models.py @@ -121,7 +121,9 @@ def test_path_params_delete(self, client: OpenAI) -> None: class TestAsyncModels: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: diff --git a/tests/api_resources/test_moderations.py b/tests/api_resources/test_moderations.py index 94b9ecd31b..8487d4a7a6 100644 --- a/tests/api_resources/test_moderations.py +++ b/tests/api_resources/test_moderations.py @@ -28,7 +28,7 @@ def test_method_create(self, client: OpenAI) -> None: def test_method_create_with_all_params(self, client: OpenAI) -> None: moderation = client.moderations.create( input="I want to kill them.", - model="text-moderation-stable", + model="omni-moderation-latest", ) assert_matches_type(ModerationCreateResponse, moderation, path=["response"]) @@ -58,7 +58,9 @@ def test_streaming_response_create(self, client: OpenAI) -> None: class TestAsyncModerations: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: @@ -71,7 +73,7 @@ async def test_method_create(self, async_client: AsyncOpenAI) -> None: async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: moderation = await async_client.moderations.create( input="I want to kill them.", - model="text-moderation-stable", + model="omni-moderation-latest", ) assert_matches_type(ModerationCreateResponse, moderation, path=["response"]) diff --git a/tests/api_resources/test_realtime.py b/tests/api_resources/test_realtime.py new file mode 100644 index 0000000000..2b0c7f7d8d --- /dev/null +++ b/tests/api_resources/test_realtime.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os + +import pytest + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRealtime: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + +class TestAsyncRealtime: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) diff --git a/tests/api_resources/test_responses.py b/tests/api_resources/test_responses.py new file mode 100644 index 0000000000..dc6083e78c --- /dev/null +++ b/tests/api_resources/test_responses.py @@ -0,0 +1,835 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai._utils import assert_signatures_in_sync +from openai.types.responses import ( + Response, + CompactedResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestResponses: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create_overload_1(self, client: OpenAI) -> None: + response = client.responses.create() + assert_matches_type(Response, response, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_1(self, client: OpenAI) -> None: + response = client.responses.create( + background=True, + context_management=[ + { + "type": "type", + "compact_threshold": 1000, + } + ], + conversation="string", + include=["file_search_call.results"], + input="string", + instructions="instructions", + max_output_tokens=16, + max_tool_calls=0, + metadata={"foo": "string"}, + model="gpt-5.1", + moderation={"model": "model"}, + parallel_tool_calls=True, + previous_response_id="previous_response_id", + prompt={ + "id": "id", + "variables": {"foo": "string"}, + "version": "version", + }, + prompt_cache_key="prompt-cache-key-1234", + prompt_cache_retention="in_memory", + reasoning={ + "effort": "none", + "generate_summary": "auto", + "summary": "auto", + }, + safety_identifier="safety-identifier-1234", + service_tier="auto", + store=True, + stream=False, + stream_options={"include_obfuscation": True}, + temperature=1, + text={ + "format": {"type": "text"}, + "verbosity": "low", + }, + tool_choice="none", + tools=[ + { + "name": "name", + "parameters": {"foo": "bar"}, + "strict": True, + "type": "function", + "defer_loading": True, + "description": "description", + } + ], + top_logprobs=0, + top_p=1, + truncation="auto", + user="user-1234", + ) + assert_matches_type(Response, response, path=["response"]) + + @parametrize + def test_raw_response_create_overload_1(self, client: OpenAI) -> None: + http_response = client.responses.with_raw_response.create() + + assert http_response.is_closed is True + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + response = http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_1(self, client: OpenAI) -> None: + with client.responses.with_streaming_response.create() as http_response: + assert not http_response.is_closed + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + + response = http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + assert cast(Any, http_response.is_closed) is True + + @parametrize + def test_method_create_overload_2(self, client: OpenAI) -> None: + response_stream = client.responses.create( + stream=True, + ) + response_stream.response.close() + + @parametrize + def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: + response_stream = client.responses.create( + stream=True, + background=True, + context_management=[ + { + "type": "type", + "compact_threshold": 1000, + } + ], + conversation="string", + include=["file_search_call.results"], + input="string", + instructions="instructions", + max_output_tokens=16, + max_tool_calls=0, + metadata={"foo": "string"}, + model="gpt-5.1", + moderation={"model": "model"}, + parallel_tool_calls=True, + previous_response_id="previous_response_id", + prompt={ + "id": "id", + "variables": {"foo": "string"}, + "version": "version", + }, + prompt_cache_key="prompt-cache-key-1234", + prompt_cache_retention="in_memory", + reasoning={ + "effort": "none", + "generate_summary": "auto", + "summary": "auto", + }, + safety_identifier="safety-identifier-1234", + service_tier="auto", + store=True, + stream_options={"include_obfuscation": True}, + temperature=1, + text={ + "format": {"type": "text"}, + "verbosity": "low", + }, + tool_choice="none", + tools=[ + { + "name": "name", + "parameters": {"foo": "bar"}, + "strict": True, + "type": "function", + "defer_loading": True, + "description": "description", + } + ], + top_logprobs=0, + top_p=1, + truncation="auto", + user="user-1234", + ) + response_stream.response.close() + + @parametrize + def test_raw_response_create_overload_2(self, client: OpenAI) -> None: + response = client.responses.with_raw_response.create( + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @parametrize + def test_streaming_response_create_overload_2(self, client: OpenAI) -> None: + with client.responses.with_streaming_response.create( + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve_overload_1(self, client: OpenAI) -> None: + response = client.responses.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + ) + assert_matches_type(Response, response, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params_overload_1(self, client: OpenAI) -> None: + response = client.responses.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + include=["file_search_call.results"], + include_obfuscation=True, + starting_after=0, + stream=False, + ) + assert_matches_type(Response, response, path=["response"]) + + @parametrize + def test_raw_response_retrieve_overload_1(self, client: OpenAI) -> None: + http_response = client.responses.with_raw_response.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + ) + + assert http_response.is_closed is True + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + response = http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_overload_1(self, client: OpenAI) -> None: + with client.responses.with_streaming_response.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + ) as http_response: + assert not http_response.is_closed + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + + response = http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + assert cast(Any, http_response.is_closed) is True + + @parametrize + def test_path_params_retrieve_overload_1(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `response_id` but received ''"): + client.responses.with_raw_response.retrieve( + response_id="", + ) + + @parametrize + def test_method_retrieve_overload_2(self, client: OpenAI) -> None: + response_stream = client.responses.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + stream=True, + ) + response_stream.response.close() + + @parametrize + def test_method_retrieve_with_all_params_overload_2(self, client: OpenAI) -> None: + response_stream = client.responses.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + stream=True, + include=["file_search_call.results"], + include_obfuscation=True, + starting_after=0, + ) + response_stream.response.close() + + @parametrize + def test_raw_response_retrieve_overload_2(self, client: OpenAI) -> None: + response = client.responses.with_raw_response.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @parametrize + def test_streaming_response_retrieve_overload_2(self, client: OpenAI) -> None: + with client.responses.with_streaming_response.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_overload_2(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `response_id` but received ''"): + client.responses.with_raw_response.retrieve( + response_id="", + stream=True, + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + response = client.responses.delete( + "resp_677efb5139a88190b512bc3fef8e535d", + ) + assert response is None + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + http_response = client.responses.with_raw_response.delete( + "resp_677efb5139a88190b512bc3fef8e535d", + ) + + assert http_response.is_closed is True + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + response = http_response.parse() + assert response is None + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.responses.with_streaming_response.delete( + "resp_677efb5139a88190b512bc3fef8e535d", + ) as http_response: + assert not http_response.is_closed + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + + response = http_response.parse() + assert response is None + + assert cast(Any, http_response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `response_id` but received ''"): + client.responses.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_cancel(self, client: OpenAI) -> None: + response = client.responses.cancel( + "resp_677efb5139a88190b512bc3fef8e535d", + ) + assert_matches_type(Response, response, path=["response"]) + + @parametrize + def test_raw_response_cancel(self, client: OpenAI) -> None: + http_response = client.responses.with_raw_response.cancel( + "resp_677efb5139a88190b512bc3fef8e535d", + ) + + assert http_response.is_closed is True + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + response = http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + @parametrize + def test_streaming_response_cancel(self, client: OpenAI) -> None: + with client.responses.with_streaming_response.cancel( + "resp_677efb5139a88190b512bc3fef8e535d", + ) as http_response: + assert not http_response.is_closed + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + + response = http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + assert cast(Any, http_response.is_closed) is True + + @parametrize + def test_path_params_cancel(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `response_id` but received ''"): + client.responses.with_raw_response.cancel( + "", + ) + + @parametrize + def test_method_compact(self, client: OpenAI) -> None: + response = client.responses.compact( + model="gpt-5.4", + ) + assert_matches_type(CompactedResponse, response, path=["response"]) + + @parametrize + def test_method_compact_with_all_params(self, client: OpenAI) -> None: + response = client.responses.compact( + model="gpt-5.4", + input="string", + instructions="instructions", + previous_response_id="resp_123", + prompt_cache_key="prompt_cache_key", + prompt_cache_retention="in_memory", + service_tier="auto", + ) + assert_matches_type(CompactedResponse, response, path=["response"]) + + @parametrize + def test_raw_response_compact(self, client: OpenAI) -> None: + http_response = client.responses.with_raw_response.compact( + model="gpt-5.4", + ) + + assert http_response.is_closed is True + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + response = http_response.parse() + assert_matches_type(CompactedResponse, response, path=["response"]) + + @parametrize + def test_streaming_response_compact(self, client: OpenAI) -> None: + with client.responses.with_streaming_response.compact( + model="gpt-5.4", + ) as http_response: + assert not http_response.is_closed + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + + response = http_response.parse() + assert_matches_type(CompactedResponse, response, path=["response"]) + + assert cast(Any, http_response.is_closed) is True + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +def test_parse_method_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: + checking_client: OpenAI | AsyncOpenAI = client if sync else async_client + + assert_signatures_in_sync( + checking_client.responses.create, + checking_client.responses.parse, + exclude_params={"stream", "tools"}, + ) + + +class TestAsyncResponses: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.create() + assert_matches_type(Response, response, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_1(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.create( + background=True, + context_management=[ + { + "type": "type", + "compact_threshold": 1000, + } + ], + conversation="string", + include=["file_search_call.results"], + input="string", + instructions="instructions", + max_output_tokens=16, + max_tool_calls=0, + metadata={"foo": "string"}, + model="gpt-5.1", + moderation={"model": "model"}, + parallel_tool_calls=True, + previous_response_id="previous_response_id", + prompt={ + "id": "id", + "variables": {"foo": "string"}, + "version": "version", + }, + prompt_cache_key="prompt-cache-key-1234", + prompt_cache_retention="in_memory", + reasoning={ + "effort": "none", + "generate_summary": "auto", + "summary": "auto", + }, + safety_identifier="safety-identifier-1234", + service_tier="auto", + store=True, + stream=False, + stream_options={"include_obfuscation": True}, + temperature=1, + text={ + "format": {"type": "text"}, + "verbosity": "low", + }, + tool_choice="none", + tools=[ + { + "name": "name", + "parameters": {"foo": "bar"}, + "strict": True, + "type": "function", + "defer_loading": True, + "description": "description", + } + ], + top_logprobs=0, + top_p=1, + truncation="auto", + user="user-1234", + ) + assert_matches_type(Response, response, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_1(self, async_client: AsyncOpenAI) -> None: + http_response = await async_client.responses.with_raw_response.create() + + assert http_response.is_closed is True + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + response = http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_1(self, async_client: AsyncOpenAI) -> None: + async with async_client.responses.with_streaming_response.create() as http_response: + assert not http_response.is_closed + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + + response = await http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + assert cast(Any, http_response.is_closed) is True + + @parametrize + async def test_method_create_overload_2(self, async_client: AsyncOpenAI) -> None: + response_stream = await async_client.responses.create( + stream=True, + ) + await response_stream.response.aclose() + + @parametrize + async def test_method_create_with_all_params_overload_2(self, async_client: AsyncOpenAI) -> None: + response_stream = await async_client.responses.create( + stream=True, + background=True, + context_management=[ + { + "type": "type", + "compact_threshold": 1000, + } + ], + conversation="string", + include=["file_search_call.results"], + input="string", + instructions="instructions", + max_output_tokens=16, + max_tool_calls=0, + metadata={"foo": "string"}, + model="gpt-5.1", + moderation={"model": "model"}, + parallel_tool_calls=True, + previous_response_id="previous_response_id", + prompt={ + "id": "id", + "variables": {"foo": "string"}, + "version": "version", + }, + prompt_cache_key="prompt-cache-key-1234", + prompt_cache_retention="in_memory", + reasoning={ + "effort": "none", + "generate_summary": "auto", + "summary": "auto", + }, + safety_identifier="safety-identifier-1234", + service_tier="auto", + store=True, + stream_options={"include_obfuscation": True}, + temperature=1, + text={ + "format": {"type": "text"}, + "verbosity": "low", + }, + tool_choice="none", + tools=[ + { + "name": "name", + "parameters": {"foo": "bar"}, + "strict": True, + "type": "function", + "defer_loading": True, + "description": "description", + } + ], + top_logprobs=0, + top_p=1, + truncation="auto", + user="user-1234", + ) + await response_stream.response.aclose() + + @parametrize + async def test_raw_response_create_overload_2(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.with_raw_response.create( + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + await stream.close() + + @parametrize + async def test_streaming_response_create_overload_2(self, async_client: AsyncOpenAI) -> None: + async with async_client.responses.with_streaming_response.create( + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve_overload_1(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + ) + assert_matches_type(Response, response, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params_overload_1(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + include=["file_search_call.results"], + include_obfuscation=True, + starting_after=0, + stream=False, + ) + assert_matches_type(Response, response, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_overload_1(self, async_client: AsyncOpenAI) -> None: + http_response = await async_client.responses.with_raw_response.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + ) + + assert http_response.is_closed is True + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + response = http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_overload_1(self, async_client: AsyncOpenAI) -> None: + async with async_client.responses.with_streaming_response.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + ) as http_response: + assert not http_response.is_closed + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + + response = await http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + assert cast(Any, http_response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_overload_1(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `response_id` but received ''"): + await async_client.responses.with_raw_response.retrieve( + response_id="", + ) + + @parametrize + async def test_method_retrieve_overload_2(self, async_client: AsyncOpenAI) -> None: + response_stream = await async_client.responses.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + stream=True, + ) + await response_stream.response.aclose() + + @parametrize + async def test_method_retrieve_with_all_params_overload_2(self, async_client: AsyncOpenAI) -> None: + response_stream = await async_client.responses.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + stream=True, + include=["file_search_call.results"], + include_obfuscation=True, + starting_after=0, + ) + await response_stream.response.aclose() + + @parametrize + async def test_raw_response_retrieve_overload_2(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.with_raw_response.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + await stream.close() + + @parametrize + async def test_streaming_response_retrieve_overload_2(self, async_client: AsyncOpenAI) -> None: + async with async_client.responses.with_streaming_response.retrieve( + response_id="resp_677efb5139a88190b512bc3fef8e535d", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_overload_2(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `response_id` but received ''"): + await async_client.responses.with_raw_response.retrieve( + response_id="", + stream=True, + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.delete( + "resp_677efb5139a88190b512bc3fef8e535d", + ) + assert response is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + http_response = await async_client.responses.with_raw_response.delete( + "resp_677efb5139a88190b512bc3fef8e535d", + ) + + assert http_response.is_closed is True + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + response = http_response.parse() + assert response is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.responses.with_streaming_response.delete( + "resp_677efb5139a88190b512bc3fef8e535d", + ) as http_response: + assert not http_response.is_closed + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + + response = await http_response.parse() + assert response is None + + assert cast(Any, http_response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `response_id` but received ''"): + await async_client.responses.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_cancel(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.cancel( + "resp_677efb5139a88190b512bc3fef8e535d", + ) + assert_matches_type(Response, response, path=["response"]) + + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncOpenAI) -> None: + http_response = await async_client.responses.with_raw_response.cancel( + "resp_677efb5139a88190b512bc3fef8e535d", + ) + + assert http_response.is_closed is True + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + response = http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncOpenAI) -> None: + async with async_client.responses.with_streaming_response.cancel( + "resp_677efb5139a88190b512bc3fef8e535d", + ) as http_response: + assert not http_response.is_closed + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + + response = await http_response.parse() + assert_matches_type(Response, response, path=["response"]) + + assert cast(Any, http_response.is_closed) is True + + @parametrize + async def test_path_params_cancel(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `response_id` but received ''"): + await async_client.responses.with_raw_response.cancel( + "", + ) + + @parametrize + async def test_method_compact(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.compact( + model="gpt-5.4", + ) + assert_matches_type(CompactedResponse, response, path=["response"]) + + @parametrize + async def test_method_compact_with_all_params(self, async_client: AsyncOpenAI) -> None: + response = await async_client.responses.compact( + model="gpt-5.4", + input="string", + instructions="instructions", + previous_response_id="resp_123", + prompt_cache_key="prompt_cache_key", + prompt_cache_retention="in_memory", + service_tier="auto", + ) + assert_matches_type(CompactedResponse, response, path=["response"]) + + @parametrize + async def test_raw_response_compact(self, async_client: AsyncOpenAI) -> None: + http_response = await async_client.responses.with_raw_response.compact( + model="gpt-5.4", + ) + + assert http_response.is_closed is True + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + response = http_response.parse() + assert_matches_type(CompactedResponse, response, path=["response"]) + + @parametrize + async def test_streaming_response_compact(self, async_client: AsyncOpenAI) -> None: + async with async_client.responses.with_streaming_response.compact( + model="gpt-5.4", + ) as http_response: + assert not http_response.is_closed + assert http_response.http_request.headers.get("X-Stainless-Lang") == "python" + + response = await http_response.parse() + assert_matches_type(CompactedResponse, response, path=["response"]) + + assert cast(Any, http_response.is_closed) is True diff --git a/tests/api_resources/test_skills.py b/tests/api_resources/test_skills.py new file mode 100644 index 0000000000..6708fb3cbf --- /dev/null +++ b/tests/api_resources/test_skills.py @@ -0,0 +1,393 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types import Skill, DeletedSkill +from openai.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSkills: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + skill = client.skills.create() + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + skill = client.skills.create( + files=[b"Example data"], + ) + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.skills.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + skill = response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.skills.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + skill = response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + skill = client.skills.retrieve( + "skill_123", + ) + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.skills.with_raw_response.retrieve( + "skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + skill = response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.skills.with_streaming_response.retrieve( + "skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + skill = response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + client.skills.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + skill = client.skills.update( + skill_id="skill_123", + default_version="default_version", + ) + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.skills.with_raw_response.update( + skill_id="skill_123", + default_version="default_version", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + skill = response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.skills.with_streaming_response.update( + skill_id="skill_123", + default_version="default_version", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + skill = response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + client.skills.with_raw_response.update( + skill_id="", + default_version="default_version", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + skill = client.skills.list() + assert_matches_type(SyncCursorPage[Skill], skill, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + skill = client.skills.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncCursorPage[Skill], skill, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.skills.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + skill = response.parse() + assert_matches_type(SyncCursorPage[Skill], skill, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.skills.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + skill = response.parse() + assert_matches_type(SyncCursorPage[Skill], skill, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + skill = client.skills.delete( + "skill_123", + ) + assert_matches_type(DeletedSkill, skill, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.skills.with_raw_response.delete( + "skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + skill = response.parse() + assert_matches_type(DeletedSkill, skill, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.skills.with_streaming_response.delete( + "skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + skill = response.parse() + assert_matches_type(DeletedSkill, skill, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + client.skills.with_raw_response.delete( + "", + ) + + +class TestAsyncSkills: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + skill = await async_client.skills.create() + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + skill = await async_client.skills.create( + files=[b"Example data"], + ) + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.skills.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + skill = response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.skills.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + skill = await response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + skill = await async_client.skills.retrieve( + "skill_123", + ) + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.skills.with_raw_response.retrieve( + "skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + skill = response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.skills.with_streaming_response.retrieve( + "skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + skill = await response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + await async_client.skills.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + skill = await async_client.skills.update( + skill_id="skill_123", + default_version="default_version", + ) + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.skills.with_raw_response.update( + skill_id="skill_123", + default_version="default_version", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + skill = response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.skills.with_streaming_response.update( + skill_id="skill_123", + default_version="default_version", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + skill = await response.parse() + assert_matches_type(Skill, skill, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + await async_client.skills.with_raw_response.update( + skill_id="", + default_version="default_version", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + skill = await async_client.skills.list() + assert_matches_type(AsyncCursorPage[Skill], skill, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + skill = await async_client.skills.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncCursorPage[Skill], skill, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.skills.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + skill = response.parse() + assert_matches_type(AsyncCursorPage[Skill], skill, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.skills.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + skill = await response.parse() + assert_matches_type(AsyncCursorPage[Skill], skill, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + skill = await async_client.skills.delete( + "skill_123", + ) + assert_matches_type(DeletedSkill, skill, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.skills.with_raw_response.delete( + "skill_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + skill = response.parse() + assert_matches_type(DeletedSkill, skill, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.skills.with_streaming_response.delete( + "skill_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + skill = await response.parse() + assert_matches_type(DeletedSkill, skill, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `skill_id` but received ''"): + await async_client.skills.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_uploads.py b/tests/api_resources/test_uploads.py index cb62df6b51..0e438a3c61 100644 --- a/tests/api_resources/test_uploads.py +++ b/tests/api_resources/test_uploads.py @@ -27,6 +27,20 @@ def test_method_create(self, client: OpenAI) -> None: ) assert_matches_type(Upload, upload, path=["response"]) + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + upload = client.uploads.create( + bytes=0, + filename="filename", + mime_type="mime_type", + purpose="assistants", + expires_after={ + "anchor": "created_at", + "seconds": 3600, + }, + ) + assert_matches_type(Upload, upload, path=["response"]) + @parametrize def test_raw_response_create(self, client: OpenAI) -> None: response = client.uploads.with_raw_response.create( @@ -99,7 +113,7 @@ def test_path_params_cancel(self, client: OpenAI) -> None: def test_method_complete(self, client: OpenAI) -> None: upload = client.uploads.complete( upload_id="upload_abc123", - part_ids=["string", "string", "string"], + part_ids=["string"], ) assert_matches_type(Upload, upload, path=["response"]) @@ -107,7 +121,7 @@ def test_method_complete(self, client: OpenAI) -> None: def test_method_complete_with_all_params(self, client: OpenAI) -> None: upload = client.uploads.complete( upload_id="upload_abc123", - part_ids=["string", "string", "string"], + part_ids=["string"], md5="md5", ) assert_matches_type(Upload, upload, path=["response"]) @@ -116,7 +130,7 @@ def test_method_complete_with_all_params(self, client: OpenAI) -> None: def test_raw_response_complete(self, client: OpenAI) -> None: response = client.uploads.with_raw_response.complete( upload_id="upload_abc123", - part_ids=["string", "string", "string"], + part_ids=["string"], ) assert response.is_closed is True @@ -128,7 +142,7 @@ def test_raw_response_complete(self, client: OpenAI) -> None: def test_streaming_response_complete(self, client: OpenAI) -> None: with client.uploads.with_streaming_response.complete( upload_id="upload_abc123", - part_ids=["string", "string", "string"], + part_ids=["string"], ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -143,12 +157,14 @@ def test_path_params_complete(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `upload_id` but received ''"): client.uploads.with_raw_response.complete( upload_id="", - part_ids=["string", "string", "string"], + part_ids=["string"], ) class TestAsyncUploads: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: @@ -160,6 +176,20 @@ async def test_method_create(self, async_client: AsyncOpenAI) -> None: ) assert_matches_type(Upload, upload, path=["response"]) + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + upload = await async_client.uploads.create( + bytes=0, + filename="filename", + mime_type="mime_type", + purpose="assistants", + expires_after={ + "anchor": "created_at", + "seconds": 3600, + }, + ) + assert_matches_type(Upload, upload, path=["response"]) + @parametrize async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: response = await async_client.uploads.with_raw_response.create( @@ -232,7 +262,7 @@ async def test_path_params_cancel(self, async_client: AsyncOpenAI) -> None: async def test_method_complete(self, async_client: AsyncOpenAI) -> None: upload = await async_client.uploads.complete( upload_id="upload_abc123", - part_ids=["string", "string", "string"], + part_ids=["string"], ) assert_matches_type(Upload, upload, path=["response"]) @@ -240,7 +270,7 @@ async def test_method_complete(self, async_client: AsyncOpenAI) -> None: async def test_method_complete_with_all_params(self, async_client: AsyncOpenAI) -> None: upload = await async_client.uploads.complete( upload_id="upload_abc123", - part_ids=["string", "string", "string"], + part_ids=["string"], md5="md5", ) assert_matches_type(Upload, upload, path=["response"]) @@ -249,7 +279,7 @@ async def test_method_complete_with_all_params(self, async_client: AsyncOpenAI) async def test_raw_response_complete(self, async_client: AsyncOpenAI) -> None: response = await async_client.uploads.with_raw_response.complete( upload_id="upload_abc123", - part_ids=["string", "string", "string"], + part_ids=["string"], ) assert response.is_closed is True @@ -261,7 +291,7 @@ async def test_raw_response_complete(self, async_client: AsyncOpenAI) -> None: async def test_streaming_response_complete(self, async_client: AsyncOpenAI) -> None: async with async_client.uploads.with_streaming_response.complete( upload_id="upload_abc123", - part_ids=["string", "string", "string"], + part_ids=["string"], ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -276,5 +306,5 @@ async def test_path_params_complete(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `upload_id` but received ''"): await async_client.uploads.with_raw_response.complete( upload_id="", - part_ids=["string", "string", "string"], + part_ids=["string"], ) diff --git a/tests/api_resources/beta/test_vector_stores.py b/tests/api_resources/test_vector_stores.py similarity index 58% rename from tests/api_resources/beta/test_vector_stores.py rename to tests/api_resources/test_vector_stores.py index 39fdb9d1d4..cce9c52cea 100644 --- a/tests/api_resources/beta/test_vector_stores.py +++ b/tests/api_resources/test_vector_stores.py @@ -9,11 +9,12 @@ from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type -from openai.pagination import SyncCursorPage, AsyncCursorPage -from openai.types.beta import ( +from openai.types import ( VectorStore, VectorStoreDeleted, + VectorStoreSearchResponse, ) +from openai.pagination import SyncPage, AsyncPage, SyncCursorPage, AsyncCursorPage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -23,26 +24,27 @@ class TestVectorStores: @parametrize def test_method_create(self, client: OpenAI) -> None: - vector_store = client.beta.vector_stores.create() + vector_store = client.vector_stores.create() assert_matches_type(VectorStore, vector_store, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: OpenAI) -> None: - vector_store = client.beta.vector_stores.create( + vector_store = client.vector_stores.create( chunking_strategy={"type": "auto"}, + description="description", expires_after={ "anchor": "last_active_at", "days": 1, }, - file_ids=["string", "string", "string"], - metadata={}, - name="string", + file_ids=["string"], + metadata={"foo": "string"}, + name="name", ) assert_matches_type(VectorStore, vector_store, path=["response"]) @parametrize def test_raw_response_create(self, client: OpenAI) -> None: - response = client.beta.vector_stores.with_raw_response.create() + response = client.vector_stores.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -51,7 +53,7 @@ def test_raw_response_create(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create(self, client: OpenAI) -> None: - with client.beta.vector_stores.with_streaming_response.create() as response: + with client.vector_stores.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -62,15 +64,15 @@ def test_streaming_response_create(self, client: OpenAI) -> None: @parametrize def test_method_retrieve(self, client: OpenAI) -> None: - vector_store = client.beta.vector_stores.retrieve( - "string", + vector_store = client.vector_stores.retrieve( + "vector_store_id", ) assert_matches_type(VectorStore, vector_store, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: OpenAI) -> None: - response = client.beta.vector_stores.with_raw_response.retrieve( - "string", + response = client.vector_stores.with_raw_response.retrieve( + "vector_store_id", ) assert response.is_closed is True @@ -80,8 +82,8 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: @parametrize def test_streaming_response_retrieve(self, client: OpenAI) -> None: - with client.beta.vector_stores.with_streaming_response.retrieve( - "string", + with client.vector_stores.with_streaming_response.retrieve( + "vector_store_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -94,34 +96,34 @@ def test_streaming_response_retrieve(self, client: OpenAI) -> None: @parametrize def test_path_params_retrieve(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.with_raw_response.retrieve( + client.vector_stores.with_raw_response.retrieve( "", ) @parametrize def test_method_update(self, client: OpenAI) -> None: - vector_store = client.beta.vector_stores.update( - "string", + vector_store = client.vector_stores.update( + vector_store_id="vector_store_id", ) assert_matches_type(VectorStore, vector_store, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: OpenAI) -> None: - vector_store = client.beta.vector_stores.update( - "string", + vector_store = client.vector_stores.update( + vector_store_id="vector_store_id", expires_after={ "anchor": "last_active_at", "days": 1, }, - metadata={}, - name="string", + metadata={"foo": "string"}, + name="name", ) assert_matches_type(VectorStore, vector_store, path=["response"]) @parametrize def test_raw_response_update(self, client: OpenAI) -> None: - response = client.beta.vector_stores.with_raw_response.update( - "string", + response = client.vector_stores.with_raw_response.update( + vector_store_id="vector_store_id", ) assert response.is_closed is True @@ -131,8 +133,8 @@ def test_raw_response_update(self, client: OpenAI) -> None: @parametrize def test_streaming_response_update(self, client: OpenAI) -> None: - with client.beta.vector_stores.with_streaming_response.update( - "string", + with client.vector_stores.with_streaming_response.update( + vector_store_id="vector_store_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -145,20 +147,20 @@ def test_streaming_response_update(self, client: OpenAI) -> None: @parametrize def test_path_params_update(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.with_raw_response.update( - "", + client.vector_stores.with_raw_response.update( + vector_store_id="", ) @parametrize def test_method_list(self, client: OpenAI) -> None: - vector_store = client.beta.vector_stores.list() + vector_store = client.vector_stores.list() assert_matches_type(SyncCursorPage[VectorStore], vector_store, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: OpenAI) -> None: - vector_store = client.beta.vector_stores.list( - after="string", - before="string", + vector_store = client.vector_stores.list( + after="after", + before="before", limit=0, order="asc", ) @@ -166,7 +168,7 @@ def test_method_list_with_all_params(self, client: OpenAI) -> None: @parametrize def test_raw_response_list(self, client: OpenAI) -> None: - response = client.beta.vector_stores.with_raw_response.list() + response = client.vector_stores.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -175,7 +177,7 @@ def test_raw_response_list(self, client: OpenAI) -> None: @parametrize def test_streaming_response_list(self, client: OpenAI) -> None: - with client.beta.vector_stores.with_streaming_response.list() as response: + with client.vector_stores.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -186,15 +188,15 @@ def test_streaming_response_list(self, client: OpenAI) -> None: @parametrize def test_method_delete(self, client: OpenAI) -> None: - vector_store = client.beta.vector_stores.delete( - "string", + vector_store = client.vector_stores.delete( + "vector_store_id", ) assert_matches_type(VectorStoreDeleted, vector_store, path=["response"]) @parametrize def test_raw_response_delete(self, client: OpenAI) -> None: - response = client.beta.vector_stores.with_raw_response.delete( - "string", + response = client.vector_stores.with_raw_response.delete( + "vector_store_id", ) assert response.is_closed is True @@ -204,8 +206,8 @@ def test_raw_response_delete(self, client: OpenAI) -> None: @parametrize def test_streaming_response_delete(self, client: OpenAI) -> None: - with client.beta.vector_stores.with_streaming_response.delete( - "string", + with client.vector_stores.with_streaming_response.delete( + "vector_store_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -218,36 +220,100 @@ def test_streaming_response_delete(self, client: OpenAI) -> None: @parametrize def test_path_params_delete(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.with_raw_response.delete( + client.vector_stores.with_raw_response.delete( "", ) + @parametrize + def test_method_search(self, client: OpenAI) -> None: + vector_store = client.vector_stores.search( + vector_store_id="vs_abc123", + query="string", + ) + assert_matches_type(SyncPage[VectorStoreSearchResponse], vector_store, path=["response"]) + + @parametrize + def test_method_search_with_all_params(self, client: OpenAI) -> None: + vector_store = client.vector_stores.search( + vector_store_id="vs_abc123", + query="string", + filters={ + "key": "key", + "type": "eq", + "value": "string", + }, + max_num_results=1, + ranking_options={ + "ranker": "none", + "score_threshold": 0, + }, + rewrite_query=True, + ) + assert_matches_type(SyncPage[VectorStoreSearchResponse], vector_store, path=["response"]) + + @parametrize + def test_raw_response_search(self, client: OpenAI) -> None: + response = client.vector_stores.with_raw_response.search( + vector_store_id="vs_abc123", + query="string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + vector_store = response.parse() + assert_matches_type(SyncPage[VectorStoreSearchResponse], vector_store, path=["response"]) + + @parametrize + def test_streaming_response_search(self, client: OpenAI) -> None: + with client.vector_stores.with_streaming_response.search( + vector_store_id="vs_abc123", + query="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + vector_store = response.parse() + assert_matches_type(SyncPage[VectorStoreSearchResponse], vector_store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_search(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + client.vector_stores.with_raw_response.search( + vector_store_id="", + query="string", + ) + class TestAsyncVectorStores: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: - vector_store = await async_client.beta.vector_stores.create() + vector_store = await async_client.vector_stores.create() assert_matches_type(VectorStore, vector_store, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: - vector_store = await async_client.beta.vector_stores.create( + vector_store = await async_client.vector_stores.create( chunking_strategy={"type": "auto"}, + description="description", expires_after={ "anchor": "last_active_at", "days": 1, }, - file_ids=["string", "string", "string"], - metadata={}, - name="string", + file_ids=["string"], + metadata={"foo": "string"}, + name="name", ) assert_matches_type(VectorStore, vector_store, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.with_raw_response.create() + response = await async_client.vector_stores.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -256,7 +322,7 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.with_streaming_response.create() as response: + async with async_client.vector_stores.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -267,15 +333,15 @@ async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> Non @parametrize async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: - vector_store = await async_client.beta.vector_stores.retrieve( - "string", + vector_store = await async_client.vector_stores.retrieve( + "vector_store_id", ) assert_matches_type(VectorStore, vector_store, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.with_raw_response.retrieve( - "string", + response = await async_client.vector_stores.with_raw_response.retrieve( + "vector_store_id", ) assert response.is_closed is True @@ -285,8 +351,8 @@ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.with_streaming_response.retrieve( - "string", + async with async_client.vector_stores.with_streaming_response.retrieve( + "vector_store_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -299,34 +365,34 @@ async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> N @parametrize async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.with_raw_response.retrieve( + await async_client.vector_stores.with_raw_response.retrieve( "", ) @parametrize async def test_method_update(self, async_client: AsyncOpenAI) -> None: - vector_store = await async_client.beta.vector_stores.update( - "string", + vector_store = await async_client.vector_stores.update( + vector_store_id="vector_store_id", ) assert_matches_type(VectorStore, vector_store, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncOpenAI) -> None: - vector_store = await async_client.beta.vector_stores.update( - "string", + vector_store = await async_client.vector_stores.update( + vector_store_id="vector_store_id", expires_after={ "anchor": "last_active_at", "days": 1, }, - metadata={}, - name="string", + metadata={"foo": "string"}, + name="name", ) assert_matches_type(VectorStore, vector_store, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.with_raw_response.update( - "string", + response = await async_client.vector_stores.with_raw_response.update( + vector_store_id="vector_store_id", ) assert response.is_closed is True @@ -336,8 +402,8 @@ async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.with_streaming_response.update( - "string", + async with async_client.vector_stores.with_streaming_response.update( + vector_store_id="vector_store_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -350,20 +416,20 @@ async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> Non @parametrize async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.with_raw_response.update( - "", + await async_client.vector_stores.with_raw_response.update( + vector_store_id="", ) @parametrize async def test_method_list(self, async_client: AsyncOpenAI) -> None: - vector_store = await async_client.beta.vector_stores.list() + vector_store = await async_client.vector_stores.list() assert_matches_type(AsyncCursorPage[VectorStore], vector_store, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: - vector_store = await async_client.beta.vector_stores.list( - after="string", - before="string", + vector_store = await async_client.vector_stores.list( + after="after", + before="before", limit=0, order="asc", ) @@ -371,7 +437,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> N @parametrize async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.with_raw_response.list() + response = await async_client.vector_stores.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -380,7 +446,7 @@ async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.with_streaming_response.list() as response: + async with async_client.vector_stores.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -391,15 +457,15 @@ async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_method_delete(self, async_client: AsyncOpenAI) -> None: - vector_store = await async_client.beta.vector_stores.delete( - "string", + vector_store = await async_client.vector_stores.delete( + "vector_store_id", ) assert_matches_type(VectorStoreDeleted, vector_store, path=["response"]) @parametrize async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.with_raw_response.delete( - "string", + response = await async_client.vector_stores.with_raw_response.delete( + "vector_store_id", ) assert response.is_closed is True @@ -409,8 +475,8 @@ async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.with_streaming_response.delete( - "string", + async with async_client.vector_stores.with_streaming_response.delete( + "vector_store_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -423,6 +489,67 @@ async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> Non @parametrize async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.with_raw_response.delete( + await async_client.vector_stores.with_raw_response.delete( "", ) + + @parametrize + async def test_method_search(self, async_client: AsyncOpenAI) -> None: + vector_store = await async_client.vector_stores.search( + vector_store_id="vs_abc123", + query="string", + ) + assert_matches_type(AsyncPage[VectorStoreSearchResponse], vector_store, path=["response"]) + + @parametrize + async def test_method_search_with_all_params(self, async_client: AsyncOpenAI) -> None: + vector_store = await async_client.vector_stores.search( + vector_store_id="vs_abc123", + query="string", + filters={ + "key": "key", + "type": "eq", + "value": "string", + }, + max_num_results=1, + ranking_options={ + "ranker": "none", + "score_threshold": 0, + }, + rewrite_query=True, + ) + assert_matches_type(AsyncPage[VectorStoreSearchResponse], vector_store, path=["response"]) + + @parametrize + async def test_raw_response_search(self, async_client: AsyncOpenAI) -> None: + response = await async_client.vector_stores.with_raw_response.search( + vector_store_id="vs_abc123", + query="string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + vector_store = response.parse() + assert_matches_type(AsyncPage[VectorStoreSearchResponse], vector_store, path=["response"]) + + @parametrize + async def test_streaming_response_search(self, async_client: AsyncOpenAI) -> None: + async with async_client.vector_stores.with_streaming_response.search( + vector_store_id="vs_abc123", + query="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + vector_store = await response.parse() + assert_matches_type(AsyncPage[VectorStoreSearchResponse], vector_store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_search(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + await async_client.vector_stores.with_raw_response.search( + vector_store_id="", + query="string", + ) diff --git a/tests/api_resources/test_videos.py b/tests/api_resources/test_videos.py new file mode 100644 index 0000000000..d6b51f9674 --- /dev/null +++ b/tests/api_resources/test_videos.py @@ -0,0 +1,839 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import httpx +import pytest +from respx import MockRouter + +import openai._legacy_response as _legacy_response +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types import ( + Video, + VideoDeleteResponse, + VideoGetCharacterResponse, + VideoCreateCharacterResponse, +) +from openai._utils import assert_signatures_in_sync +from openai.pagination import SyncConversationCursorPage, AsyncConversationCursorPage + +# pyright: reportDeprecated=false + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestVideos: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + video = client.videos.create( + prompt="x", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + video = client.videos.create( + prompt="x", + input_reference=b"Example data", + model="sora-2", + seconds="4", + size="720x1280", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.videos.with_raw_response.create( + prompt="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.videos.with_streaming_response.create( + prompt="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + video = client.videos.retrieve( + "video_123", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.videos.with_raw_response.retrieve( + "video_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.videos.with_streaming_response.retrieve( + "video_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"): + client.videos.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + video = client.videos.list() + assert_matches_type(SyncConversationCursorPage[Video], video, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + video = client.videos.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(SyncConversationCursorPage[Video], video, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.videos.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(SyncConversationCursorPage[Video], video, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.videos.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = response.parse() + assert_matches_type(SyncConversationCursorPage[Video], video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + video = client.videos.delete( + "video_123", + ) + assert_matches_type(VideoDeleteResponse, video, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.videos.with_raw_response.delete( + "video_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(VideoDeleteResponse, video, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.videos.with_streaming_response.delete( + "video_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = response.parse() + assert_matches_type(VideoDeleteResponse, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"): + client.videos.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_create_character(self, client: OpenAI) -> None: + video = client.videos.create_character( + name="x", + video=b"Example data", + ) + assert_matches_type(VideoCreateCharacterResponse, video, path=["response"]) + + @parametrize + def test_raw_response_create_character(self, client: OpenAI) -> None: + response = client.videos.with_raw_response.create_character( + name="x", + video=b"Example data", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(VideoCreateCharacterResponse, video, path=["response"]) + + @parametrize + def test_streaming_response_create_character(self, client: OpenAI) -> None: + with client.videos.with_streaming_response.create_character( + name="x", + video=b"Example data", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = response.parse() + assert_matches_type(VideoCreateCharacterResponse, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_download_content(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + video = client.videos.download_content( + video_id="video_123", + ) + assert isinstance(video, _legacy_response.HttpxBinaryResponseContent) + assert video.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_download_content_with_all_params(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + video = client.videos.download_content( + video_id="video_123", + variant="video", + ) + assert isinstance(video, _legacy_response.HttpxBinaryResponseContent) + assert video.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_download_content(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + response = client.videos.with_raw_response.download_content( + video_id="video_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, video, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_download_content(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + with client.videos.with_streaming_response.download_content( + video_id="video_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = response.parse() + assert_matches_type(bytes, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_download_content(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"): + client.videos.with_raw_response.download_content( + video_id="", + ) + + @parametrize + def test_method_edit(self, client: OpenAI) -> None: + video = client.videos.edit( + prompt="x", + video=b"Example data", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_raw_response_edit(self, client: OpenAI) -> None: + response = client.videos.with_raw_response.edit( + prompt="x", + video=b"Example data", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_streaming_response_edit(self, client: OpenAI) -> None: + with client.videos.with_streaming_response.edit( + prompt="x", + video=b"Example data", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_extend(self, client: OpenAI) -> None: + video = client.videos.extend( + prompt="x", + seconds="4", + video=b"Example data", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_raw_response_extend(self, client: OpenAI) -> None: + response = client.videos.with_raw_response.extend( + prompt="x", + seconds="4", + video=b"Example data", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_streaming_response_extend(self, client: OpenAI) -> None: + with client.videos.with_streaming_response.extend( + prompt="x", + seconds="4", + video=b"Example data", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_get_character(self, client: OpenAI) -> None: + video = client.videos.get_character( + "char_123", + ) + assert_matches_type(VideoGetCharacterResponse, video, path=["response"]) + + @parametrize + def test_raw_response_get_character(self, client: OpenAI) -> None: + response = client.videos.with_raw_response.get_character( + "char_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(VideoGetCharacterResponse, video, path=["response"]) + + @parametrize + def test_streaming_response_get_character(self, client: OpenAI) -> None: + with client.videos.with_streaming_response.get_character( + "char_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = response.parse() + assert_matches_type(VideoGetCharacterResponse, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_get_character(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `character_id` but received ''"): + client.videos.with_raw_response.get_character( + "", + ) + + @parametrize + def test_method_remix(self, client: OpenAI) -> None: + video = client.videos.remix( + video_id="video_123", + prompt="x", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_raw_response_remix(self, client: OpenAI) -> None: + response = client.videos.with_raw_response.remix( + video_id="video_123", + prompt="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + @parametrize + def test_streaming_response_remix(self, client: OpenAI) -> None: + with client.videos.with_streaming_response.remix( + video_id="video_123", + prompt="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_remix(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"): + client.videos.with_raw_response.remix( + video_id="", + prompt="x", + ) + + +class TestAsyncVideos: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.create( + prompt="x", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.create( + prompt="x", + input_reference=b"Example data", + model="sora-2", + seconds="4", + size="720x1280", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.videos.with_raw_response.create( + prompt="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.videos.with_streaming_response.create( + prompt="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = await response.parse() + assert_matches_type(Video, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.retrieve( + "video_123", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.videos.with_raw_response.retrieve( + "video_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.videos.with_streaming_response.retrieve( + "video_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = await response.parse() + assert_matches_type(Video, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"): + await async_client.videos.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.list() + assert_matches_type(AsyncConversationCursorPage[Video], video, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.list( + after="after", + limit=0, + order="asc", + ) + assert_matches_type(AsyncConversationCursorPage[Video], video, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.videos.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(AsyncConversationCursorPage[Video], video, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.videos.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = await response.parse() + assert_matches_type(AsyncConversationCursorPage[Video], video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.delete( + "video_123", + ) + assert_matches_type(VideoDeleteResponse, video, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.videos.with_raw_response.delete( + "video_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(VideoDeleteResponse, video, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.videos.with_streaming_response.delete( + "video_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = await response.parse() + assert_matches_type(VideoDeleteResponse, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"): + await async_client.videos.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_create_character(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.create_character( + name="x", + video=b"Example data", + ) + assert_matches_type(VideoCreateCharacterResponse, video, path=["response"]) + + @parametrize + async def test_raw_response_create_character(self, async_client: AsyncOpenAI) -> None: + response = await async_client.videos.with_raw_response.create_character( + name="x", + video=b"Example data", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(VideoCreateCharacterResponse, video, path=["response"]) + + @parametrize + async def test_streaming_response_create_character(self, async_client: AsyncOpenAI) -> None: + async with async_client.videos.with_streaming_response.create_character( + name="x", + video=b"Example data", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = await response.parse() + assert_matches_type(VideoCreateCharacterResponse, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_download_content(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + video = await async_client.videos.download_content( + video_id="video_123", + ) + assert isinstance(video, _legacy_response.HttpxBinaryResponseContent) + assert video.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_download_content_with_all_params( + self, async_client: AsyncOpenAI, respx_mock: MockRouter + ) -> None: + respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + video = await async_client.videos.download_content( + video_id="video_123", + variant="video", + ) + assert isinstance(video, _legacy_response.HttpxBinaryResponseContent) + assert video.json() == {"foo": "bar"} + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_download_content(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + response = await async_client.videos.with_raw_response.download_content( + video_id="video_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, video, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_download_content(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/videos/video_123/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + async with async_client.videos.with_streaming_response.download_content( + video_id="video_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = await response.parse() + assert_matches_type(bytes, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_download_content(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"): + await async_client.videos.with_raw_response.download_content( + video_id="", + ) + + @parametrize + async def test_method_edit(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.edit( + prompt="x", + video=b"Example data", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_raw_response_edit(self, async_client: AsyncOpenAI) -> None: + response = await async_client.videos.with_raw_response.edit( + prompt="x", + video=b"Example data", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_streaming_response_edit(self, async_client: AsyncOpenAI) -> None: + async with async_client.videos.with_streaming_response.edit( + prompt="x", + video=b"Example data", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = await response.parse() + assert_matches_type(Video, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_extend(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.extend( + prompt="x", + seconds="4", + video=b"Example data", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_raw_response_extend(self, async_client: AsyncOpenAI) -> None: + response = await async_client.videos.with_raw_response.extend( + prompt="x", + seconds="4", + video=b"Example data", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_streaming_response_extend(self, async_client: AsyncOpenAI) -> None: + async with async_client.videos.with_streaming_response.extend( + prompt="x", + seconds="4", + video=b"Example data", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = await response.parse() + assert_matches_type(Video, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_get_character(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.get_character( + "char_123", + ) + assert_matches_type(VideoGetCharacterResponse, video, path=["response"]) + + @parametrize + async def test_raw_response_get_character(self, async_client: AsyncOpenAI) -> None: + response = await async_client.videos.with_raw_response.get_character( + "char_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(VideoGetCharacterResponse, video, path=["response"]) + + @parametrize + async def test_streaming_response_get_character(self, async_client: AsyncOpenAI) -> None: + async with async_client.videos.with_streaming_response.get_character( + "char_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = await response.parse() + assert_matches_type(VideoGetCharacterResponse, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_get_character(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `character_id` but received ''"): + await async_client.videos.with_raw_response.get_character( + "", + ) + + @parametrize + async def test_method_remix(self, async_client: AsyncOpenAI) -> None: + video = await async_client.videos.remix( + video_id="video_123", + prompt="x", + ) + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_raw_response_remix(self, async_client: AsyncOpenAI) -> None: + response = await async_client.videos.with_raw_response.remix( + video_id="video_123", + prompt="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + video = response.parse() + assert_matches_type(Video, video, path=["response"]) + + @parametrize + async def test_streaming_response_remix(self, async_client: AsyncOpenAI) -> None: + async with async_client.videos.with_streaming_response.remix( + video_id="video_123", + prompt="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + video = await response.parse() + assert_matches_type(Video, video, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_remix(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `video_id` but received ''"): + await async_client.videos.with_raw_response.remix( + video_id="", + prompt="x", + ) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +def test_create_and_poll_method_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: + checking_client: OpenAI | AsyncOpenAI = client if sync else async_client + + assert_signatures_in_sync( + checking_client.videos.create, + checking_client.videos.create_and_poll, + exclude_params={"extra_headers", "extra_query", "extra_body", "timeout"}, + ) diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py new file mode 100644 index 0000000000..6b404998e1 --- /dev/null +++ b/tests/api_resources/test_webhooks.py @@ -0,0 +1,284 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from unittest import mock + +import pytest + +import openai +from openai._exceptions import InvalidWebhookSignatureError + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + +# Standardized test constants (matches TypeScript implementation) +TEST_SECRET = "whsec_RdvaYFYUXuIFuEbvZHwMfYFhUf7aMYjYcmM24+Aj40c=" +TEST_PAYLOAD = '{"id": "evt_685c059ae3a481909bdc86819b066fb6", "object": "event", "created_at": 1750861210, "type": "response.completed", "data": {"id": "resp_123"}}' +TEST_TIMESTAMP = 1750861210 # Fixed timestamp that matches our test signature +TEST_WEBHOOK_ID = "wh_685c059ae39c8190af8c71ed1022a24d" +TEST_SIGNATURE = "v1,gUAg4R2hWouRZqRQG4uJypNS8YK885G838+EHb4nKBY=" + + +def create_test_headers( + timestamp: int | None = None, signature: str | None = None, webhook_id: str | None = None +) -> dict[str, str]: + """Helper function to create test headers""" + return { + "webhook-signature": signature or TEST_SIGNATURE, + "webhook-timestamp": str(timestamp or TEST_TIMESTAMP), + "webhook-id": webhook_id or TEST_WEBHOOK_ID, + } + + +class TestWebhooks: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + def test_unwrap_with_secret(self, client: openai.OpenAI) -> None: + headers = create_test_headers() + unwrapped = client.webhooks.unwrap(TEST_PAYLOAD, headers, secret=TEST_SECRET) + assert unwrapped.id == "evt_685c059ae3a481909bdc86819b066fb6" + assert unwrapped.created_at == 1750861210 + + @parametrize + def test_unwrap_without_secret(self, client: openai.OpenAI) -> None: + headers = create_test_headers() + with pytest.raises(ValueError, match="The webhook secret must either be set"): + client.webhooks.unwrap(TEST_PAYLOAD, headers) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + def test_verify_signature_valid(self, client: openai.OpenAI) -> None: + headers = create_test_headers() + # Should not raise - this is a truly valid signature for this timestamp + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @parametrize + def test_verify_signature_invalid_secret_format(self, client: openai.OpenAI) -> None: + headers = create_test_headers() + with pytest.raises(ValueError, match="The webhook secret must either be set"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=None) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + def test_verify_signature_invalid(self, client: openai.OpenAI) -> None: + headers = create_test_headers() + with pytest.raises(InvalidWebhookSignatureError, match="The given webhook signature does not match"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret="invalid_secret") + + @parametrize + def test_verify_signature_missing_webhook_signature_header(self, client: openai.OpenAI) -> None: + headers = create_test_headers(signature=None) + del headers["webhook-signature"] + with pytest.raises(ValueError, match="Could not find webhook-signature header"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @parametrize + def test_verify_signature_missing_webhook_timestamp_header(self, client: openai.OpenAI) -> None: + headers = create_test_headers() + del headers["webhook-timestamp"] + with pytest.raises(ValueError, match="Could not find webhook-timestamp header"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @parametrize + def test_verify_signature_missing_webhook_id_header(self, client: openai.OpenAI) -> None: + headers = create_test_headers() + del headers["webhook-id"] + with pytest.raises(ValueError, match="Could not find webhook-id header"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + def test_verify_signature_payload_bytes(self, client: openai.OpenAI) -> None: + headers = create_test_headers() + client.webhooks.verify_signature(TEST_PAYLOAD.encode("utf-8"), headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + def test_unwrap_with_client_secret(self) -> None: + test_client = openai.OpenAI(base_url=base_url, api_key="test-api-key", webhook_secret=TEST_SECRET) + headers = create_test_headers() + + unwrapped = test_client.webhooks.unwrap(TEST_PAYLOAD, headers) + assert unwrapped.id == "evt_685c059ae3a481909bdc86819b066fb6" + assert unwrapped.created_at == 1750861210 + + @parametrize + def test_verify_signature_timestamp_too_old(self, client: openai.OpenAI) -> None: + # Use a timestamp that's older than 5 minutes from our test timestamp + old_timestamp = TEST_TIMESTAMP - 400 # 6 minutes 40 seconds ago + headers = create_test_headers(timestamp=old_timestamp, signature="v1,dummy_signature") + + with pytest.raises(InvalidWebhookSignatureError, match="Webhook timestamp is too old"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + def test_verify_signature_timestamp_too_new(self, client: openai.OpenAI) -> None: + # Use a timestamp that's in the future beyond tolerance from our test timestamp + future_timestamp = TEST_TIMESTAMP + 400 # 6 minutes 40 seconds in the future + headers = create_test_headers(timestamp=future_timestamp, signature="v1,dummy_signature") + + with pytest.raises(InvalidWebhookSignatureError, match="Webhook timestamp is too new"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + def test_verify_signature_custom_tolerance(self, client: openai.OpenAI) -> None: + # Use a timestamp that's older than default tolerance but within custom tolerance + old_timestamp = TEST_TIMESTAMP - 400 # 6 minutes 40 seconds ago from test timestamp + headers = create_test_headers(timestamp=old_timestamp, signature="v1,dummy_signature") + + # Should fail with default tolerance + with pytest.raises(InvalidWebhookSignatureError, match="Webhook timestamp is too old"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + # Should also fail with custom tolerance of 10 minutes (signature won't match) + with pytest.raises(InvalidWebhookSignatureError, match="The given webhook signature does not match"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET, tolerance=600) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + def test_verify_signature_recent_timestamp_succeeds(self, client: openai.OpenAI) -> None: + # Use a recent timestamp with dummy signature + headers = create_test_headers(signature="v1,dummy_signature") + + # Should fail on signature verification (not timestamp validation) + with pytest.raises(InvalidWebhookSignatureError, match="The given webhook signature does not match"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + def test_verify_signature_multiple_signatures_one_valid(self, client: openai.OpenAI) -> None: + # Test multiple signatures: one invalid, one valid + multiple_signatures = f"v1,invalid_signature {TEST_SIGNATURE}" + headers = create_test_headers(signature=multiple_signatures) + + # Should not raise when at least one signature is valid + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + def test_verify_signature_multiple_signatures_all_invalid(self, client: openai.OpenAI) -> None: + # Test multiple invalid signatures + multiple_invalid_signatures = "v1,invalid_signature1 v1,invalid_signature2" + headers = create_test_headers(signature=multiple_invalid_signatures) + + with pytest.raises(InvalidWebhookSignatureError, match="The given webhook signature does not match"): + client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + +class TestAsyncWebhooks: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + async def test_unwrap_with_secret(self, async_client: openai.AsyncOpenAI) -> None: + headers = create_test_headers() + unwrapped = async_client.webhooks.unwrap(TEST_PAYLOAD, headers, secret=TEST_SECRET) + assert unwrapped.id == "evt_685c059ae3a481909bdc86819b066fb6" + assert unwrapped.created_at == 1750861210 + + @parametrize + async def test_unwrap_without_secret(self, async_client: openai.AsyncOpenAI) -> None: + headers = create_test_headers() + with pytest.raises(ValueError, match="The webhook secret must either be set"): + async_client.webhooks.unwrap(TEST_PAYLOAD, headers) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + async def test_verify_signature_valid(self, async_client: openai.AsyncOpenAI) -> None: + headers = create_test_headers() + # Should not raise - this is a truly valid signature for this timestamp + async_client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @parametrize + async def test_verify_signature_invalid_secret_format(self, async_client: openai.AsyncOpenAI) -> None: + headers = create_test_headers() + with pytest.raises(ValueError, match="The webhook secret must either be set"): + async_client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=None) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + async def test_verify_signature_invalid(self, async_client: openai.AsyncOpenAI) -> None: + headers = create_test_headers() + with pytest.raises(InvalidWebhookSignatureError, match="The given webhook signature does not match"): + async_client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret="invalid_secret") + + @parametrize + async def test_verify_signature_missing_webhook_signature_header(self, async_client: openai.AsyncOpenAI) -> None: + headers = create_test_headers() + del headers["webhook-signature"] + with pytest.raises(ValueError, match="Could not find webhook-signature header"): + async_client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @parametrize + async def test_verify_signature_missing_webhook_timestamp_header(self, async_client: openai.AsyncOpenAI) -> None: + headers = create_test_headers() + del headers["webhook-timestamp"] + with pytest.raises(ValueError, match="Could not find webhook-timestamp header"): + async_client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @parametrize + async def test_verify_signature_missing_webhook_id_header(self, async_client: openai.AsyncOpenAI) -> None: + headers = create_test_headers() + del headers["webhook-id"] + with pytest.raises(ValueError, match="Could not find webhook-id header"): + async_client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + async def test_verify_signature_payload_bytes(self, async_client: openai.AsyncOpenAI) -> None: + headers = create_test_headers() + async_client.webhooks.verify_signature(TEST_PAYLOAD.encode("utf-8"), headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + async def test_unwrap_with_client_secret(self) -> None: + test_async_client = openai.AsyncOpenAI(base_url=base_url, api_key="test-api-key", webhook_secret=TEST_SECRET) + headers = create_test_headers() + + unwrapped = test_async_client.webhooks.unwrap(TEST_PAYLOAD, headers) + assert unwrapped.id == "evt_685c059ae3a481909bdc86819b066fb6" + assert unwrapped.created_at == 1750861210 + + @parametrize + async def test_verify_signature_timestamp_too_old(self, async_client: openai.AsyncOpenAI) -> None: + # Use a timestamp that's older than 5 minutes from our test timestamp + old_timestamp = TEST_TIMESTAMP - 400 # 6 minutes 40 seconds ago + headers = create_test_headers(timestamp=old_timestamp, signature="v1,dummy_signature") + + with pytest.raises(InvalidWebhookSignatureError, match="Webhook timestamp is too old"): + async_client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + async def test_verify_signature_timestamp_too_new(self, async_client: openai.AsyncOpenAI) -> None: + # Use a timestamp that's in the future beyond tolerance from our test timestamp + future_timestamp = TEST_TIMESTAMP + 400 # 6 minutes 40 seconds in the future + headers = create_test_headers(timestamp=future_timestamp, signature="v1,dummy_signature") + + with pytest.raises(InvalidWebhookSignatureError, match="Webhook timestamp is too new"): + async_client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + async def test_verify_signature_multiple_signatures_one_valid(self, async_client: openai.AsyncOpenAI) -> None: + # Test multiple signatures: one invalid, one valid + multiple_signatures = f"v1,invalid_signature {TEST_SIGNATURE}" + headers = create_test_headers(signature=multiple_signatures) + + # Should not raise when at least one signature is valid + async_client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) + + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) + @parametrize + async def test_verify_signature_multiple_signatures_all_invalid(self, async_client: openai.AsyncOpenAI) -> None: + # Test multiple invalid signatures + multiple_invalid_signatures = "v1,invalid_signature1 v1,invalid_signature2" + headers = create_test_headers(signature=multiple_invalid_signatures) + + with pytest.raises(InvalidWebhookSignatureError, match="The given webhook signature does not match"): + async_client.webhooks.verify_signature(TEST_PAYLOAD, headers, secret=TEST_SECRET) diff --git a/tests/api_resources/uploads/test_parts.py b/tests/api_resources/uploads/test_parts.py index 2bba241a6d..b5956d263b 100644 --- a/tests/api_resources/uploads/test_parts.py +++ b/tests/api_resources/uploads/test_parts.py @@ -21,7 +21,7 @@ class TestParts: def test_method_create(self, client: OpenAI) -> None: part = client.uploads.parts.create( upload_id="upload_abc123", - data=b"raw file contents", + data=b"Example data", ) assert_matches_type(UploadPart, part, path=["response"]) @@ -29,7 +29,7 @@ def test_method_create(self, client: OpenAI) -> None: def test_raw_response_create(self, client: OpenAI) -> None: response = client.uploads.parts.with_raw_response.create( upload_id="upload_abc123", - data=b"raw file contents", + data=b"Example data", ) assert response.is_closed is True @@ -41,7 +41,7 @@ def test_raw_response_create(self, client: OpenAI) -> None: def test_streaming_response_create(self, client: OpenAI) -> None: with client.uploads.parts.with_streaming_response.create( upload_id="upload_abc123", - data=b"raw file contents", + data=b"Example data", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -56,18 +56,20 @@ def test_path_params_create(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `upload_id` but received ''"): client.uploads.parts.with_raw_response.create( upload_id="", - data=b"raw file contents", + data=b"Example data", ) class TestAsyncParts: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: part = await async_client.uploads.parts.create( upload_id="upload_abc123", - data=b"raw file contents", + data=b"Example data", ) assert_matches_type(UploadPart, part, path=["response"]) @@ -75,7 +77,7 @@ async def test_method_create(self, async_client: AsyncOpenAI) -> None: async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: response = await async_client.uploads.parts.with_raw_response.create( upload_id="upload_abc123", - data=b"raw file contents", + data=b"Example data", ) assert response.is_closed is True @@ -87,7 +89,7 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: async with async_client.uploads.parts.with_streaming_response.create( upload_id="upload_abc123", - data=b"raw file contents", + data=b"Example data", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -102,5 +104,5 @@ async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `upload_id` but received ''"): await async_client.uploads.parts.with_raw_response.create( upload_id="", - data=b"raw file contents", + data=b"Example data", ) diff --git a/tests/api_resources/vector_stores/__init__.py b/tests/api_resources/vector_stores/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/vector_stores/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/beta/vector_stores/test_file_batches.py b/tests/api_resources/vector_stores/test_file_batches.py similarity index 64% rename from tests/api_resources/beta/vector_stores/test_file_batches.py rename to tests/api_resources/vector_stores/test_file_batches.py index 631f2669ad..c1fba534a6 100644 --- a/tests/api_resources/beta/vector_stores/test_file_batches.py +++ b/tests/api_resources/vector_stores/test_file_batches.py @@ -9,8 +9,9 @@ from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type +from openai._utils import assert_signatures_in_sync from openai.pagination import SyncCursorPage, AsyncCursorPage -from openai.types.beta.vector_stores import ( +from openai.types.vector_stores import ( VectorStoreFile, VectorStoreFileBatch, ) @@ -23,26 +24,32 @@ class TestFileBatches: @parametrize def test_method_create(self, client: OpenAI) -> None: - file_batch = client.beta.vector_stores.file_batches.create( - "vs_abc123", - file_ids=["string"], + file_batch = client.vector_stores.file_batches.create( + vector_store_id="vs_abc123", ) assert_matches_type(VectorStoreFileBatch, file_batch, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: OpenAI) -> None: - file_batch = client.beta.vector_stores.file_batches.create( - "vs_abc123", - file_ids=["string"], + file_batch = client.vector_stores.file_batches.create( + vector_store_id="vs_abc123", + attributes={"foo": "string"}, chunking_strategy={"type": "auto"}, + file_ids=["string"], + files=[ + { + "file_id": "file_id", + "attributes": {"foo": "string"}, + "chunking_strategy": {"type": "auto"}, + } + ], ) assert_matches_type(VectorStoreFileBatch, file_batch, path=["response"]) @parametrize def test_raw_response_create(self, client: OpenAI) -> None: - response = client.beta.vector_stores.file_batches.with_raw_response.create( - "vs_abc123", - file_ids=["string"], + response = client.vector_stores.file_batches.with_raw_response.create( + vector_store_id="vs_abc123", ) assert response.is_closed is True @@ -52,9 +59,8 @@ def test_raw_response_create(self, client: OpenAI) -> None: @parametrize def test_streaming_response_create(self, client: OpenAI) -> None: - with client.beta.vector_stores.file_batches.with_streaming_response.create( - "vs_abc123", - file_ids=["string"], + with client.vector_stores.file_batches.with_streaming_response.create( + vector_store_id="vs_abc123", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -67,23 +73,22 @@ def test_streaming_response_create(self, client: OpenAI) -> None: @parametrize def test_path_params_create(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.file_batches.with_raw_response.create( - "", - file_ids=["string"], + client.vector_stores.file_batches.with_raw_response.create( + vector_store_id="", ) @parametrize def test_method_retrieve(self, client: OpenAI) -> None: - file_batch = client.beta.vector_stores.file_batches.retrieve( - "vsfb_abc123", + file_batch = client.vector_stores.file_batches.retrieve( + batch_id="vsfb_abc123", vector_store_id="vs_abc123", ) assert_matches_type(VectorStoreFileBatch, file_batch, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: OpenAI) -> None: - response = client.beta.vector_stores.file_batches.with_raw_response.retrieve( - "vsfb_abc123", + response = client.vector_stores.file_batches.with_raw_response.retrieve( + batch_id="vsfb_abc123", vector_store_id="vs_abc123", ) @@ -94,8 +99,8 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: @parametrize def test_streaming_response_retrieve(self, client: OpenAI) -> None: - with client.beta.vector_stores.file_batches.with_streaming_response.retrieve( - "vsfb_abc123", + with client.vector_stores.file_batches.with_streaming_response.retrieve( + batch_id="vsfb_abc123", vector_store_id="vs_abc123", ) as response: assert not response.is_closed @@ -109,30 +114,30 @@ def test_streaming_response_retrieve(self, client: OpenAI) -> None: @parametrize def test_path_params_retrieve(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.file_batches.with_raw_response.retrieve( - "vsfb_abc123", + client.vector_stores.file_batches.with_raw_response.retrieve( + batch_id="vsfb_abc123", vector_store_id="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): - client.beta.vector_stores.file_batches.with_raw_response.retrieve( - "", + client.vector_stores.file_batches.with_raw_response.retrieve( + batch_id="", vector_store_id="vs_abc123", ) @parametrize def test_method_cancel(self, client: OpenAI) -> None: - file_batch = client.beta.vector_stores.file_batches.cancel( - "string", - vector_store_id="string", + file_batch = client.vector_stores.file_batches.cancel( + batch_id="batch_id", + vector_store_id="vector_store_id", ) assert_matches_type(VectorStoreFileBatch, file_batch, path=["response"]) @parametrize def test_raw_response_cancel(self, client: OpenAI) -> None: - response = client.beta.vector_stores.file_batches.with_raw_response.cancel( - "string", - vector_store_id="string", + response = client.vector_stores.file_batches.with_raw_response.cancel( + batch_id="batch_id", + vector_store_id="vector_store_id", ) assert response.is_closed is True @@ -142,9 +147,9 @@ def test_raw_response_cancel(self, client: OpenAI) -> None: @parametrize def test_streaming_response_cancel(self, client: OpenAI) -> None: - with client.beta.vector_stores.file_batches.with_streaming_response.cancel( - "string", - vector_store_id="string", + with client.vector_stores.file_batches.with_streaming_response.cancel( + batch_id="batch_id", + vector_store_id="vector_store_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -157,32 +162,32 @@ def test_streaming_response_cancel(self, client: OpenAI) -> None: @parametrize def test_path_params_cancel(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.file_batches.with_raw_response.cancel( - "string", + client.vector_stores.file_batches.with_raw_response.cancel( + batch_id="batch_id", vector_store_id="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): - client.beta.vector_stores.file_batches.with_raw_response.cancel( - "", - vector_store_id="string", + client.vector_stores.file_batches.with_raw_response.cancel( + batch_id="", + vector_store_id="vector_store_id", ) @parametrize def test_method_list_files(self, client: OpenAI) -> None: - file_batch = client.beta.vector_stores.file_batches.list_files( - "string", - vector_store_id="string", + file_batch = client.vector_stores.file_batches.list_files( + batch_id="batch_id", + vector_store_id="vector_store_id", ) assert_matches_type(SyncCursorPage[VectorStoreFile], file_batch, path=["response"]) @parametrize def test_method_list_files_with_all_params(self, client: OpenAI) -> None: - file_batch = client.beta.vector_stores.file_batches.list_files( - "string", - vector_store_id="string", - after="string", - before="string", + file_batch = client.vector_stores.file_batches.list_files( + batch_id="batch_id", + vector_store_id="vector_store_id", + after="after", + before="before", filter="in_progress", limit=0, order="asc", @@ -191,9 +196,9 @@ def test_method_list_files_with_all_params(self, client: OpenAI) -> None: @parametrize def test_raw_response_list_files(self, client: OpenAI) -> None: - response = client.beta.vector_stores.file_batches.with_raw_response.list_files( - "string", - vector_store_id="string", + response = client.vector_stores.file_batches.with_raw_response.list_files( + batch_id="batch_id", + vector_store_id="vector_store_id", ) assert response.is_closed is True @@ -203,9 +208,9 @@ def test_raw_response_list_files(self, client: OpenAI) -> None: @parametrize def test_streaming_response_list_files(self, client: OpenAI) -> None: - with client.beta.vector_stores.file_batches.with_streaming_response.list_files( - "string", - vector_store_id="string", + with client.vector_stores.file_batches.with_streaming_response.list_files( + batch_id="batch_id", + vector_store_id="vector_store_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -218,43 +223,51 @@ def test_streaming_response_list_files(self, client: OpenAI) -> None: @parametrize def test_path_params_list_files(self, client: OpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.beta.vector_stores.file_batches.with_raw_response.list_files( - "string", + client.vector_stores.file_batches.with_raw_response.list_files( + batch_id="batch_id", vector_store_id="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): - client.beta.vector_stores.file_batches.with_raw_response.list_files( - "", - vector_store_id="string", + client.vector_stores.file_batches.with_raw_response.list_files( + batch_id="", + vector_store_id="vector_store_id", ) class TestAsyncFileBatches: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncOpenAI) -> None: - file_batch = await async_client.beta.vector_stores.file_batches.create( - "vs_abc123", - file_ids=["string"], + file_batch = await async_client.vector_stores.file_batches.create( + vector_store_id="vs_abc123", ) assert_matches_type(VectorStoreFileBatch, file_batch, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: - file_batch = await async_client.beta.vector_stores.file_batches.create( - "vs_abc123", - file_ids=["string"], + file_batch = await async_client.vector_stores.file_batches.create( + vector_store_id="vs_abc123", + attributes={"foo": "string"}, chunking_strategy={"type": "auto"}, + file_ids=["string"], + files=[ + { + "file_id": "file_id", + "attributes": {"foo": "string"}, + "chunking_strategy": {"type": "auto"}, + } + ], ) assert_matches_type(VectorStoreFileBatch, file_batch, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.file_batches.with_raw_response.create( - "vs_abc123", - file_ids=["string"], + response = await async_client.vector_stores.file_batches.with_raw_response.create( + vector_store_id="vs_abc123", ) assert response.is_closed is True @@ -264,9 +277,8 @@ async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.file_batches.with_streaming_response.create( - "vs_abc123", - file_ids=["string"], + async with async_client.vector_stores.file_batches.with_streaming_response.create( + vector_store_id="vs_abc123", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -279,23 +291,22 @@ async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> Non @parametrize async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.file_batches.with_raw_response.create( - "", - file_ids=["string"], + await async_client.vector_stores.file_batches.with_raw_response.create( + vector_store_id="", ) @parametrize async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: - file_batch = await async_client.beta.vector_stores.file_batches.retrieve( - "vsfb_abc123", + file_batch = await async_client.vector_stores.file_batches.retrieve( + batch_id="vsfb_abc123", vector_store_id="vs_abc123", ) assert_matches_type(VectorStoreFileBatch, file_batch, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.file_batches.with_raw_response.retrieve( - "vsfb_abc123", + response = await async_client.vector_stores.file_batches.with_raw_response.retrieve( + batch_id="vsfb_abc123", vector_store_id="vs_abc123", ) @@ -306,8 +317,8 @@ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.file_batches.with_streaming_response.retrieve( - "vsfb_abc123", + async with async_client.vector_stores.file_batches.with_streaming_response.retrieve( + batch_id="vsfb_abc123", vector_store_id="vs_abc123", ) as response: assert not response.is_closed @@ -321,30 +332,30 @@ async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> N @parametrize async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.file_batches.with_raw_response.retrieve( - "vsfb_abc123", + await async_client.vector_stores.file_batches.with_raw_response.retrieve( + batch_id="vsfb_abc123", vector_store_id="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): - await async_client.beta.vector_stores.file_batches.with_raw_response.retrieve( - "", + await async_client.vector_stores.file_batches.with_raw_response.retrieve( + batch_id="", vector_store_id="vs_abc123", ) @parametrize async def test_method_cancel(self, async_client: AsyncOpenAI) -> None: - file_batch = await async_client.beta.vector_stores.file_batches.cancel( - "string", - vector_store_id="string", + file_batch = await async_client.vector_stores.file_batches.cancel( + batch_id="batch_id", + vector_store_id="vector_store_id", ) assert_matches_type(VectorStoreFileBatch, file_batch, path=["response"]) @parametrize async def test_raw_response_cancel(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.file_batches.with_raw_response.cancel( - "string", - vector_store_id="string", + response = await async_client.vector_stores.file_batches.with_raw_response.cancel( + batch_id="batch_id", + vector_store_id="vector_store_id", ) assert response.is_closed is True @@ -354,9 +365,9 @@ async def test_raw_response_cancel(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_cancel(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.file_batches.with_streaming_response.cancel( - "string", - vector_store_id="string", + async with async_client.vector_stores.file_batches.with_streaming_response.cancel( + batch_id="batch_id", + vector_store_id="vector_store_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -369,32 +380,32 @@ async def test_streaming_response_cancel(self, async_client: AsyncOpenAI) -> Non @parametrize async def test_path_params_cancel(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.file_batches.with_raw_response.cancel( - "string", + await async_client.vector_stores.file_batches.with_raw_response.cancel( + batch_id="batch_id", vector_store_id="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): - await async_client.beta.vector_stores.file_batches.with_raw_response.cancel( - "", - vector_store_id="string", + await async_client.vector_stores.file_batches.with_raw_response.cancel( + batch_id="", + vector_store_id="vector_store_id", ) @parametrize async def test_method_list_files(self, async_client: AsyncOpenAI) -> None: - file_batch = await async_client.beta.vector_stores.file_batches.list_files( - "string", - vector_store_id="string", + file_batch = await async_client.vector_stores.file_batches.list_files( + batch_id="batch_id", + vector_store_id="vector_store_id", ) assert_matches_type(AsyncCursorPage[VectorStoreFile], file_batch, path=["response"]) @parametrize async def test_method_list_files_with_all_params(self, async_client: AsyncOpenAI) -> None: - file_batch = await async_client.beta.vector_stores.file_batches.list_files( - "string", - vector_store_id="string", - after="string", - before="string", + file_batch = await async_client.vector_stores.file_batches.list_files( + batch_id="batch_id", + vector_store_id="vector_store_id", + after="after", + before="before", filter="in_progress", limit=0, order="asc", @@ -403,9 +414,9 @@ async def test_method_list_files_with_all_params(self, async_client: AsyncOpenAI @parametrize async def test_raw_response_list_files(self, async_client: AsyncOpenAI) -> None: - response = await async_client.beta.vector_stores.file_batches.with_raw_response.list_files( - "string", - vector_store_id="string", + response = await async_client.vector_stores.file_batches.with_raw_response.list_files( + batch_id="batch_id", + vector_store_id="vector_store_id", ) assert response.is_closed is True @@ -415,9 +426,9 @@ async def test_raw_response_list_files(self, async_client: AsyncOpenAI) -> None: @parametrize async def test_streaming_response_list_files(self, async_client: AsyncOpenAI) -> None: - async with async_client.beta.vector_stores.file_batches.with_streaming_response.list_files( - "string", - vector_store_id="string", + async with async_client.vector_stores.file_batches.with_streaming_response.list_files( + batch_id="batch_id", + vector_store_id="vector_store_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -430,13 +441,24 @@ async def test_streaming_response_list_files(self, async_client: AsyncOpenAI) -> @parametrize async def test_path_params_list_files(self, async_client: AsyncOpenAI) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.beta.vector_stores.file_batches.with_raw_response.list_files( - "string", + await async_client.vector_stores.file_batches.with_raw_response.list_files( + batch_id="batch_id", vector_store_id="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): - await async_client.beta.vector_stores.file_batches.with_raw_response.list_files( - "", - vector_store_id="string", + await async_client.vector_stores.file_batches.with_raw_response.list_files( + batch_id="", + vector_store_id="vector_store_id", ) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +def test_create_and_poll_method_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: + checking_client: OpenAI | AsyncOpenAI = client if sync else async_client + + # ensure helpers do not drift from generated spec + assert_signatures_in_sync( + checking_client.vector_stores.file_batches.create, + checking_client.vector_stores.file_batches.create_and_poll, + ) diff --git a/tests/api_resources/vector_stores/test_files.py b/tests/api_resources/vector_stores/test_files.py new file mode 100644 index 0000000000..53aa5ee041 --- /dev/null +++ b/tests/api_resources/vector_stores/test_files.py @@ -0,0 +1,649 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai._utils import assert_signatures_in_sync +from openai.pagination import SyncPage, AsyncPage, SyncCursorPage, AsyncCursorPage +from openai.types.vector_stores import ( + VectorStoreFile, + FileContentResponse, + VectorStoreFileDeleted, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestFiles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + file = client.vector_stores.files.create( + vector_store_id="vs_abc123", + file_id="file_id", + ) + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + file = client.vector_stores.files.create( + vector_store_id="vs_abc123", + file_id="file_id", + attributes={"foo": "string"}, + chunking_strategy={"type": "auto"}, + ) + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.vector_stores.files.with_raw_response.create( + vector_store_id="vs_abc123", + file_id="file_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.vector_stores.files.with_streaming_response.create( + vector_store_id="vs_abc123", + file_id="file_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + client.vector_stores.files.with_raw_response.create( + vector_store_id="", + file_id="file_id", + ) + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + file = client.vector_stores.files.retrieve( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.vector_stores.files.with_raw_response.retrieve( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.vector_stores.files.with_streaming_response.retrieve( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + client.vector_stores.files.with_raw_response.retrieve( + file_id="file-abc123", + vector_store_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.vector_stores.files.with_raw_response.retrieve( + file_id="", + vector_store_id="vs_abc123", + ) + + @parametrize + def test_method_update(self, client: OpenAI) -> None: + file = client.vector_stores.files.update( + file_id="file-abc123", + vector_store_id="vs_abc123", + attributes={"foo": "string"}, + ) + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: OpenAI) -> None: + response = client.vector_stores.files.with_raw_response.update( + file_id="file-abc123", + vector_store_id="vs_abc123", + attributes={"foo": "string"}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.vector_stores.files.with_streaming_response.update( + file_id="file-abc123", + vector_store_id="vs_abc123", + attributes={"foo": "string"}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + client.vector_stores.files.with_raw_response.update( + file_id="file-abc123", + vector_store_id="", + attributes={"foo": "string"}, + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.vector_stores.files.with_raw_response.update( + file_id="", + vector_store_id="vs_abc123", + attributes={"foo": "string"}, + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + file = client.vector_stores.files.list( + vector_store_id="vector_store_id", + ) + assert_matches_type(SyncCursorPage[VectorStoreFile], file, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + file = client.vector_stores.files.list( + vector_store_id="vector_store_id", + after="after", + before="before", + filter="in_progress", + limit=0, + order="asc", + ) + assert_matches_type(SyncCursorPage[VectorStoreFile], file, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.vector_stores.files.with_raw_response.list( + vector_store_id="vector_store_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(SyncCursorPage[VectorStoreFile], file, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.vector_stores.files.with_streaming_response.list( + vector_store_id="vector_store_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(SyncCursorPage[VectorStoreFile], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + client.vector_stores.files.with_raw_response.list( + vector_store_id="", + ) + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + file = client.vector_stores.files.delete( + file_id="file_id", + vector_store_id="vector_store_id", + ) + assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.vector_stores.files.with_raw_response.delete( + file_id="file_id", + vector_store_id="vector_store_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.vector_stores.files.with_streaming_response.delete( + file_id="file_id", + vector_store_id="vector_store_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + client.vector_stores.files.with_raw_response.delete( + file_id="file_id", + vector_store_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.vector_stores.files.with_raw_response.delete( + file_id="", + vector_store_id="vector_store_id", + ) + + @parametrize + def test_method_content(self, client: OpenAI) -> None: + file = client.vector_stores.files.content( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) + assert_matches_type(SyncPage[FileContentResponse], file, path=["response"]) + + @parametrize + def test_raw_response_content(self, client: OpenAI) -> None: + response = client.vector_stores.files.with_raw_response.content( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(SyncPage[FileContentResponse], file, path=["response"]) + + @parametrize + def test_streaming_response_content(self, client: OpenAI) -> None: + with client.vector_stores.files.with_streaming_response.content( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(SyncPage[FileContentResponse], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_content(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + client.vector_stores.files.with_raw_response.content( + file_id="file-abc123", + vector_store_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.vector_stores.files.with_raw_response.content( + file_id="", + vector_store_id="vs_abc123", + ) + + +class TestAsyncFiles: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + file = await async_client.vector_stores.files.create( + vector_store_id="vs_abc123", + file_id="file_id", + ) + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + file = await async_client.vector_stores.files.create( + vector_store_id="vs_abc123", + file_id="file_id", + attributes={"foo": "string"}, + chunking_strategy={"type": "auto"}, + ) + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.vector_stores.files.with_raw_response.create( + vector_store_id="vs_abc123", + file_id="file_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.vector_stores.files.with_streaming_response.create( + vector_store_id="vs_abc123", + file_id="file_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + await async_client.vector_stores.files.with_raw_response.create( + vector_store_id="", + file_id="file_id", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + file = await async_client.vector_stores.files.retrieve( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.vector_stores.files.with_raw_response.retrieve( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.vector_stores.files.with_streaming_response.retrieve( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + await async_client.vector_stores.files.with_raw_response.retrieve( + file_id="file-abc123", + vector_store_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.vector_stores.files.with_raw_response.retrieve( + file_id="", + vector_store_id="vs_abc123", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenAI) -> None: + file = await async_client.vector_stores.files.update( + file_id="file-abc123", + vector_store_id="vs_abc123", + attributes={"foo": "string"}, + ) + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenAI) -> None: + response = await async_client.vector_stores.files.with_raw_response.update( + file_id="file-abc123", + vector_store_id="vs_abc123", + attributes={"foo": "string"}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenAI) -> None: + async with async_client.vector_stores.files.with_streaming_response.update( + file_id="file-abc123", + vector_store_id="vs_abc123", + attributes={"foo": "string"}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(VectorStoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + await async_client.vector_stores.files.with_raw_response.update( + file_id="file-abc123", + vector_store_id="", + attributes={"foo": "string"}, + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.vector_stores.files.with_raw_response.update( + file_id="", + vector_store_id="vs_abc123", + attributes={"foo": "string"}, + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + file = await async_client.vector_stores.files.list( + vector_store_id="vector_store_id", + ) + assert_matches_type(AsyncCursorPage[VectorStoreFile], file, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + file = await async_client.vector_stores.files.list( + vector_store_id="vector_store_id", + after="after", + before="before", + filter="in_progress", + limit=0, + order="asc", + ) + assert_matches_type(AsyncCursorPage[VectorStoreFile], file, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.vector_stores.files.with_raw_response.list( + vector_store_id="vector_store_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(AsyncCursorPage[VectorStoreFile], file, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.vector_stores.files.with_streaming_response.list( + vector_store_id="vector_store_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(AsyncCursorPage[VectorStoreFile], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + await async_client.vector_stores.files.with_raw_response.list( + vector_store_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + file = await async_client.vector_stores.files.delete( + file_id="file_id", + vector_store_id="vector_store_id", + ) + assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.vector_stores.files.with_raw_response.delete( + file_id="file_id", + vector_store_id="vector_store_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.vector_stores.files.with_streaming_response.delete( + file_id="file_id", + vector_store_id="vector_store_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + await async_client.vector_stores.files.with_raw_response.delete( + file_id="file_id", + vector_store_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.vector_stores.files.with_raw_response.delete( + file_id="", + vector_store_id="vector_store_id", + ) + + @parametrize + async def test_method_content(self, async_client: AsyncOpenAI) -> None: + file = await async_client.vector_stores.files.content( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) + assert_matches_type(AsyncPage[FileContentResponse], file, path=["response"]) + + @parametrize + async def test_raw_response_content(self, async_client: AsyncOpenAI) -> None: + response = await async_client.vector_stores.files.with_raw_response.content( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(AsyncPage[FileContentResponse], file, path=["response"]) + + @parametrize + async def test_streaming_response_content(self, async_client: AsyncOpenAI) -> None: + async with async_client.vector_stores.files.with_streaming_response.content( + file_id="file-abc123", + vector_store_id="vs_abc123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(AsyncPage[FileContentResponse], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_content(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): + await async_client.vector_stores.files.with_raw_response.content( + file_id="file-abc123", + vector_store_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.vector_stores.files.with_raw_response.content( + file_id="", + vector_store_id="vs_abc123", + ) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +def test_create_and_poll_method_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: + checking_client: OpenAI | AsyncOpenAI = client if sync else async_client + + assert_signatures_in_sync( + checking_client.vector_stores.files.create, + checking_client.vector_stores.files.create_and_poll, + ) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +def test_upload_and_poll_method_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: + checking_client: OpenAI | AsyncOpenAI = client if sync else async_client + + assert_signatures_in_sync( + checking_client.vector_stores.files.create, + checking_client.vector_stores.files.upload_and_poll, + exclude_params={"file_id", "extra_headers", "extra_query", "extra_body", "timeout"}, + ) diff --git a/tests/api_resources/webhooks/__init__.py b/tests/api_resources/webhooks/__init__.py new file mode 100644 index 0000000000..fd8019a9a1 --- /dev/null +++ b/tests/api_resources/webhooks/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/compat/test_tool_param.py b/tests/compat/test_tool_param.py new file mode 100644 index 0000000000..f2f84c6e94 --- /dev/null +++ b/tests/compat/test_tool_param.py @@ -0,0 +1,8 @@ +from openai.types.chat import ChatCompletionToolParam + + +def test_tool_param_can_be_instantiated() -> None: + assert ChatCompletionToolParam(type="function", function={"name": "test"}) == { + "function": {"name": "test"}, + "type": "function", + } diff --git a/tests/conftest.py b/tests/conftest.py index 15af57e770..1042fe59d9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,32 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os -import asyncio import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest +from pytest_asyncio import is_async_test -from openai import OpenAI, AsyncOpenAI +from openai import OpenAI, AsyncOpenAI, DefaultAioHttpClient +from openai._utils import is_dict if TYPE_CHECKING: - from _pytest.fixtures import FixtureRequest + from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] pytest.register_assert_rewrite("tests.utils") logging.getLogger("openai").setLevel(logging.DEBUG) -@pytest.fixture(scope="session") -def event_loop() -> Iterator[asyncio.AbstractEventLoop]: - loop = asyncio.new_event_loop() - yield loop - loop.close() +# automatically add `pytest.mark.asyncio()` to all of our async tests +# so we don't have to add that boilerplate everywhere +def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: + pytest_asyncio_tests = (item for item in items if is_async_test(item)) + session_scope_marker = pytest.mark.asyncio(loop_scope="session") + for async_test in pytest_asyncio_tests: + async_test.add_marker(session_scope_marker, append=False) + + # We skip tests that use both the aiohttp client and respx_mock as respx_mock + # doesn't support custom transports. + for item in items: + if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: + continue + + if not hasattr(item, "callspec"): + continue + + async_client_param = item.callspec.params.get("async_client") + if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") api_key = "My API Key" +admin_api_key = "My Admin API Key" @pytest.fixture(scope="session") @@ -35,15 +55,37 @@ def client(request: FixtureRequest) -> Iterator[OpenAI]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + with OpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=strict + ) as client: yield client @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncOpenAI]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - - async with AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + param = getattr(request, "param", True) + + # defaults + strict = True + http_client: None | httpx.AsyncClient = None + + if isinstance(param, bool): + strict = param + elif is_dict(param): + strict = param.get("strict", True) + assert isinstance(strict, bool) + + http_client_type = param.get("http_client", "httpx") + if http_client_type == "aiohttp": + http_client = DefaultAioHttpClient() + else: + raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") + + async with AsyncOpenAI( + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=strict, + http_client=http_client, + ) as client: yield client diff --git a/tests/lib/chat/_utils.py b/tests/lib/chat/_utils.py deleted file mode 100644 index dcc32b17fd..0000000000 --- a/tests/lib/chat/_utils.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import annotations - -import io -import inspect -from typing import Any, Iterable -from typing_extensions import TypeAlias - -import rich -import pytest -import pydantic - -ReprArgs: TypeAlias = "Iterable[tuple[str | None, Any]]" - - -def print_obj(obj: object, monkeypatch: pytest.MonkeyPatch) -> str: - """Pretty print an object to a string""" - - # monkeypatch pydantic model printing so that model fields - # are always printed in the same order so we can reliably - # use this for snapshot tests - original_repr = pydantic.BaseModel.__repr_args__ - - def __repr_args__(self: pydantic.BaseModel) -> ReprArgs: - return sorted(original_repr(self), key=lambda arg: arg[0] or arg) - - with monkeypatch.context() as m: - m.setattr(pydantic.BaseModel, "__repr_args__", __repr_args__) - - buf = io.StringIO() - - console = rich.console.Console(file=buf, width=120) - console.print(obj) - - string = buf.getvalue() - - # we remove all `fn_name..` occurences - # so that we can share the same snapshots between - # pydantic v1 and pydantic v2 as their output for - # generic models differs, e.g. - # - # v2: `ParsedChatCompletion[test_parse_pydantic_model..Location]` - # v1: `ParsedChatCompletion[Location]` - return clear_locals(string, stacklevel=2) - - -def get_caller_name(*, stacklevel: int = 1) -> str: - frame = inspect.currentframe() - assert frame is not None - - for i in range(stacklevel): - frame = frame.f_back - assert frame is not None, f"no {i}th frame" - - return frame.f_code.co_name - - -def clear_locals(string: str, *, stacklevel: int) -> str: - caller = get_caller_name(stacklevel=stacklevel + 1) - return string.replace(f"{caller}..", "") diff --git a/tests/lib/chat/test_completions.py b/tests/lib/chat/test_completions.py index e7b9c4f1fd..741f5eaa75 100644 --- a/tests/lib/chat/test_completions.py +++ b/tests/lib/chat/test_completions.py @@ -1,12 +1,9 @@ from __future__ import annotations -import os -import json from enum import Enum -from typing import Any, List, Callable, Optional +from typing import List, Optional from typing_extensions import Literal, TypeVar -import httpx import pytest from respx import MockRouter from pydantic import Field, BaseModel @@ -15,10 +12,11 @@ import openai from openai import OpenAI, AsyncOpenAI from openai._utils import assert_signatures_in_sync -from openai._compat import PYDANTIC_V2 +from openai._compat import PYDANTIC_V1 -from ._utils import print_obj +from ..utils import print_obj from ...conftest import base_url +from ..snapshots import make_snapshot_request, make_async_snapshot_request from ..schema_types.query import Query _T = TypeVar("_T") @@ -27,13 +25,13 @@ # # you can update them with # -# `OPENAI_LIVE=1 pytest --inline-snapshot=fix` +# `OPENAI_LIVE=1 pytest --inline-snapshot=fix -p no:xdist -o addopts=""` @pytest.mark.respx(base_url=base_url) def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -43,38 +41,53 @@ def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pyte ], ), content_snapshot=snapshot( - '{"id": "chatcmpl-9tXjSozlYq8oGdlRH3vgLsiUNRg8c", "object": "chat.completion", "created": 1723024734, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "I\'m unable to provide real-time weather updates. To find out the current weather in San Francisco, please check a reliable weather website or app.", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 14, "completion_tokens": 28, "total_tokens": 42}, "system_fingerprint": "fp_845eaabc1f"}' + '{"id": "chatcmpl-ABfvaueLEMLNYbT8YzpJxsmiQ6HSY", "object": "chat.completion", "created": 1727346142, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "I\'m unable to provide real-time weather updates. To get the current weather in San Francisco, I recommend checking a reliable weather website or app like the Weather Channel or a local news station.", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 14, "completion_tokens": 37, "total_tokens": 51, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_b40fb1c6fb"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) assert print_obj(completion, monkeypatch) == snapshot( """\ -ParsedChatCompletion[NoneType]( +ParsedChatCompletion( choices=[ - ParsedChoice[NoneType]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[NoneType]( - content="I'm unable to provide real-time weather updates. To find out the current weather in San -Francisco, please check a reliable weather website or app.", + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content="I'm unable to provide real-time weather updates. To get the current weather in San Francisco, I +recommend checking a reliable weather website or app like the Weather Channel or a local news station.", function_call=None, parsed=None, refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) ], - created=1723024734, - id='chatcmpl-9tXjSozlYq8oGdlRH3vgLsiUNRg8c', + created=1727346142, + id='chatcmpl-ABfvaueLEMLNYbT8YzpJxsmiQ6HSY', model='gpt-4o-2024-08-06', + moderation=None, object='chat.completion', service_tier=None, - system_fingerprint='fp_845eaabc1f', - usage=CompletionUsage(completion_tokens=28, completion_tokens_details=None, prompt_tokens=14, total_tokens=42) + system_fingerprint='fp_b40fb1c6fb', + usage=CompletionUsage( + completion_tokens=37, + completion_tokens_details=CompletionTokensDetails( + accepted_prediction_tokens=None, + audio_tokens=None, + reasoning_tokens=0, + rejected_prediction_tokens=None + ), + prompt_tokens=14, + prompt_tokens_details=None, + total_tokens=51 + ) ) """ ) @@ -87,8 +100,8 @@ class Location(BaseModel): temperature: float units: Literal["c", "f"] - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -99,37 +112,52 @@ class Location(BaseModel): response_format=Location, ), content_snapshot=snapshot( - '{"id": "chatcmpl-9tXjTNupyDe7nL1Z8eOO6BdSyrHAD", "object": "chat.completion", "created": 1723024735, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":56,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 17, "completion_tokens": 14, "total_tokens": 31}, "system_fingerprint": "fp_2a322c9ffc"}' + '{"id": "chatcmpl-ABfvbtVnTu5DeC4EFnRYj8mtfOM99", "object": "chat.completion", "created": 1727346143, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":65,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 79, "completion_tokens": 14, "total_tokens": 93, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_5050236cbd"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) assert print_obj(completion, monkeypatch) == snapshot( """\ -ParsedChatCompletion[Location]( +ParsedChatCompletion( choices=[ - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[Location]( - content='{"city":"San Francisco","temperature":56,"units":"f"}', + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":65,"units":"f"}', function_call=None, - parsed=Location(city='San Francisco', temperature=56.0, units='f'), + parsed=Location(city='San Francisco', temperature=65.0, units='f'), refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) ], - created=1723024735, - id='chatcmpl-9tXjTNupyDe7nL1Z8eOO6BdSyrHAD', + created=1727346143, + id='chatcmpl-ABfvbtVnTu5DeC4EFnRYj8mtfOM99', model='gpt-4o-2024-08-06', + moderation=None, object='chat.completion', service_tier=None, - system_fingerprint='fp_2a322c9ffc', - usage=CompletionUsage(completion_tokens=14, completion_tokens_details=None, prompt_tokens=17, total_tokens=31) + system_fingerprint='fp_5050236cbd', + usage=CompletionUsage( + completion_tokens=14, + completion_tokens_details=CompletionTokensDetails( + accepted_prediction_tokens=None, + audio_tokens=None, + reasoning_tokens=0, + rejected_prediction_tokens=None + ), + prompt_tokens=79, + prompt_tokens_details=None, + total_tokens=93 + ) ) """ ) @@ -144,8 +172,8 @@ class Location(BaseModel): temperature: float units: Optional[Literal["c", "f"]] = None - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -156,37 +184,52 @@ class Location(BaseModel): response_format=Location, ), content_snapshot=snapshot( - '{"id": "chatcmpl-9y39Q2jGzWmeEZlm5CoNVOuQzcxP4", "object": "chat.completion", "created": 1724098820, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":62,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 17, "completion_tokens": 14, "total_tokens": 31}, "system_fingerprint": "fp_2a322c9ffc"}' + '{"id": "chatcmpl-ABfvcC8grKYsRkSoMp9CCAhbXAd0b", "object": "chat.completion", "created": 1727346144, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":65,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 88, "completion_tokens": 14, "total_tokens": 102, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_b40fb1c6fb"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) assert print_obj(completion, monkeypatch) == snapshot( """\ -ParsedChatCompletion[Location]( +ParsedChatCompletion( choices=[ - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[Location]( - content='{"city":"San Francisco","temperature":62,"units":"f"}', + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":65,"units":"f"}', function_call=None, - parsed=Location(city='San Francisco', temperature=62.0, units='f'), + parsed=Location(city='San Francisco', temperature=65.0, units='f'), refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) ], - created=1724098820, - id='chatcmpl-9y39Q2jGzWmeEZlm5CoNVOuQzcxP4', + created=1727346144, + id='chatcmpl-ABfvcC8grKYsRkSoMp9CCAhbXAd0b', model='gpt-4o-2024-08-06', + moderation=None, object='chat.completion', service_tier=None, - system_fingerprint='fp_2a322c9ffc', - usage=CompletionUsage(completion_tokens=14, completion_tokens_details=None, prompt_tokens=17, total_tokens=31) + system_fingerprint='fp_b40fb1c6fb', + usage=CompletionUsage( + completion_tokens=14, + completion_tokens_details=CompletionTokensDetails( + accepted_prediction_tokens=None, + audio_tokens=None, + reasoning_tokens=0, + rejected_prediction_tokens=None + ), + prompt_tokens=88, + prompt_tokens_details=None, + total_tokens=102 + ) ) """ ) @@ -205,11 +248,11 @@ class ColorDetection(BaseModel): color: Color hex_color_code: str = Field(description="The hex color code of the detected color") - if not PYDANTIC_V2: + if PYDANTIC_V1: ColorDetection.update_forward_refs(**locals()) # type: ignore - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ {"role": "user", "content": "What color is a Coke can?"}, @@ -217,25 +260,28 @@ class ColorDetection(BaseModel): response_format=ColorDetection, ), content_snapshot=snapshot( - '{"id": "chatcmpl-9vK4UZVr385F2UgZlP1ShwPn2nFxG", "object": "chat.completion", "created": 1723448878, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"color\\":\\"red\\",\\"hex_color_code\\":\\"#FF0000\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 18, "completion_tokens": 14, "total_tokens": 32}, "system_fingerprint": "fp_845eaabc1f"}' + '{"id": "chatcmpl-ABfvjIatz0zrZu50gRbMtlp0asZpz", "object": "chat.completion", "created": 1727346151, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"color\\":\\"red\\",\\"hex_color_code\\":\\"#FF0000\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 109, "completion_tokens": 14, "total_tokens": 123, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_5050236cbd"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) assert print_obj(completion.choices[0], monkeypatch) == snapshot( """\ -ParsedChoice[ColorDetection]( +ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[ColorDetection]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content='{"color":"red","hex_color_code":"#FF0000"}', function_call=None, parsed=ColorDetection(color=, hex_color_code='#FF0000'), refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) """ @@ -251,8 +297,8 @@ class Location(BaseModel): temperature: float units: Literal["c", "f"] - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -264,8 +310,9 @@ class Location(BaseModel): response_format=Location, ), content_snapshot=snapshot( - '{"id": "chatcmpl-9tXjUrNFyyjSB2FJ842TMDNRM6Gen", "object": "chat.completion", "created": 1723024736, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":58,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}, {"index": 1, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":58,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}, {"index": 2, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":63,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 17, "completion_tokens": 42, "total_tokens": 59}, "system_fingerprint": "fp_845eaabc1f"}' + '{"id": "chatcmpl-ABfvp8qzboW92q8ONDF4DPHlI7ckC", "object": "chat.completion", "created": 1727346157, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":64,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}, {"index": 1, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":65,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}, {"index": 2, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":63.0,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 79, "completion_tokens": 44, "total_tokens": 123, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_b40fb1c6fb"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) @@ -273,43 +320,49 @@ class Location(BaseModel): assert print_obj(completion.choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[Location]( - content='{"city":"San Francisco","temperature":58,"units":"f"}', + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":64,"units":"f"}', function_call=None, - parsed=Location(city='San Francisco', temperature=58.0, units='f'), + parsed=Location(city='San Francisco', temperature=64.0, units='f'), refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ), - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=1, logprobs=None, - message=ParsedChatCompletionMessage[Location]( - content='{"city":"San Francisco","temperature":58,"units":"f"}', + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":65,"units":"f"}', function_call=None, - parsed=Location(city='San Francisco', temperature=58.0, units='f'), + parsed=Location(city='San Francisco', temperature=65.0, units='f'), refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ), - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=2, logprobs=None, - message=ParsedChatCompletionMessage[Location]( - content='{"city":"San Francisco","temperature":63,"units":"f"}', + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":63.0,"units":"f"}', function_call=None, parsed=Location(city='San Francisco', temperature=63.0, units='f'), refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) ] @@ -318,7 +371,7 @@ class Location(BaseModel): @pytest.mark.respx(base_url=base_url) -@pytest.mark.skipif(not PYDANTIC_V2, reason="dataclasses only supported in v2") +@pytest.mark.skipif(PYDANTIC_V1, reason="dataclasses only supported in v2") def test_parse_pydantic_dataclass(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: from pydantic.dataclasses import dataclass @@ -328,8 +381,8 @@ class CalendarEvent: date: str participants: List[str] - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ {"role": "system", "content": "Extract the event information."}, @@ -338,37 +391,52 @@ class CalendarEvent: response_format=CalendarEvent, ), content_snapshot=snapshot( - '{"id": "chatcmpl-9wdGqXkJJARAz7rOrLH5u5FBwLjF3", "object": "chat.completion", "created": 1723761008, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"name\\":\\"Science Fair\\",\\"date\\":\\"Friday\\",\\"participants\\":[\\"Alice\\",\\"Bob\\"]}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 32, "completion_tokens": 17, "total_tokens": 49}, "system_fingerprint": "fp_2a322c9ffc"}' + '{"id": "chatcmpl-ABfvqhz4uUUWsw8Ohw2Mp9B4sKKV8", "object": "chat.completion", "created": 1727346158, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"name\\":\\"Science Fair\\",\\"date\\":\\"Friday\\",\\"participants\\":[\\"Alice\\",\\"Bob\\"]}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 92, "completion_tokens": 17, "total_tokens": 109, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_7568d46099"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) assert print_obj(completion, monkeypatch) == snapshot( """\ -ParsedChatCompletion[CalendarEvent]( +ParsedChatCompletion( choices=[ - ParsedChoice[CalendarEvent]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[CalendarEvent]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content='{"name":"Science Fair","date":"Friday","participants":["Alice","Bob"]}', function_call=None, parsed=CalendarEvent(name='Science Fair', date='Friday', participants=['Alice', 'Bob']), refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) ], - created=1723761008, - id='chatcmpl-9wdGqXkJJARAz7rOrLH5u5FBwLjF3', + created=1727346158, + id='chatcmpl-ABfvqhz4uUUWsw8Ohw2Mp9B4sKKV8', model='gpt-4o-2024-08-06', + moderation=None, object='chat.completion', service_tier=None, - system_fingerprint='fp_2a322c9ffc', - usage=CompletionUsage(completion_tokens=17, completion_tokens_details=None, prompt_tokens=32, total_tokens=49) + system_fingerprint='fp_7568d46099', + usage=CompletionUsage( + completion_tokens=17, + completion_tokens_details=CompletionTokensDetails( + accepted_prediction_tokens=None, + audio_tokens=None, + reasoning_tokens=0, + rejected_prediction_tokens=None + ), + prompt_tokens=92, + prompt_tokens_details=None, + total_tokens=109 + ) ) """ ) @@ -376,8 +444,8 @@ class CalendarEvent: @pytest.mark.respx(base_url=base_url) def test_pydantic_tool_model_all_types(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -389,19 +457,22 @@ def test_pydantic_tool_model_all_types(client: OpenAI, respx_mock: MockRouter, m response_format=Query, ), content_snapshot=snapshot( - '{"id": "chatcmpl-9tXjVJVCLTn7CWFhpjETixvvApCk3", "object": "chat.completion", "created": 1723024737, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_Un4g0IXeQGOyqKBS3zhqNCox", "type": "function", "function": {"name": "Query", "arguments": "{\\"table_name\\":\\"orders\\",\\"columns\\":[\\"id\\",\\"status\\",\\"expected_delivery_date\\",\\"delivered_at\\",\\"shipped_at\\",\\"ordered_at\\"],\\"conditions\\":[{\\"column\\":\\"ordered_at\\",\\"operator\\":\\">=\\",\\"value\\":\\"2022-05-01\\"},{\\"column\\":\\"ordered_at\\",\\"operator\\":\\"<=\\",\\"value\\":\\"2022-05-31\\"},{\\"column\\":\\"status\\",\\"operator\\":\\"=\\",\\"value\\":\\"fulfilled\\"},{\\"column\\":\\"delivered_at\\",\\"operator\\":\\">\\",\\"value\\":{\\"column_name\\":\\"expected_delivery_date\\"}}],\\"order_by\\":\\"asc\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 195, "completion_tokens": 114, "total_tokens": 309}, "system_fingerprint": "fp_845eaabc1f"}' + '{"id": "chatcmpl-ABfvtNiaTNUF6OymZUnEFc9lPq9p1", "object": "chat.completion", "created": 1727346161, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_NKpApJybW1MzOjZO2FzwYw0d", "type": "function", "function": {"name": "Query", "arguments": "{\\"name\\":\\"May 2022 Fulfilled Orders Not Delivered on Time\\",\\"table_name\\":\\"orders\\",\\"columns\\":[\\"id\\",\\"status\\",\\"expected_delivery_date\\",\\"delivered_at\\",\\"shipped_at\\",\\"ordered_at\\",\\"canceled_at\\"],\\"conditions\\":[{\\"column\\":\\"ordered_at\\",\\"operator\\":\\">=\\",\\"value\\":\\"2022-05-01\\"},{\\"column\\":\\"ordered_at\\",\\"operator\\":\\"<=\\",\\"value\\":\\"2022-05-31\\"},{\\"column\\":\\"status\\",\\"operator\\":\\"=\\",\\"value\\":\\"fulfilled\\"},{\\"column\\":\\"delivered_at\\",\\"operator\\":\\">\\",\\"value\\":{\\"column_name\\":\\"expected_delivery_date\\"}}],\\"order_by\\":\\"asc\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 512, "completion_tokens": 132, "total_tokens": 644, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_7568d46099"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) assert print_obj(completion.choices[0], monkeypatch) == snapshot( """\ -ParsedChoice[Query]( +ParsedChoice( finish_reason='tool_calls', index=0, logprobs=None, - message=ParsedChatCompletionMessage[Query]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, @@ -410,10 +481,11 @@ def test_pydantic_tool_model_all_types(client: OpenAI, respx_mock: MockRouter, m tool_calls=[ ParsedFunctionToolCall( function=ParsedFunction( - arguments='{"table_name":"orders","columns":["id","status","expected_delivery_date","delivered_at"," -shipped_at","ordered_at"],"conditions":[{"column":"ordered_at","operator":">=","value":"2022-05-01"},{"column":"ordered_ -at","operator":"<=","value":"2022-05-31"},{"column":"status","operator":"=","value":"fulfilled"},{"column":"delivered_at -","operator":">","value":{"column_name":"expected_delivery_date"}}],"order_by":"asc"}', + arguments='{"name":"May 2022 Fulfilled Orders Not Delivered on +Time","table_name":"orders","columns":["id","status","expected_delivery_date","delivered_at","shipped_at","ordered_at"," +canceled_at"],"conditions":[{"column":"ordered_at","operator":">=","value":"2022-05-01"},{"column":"ordered_at","operato +r":"<=","value":"2022-05-31"},{"column":"status","operator":"=","value":"fulfilled"},{"column":"delivered_at","operator" +:">","value":{"column_name":"expected_delivery_date"}}],"order_by":"asc"}', name='Query', parsed_arguments=Query( columns=[ @@ -422,7 +494,8 @@ def test_pydantic_tool_model_all_types(client: OpenAI, respx_mock: MockRouter, m , , , - + , + ], conditions=[ Condition(column='ordered_at', operator=='>, value='2022-05-01'), @@ -434,12 +507,12 @@ def test_pydantic_tool_model_all_types(client: OpenAI, respx_mock: MockRouter, m value=DynamicValue(column_name='expected_delivery_date') ) ], - name=None, + name='May 2022 Fulfilled Orders Not Delivered on Time', order_by=, table_name= ) ), - id='call_Un4g0IXeQGOyqKBS3zhqNCox', + id='call_NKpApJybW1MzOjZO2FzwYw0d', type='function' ) ] @@ -457,8 +530,8 @@ class Location(BaseModel): units: Literal["c", "f"] with pytest.raises(openai.LengthFinishReasonError): - _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -470,8 +543,9 @@ class Location(BaseModel): response_format=Location, ), content_snapshot=snapshot( - '{"id": "chatcmpl-9tXjYACgVKixKdMv2nVQqDVELkdSF", "object": "chat.completion", "created": 1723024740, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"", "refusal": null}, "logprobs": null, "finish_reason": "length"}], "usage": {"prompt_tokens": 17, "completion_tokens": 1, "total_tokens": 18}, "system_fingerprint": "fp_2a322c9ffc"}' + '{"id": "chatcmpl-ABfvvX7eB1KsfeZj8VcF3z7G7SbaA", "object": "chat.completion", "created": 1727346163, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"", "refusal": null}, "logprobs": null, "finish_reason": "length"}], "usage": {"prompt_tokens": 79, "completion_tokens": 1, "total_tokens": 80, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_7568d46099"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) @@ -484,8 +558,8 @@ class Location(BaseModel): temperature: float units: Literal["c", "f"] - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -496,8 +570,9 @@ class Location(BaseModel): response_format=Location, ), content_snapshot=snapshot( - '{"id": "chatcmpl-9tXm7FnIj3hSot5xM4c954MIePle0", "object": "chat.completion", "created": 1723024899, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "refusal": "I\'m very sorry, but I can\'t assist with that request."}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 17, "completion_tokens": 13, "total_tokens": 30}, "system_fingerprint": "fp_845eaabc1f"}' + '{"id": "chatcmpl-ABfvwoKVWPQj2UPlAcAKM7s40GsRx", "object": "chat.completion", "created": 1727346164, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "refusal": "I\'m very sorry, but I can\'t assist with that."}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 79, "completion_tokens": 12, "total_tokens": 91, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_5050236cbd"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) @@ -505,17 +580,19 @@ class Location(BaseModel): assert print_obj(completion.choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[Location]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, - refusal="I'm very sorry, but I can't assist with that request.", + refusal="I'm very sorry, but I can't assist with that.", role='assistant', - tool_calls=[] + tool_calls=None ) ) ] @@ -530,8 +607,8 @@ class GetWeatherArgs(BaseModel): country: str units: Literal["c", "f"] = "c" - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -544,8 +621,9 @@ class GetWeatherArgs(BaseModel): ], ), content_snapshot=snapshot( - '{"id": "chatcmpl-9tXjbQ9V0l5XPlynOJHKvrWsJQymO", "object": "chat.completion", "created": 1723024743, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_EEaIYq8aTdiDWro8jILNl3XK", "type": "function", "function": {"name": "GetWeatherArgs", "arguments": "{\\"city\\":\\"Edinburgh\\",\\"country\\":\\"GB\\",\\"units\\":\\"c\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 76, "completion_tokens": 24, "total_tokens": 100}, "system_fingerprint": "fp_2a322c9ffc"}' + '{"id": "chatcmpl-ABfvx6Z4dchiW2nya1N8KMsHFrQRE", "object": "chat.completion", "created": 1727346165, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_Y6qJ7ofLgOrBnMD5WbVAeiRV", "type": "function", "function": {"name": "GetWeatherArgs", "arguments": "{\\"city\\":\\"Edinburgh\\",\\"country\\":\\"UK\\",\\"units\\":\\"c\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 76, "completion_tokens": 24, "total_tokens": 100, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_e45dabd248"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) @@ -553,11 +631,13 @@ class GetWeatherArgs(BaseModel): assert print_obj(completion.choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[NoneType]( + ParsedChoice( finish_reason='tool_calls', index=0, logprobs=None, - message=ParsedChatCompletionMessage[NoneType]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, @@ -566,11 +646,11 @@ class GetWeatherArgs(BaseModel): tool_calls=[ ParsedFunctionToolCall( function=ParsedFunction( - arguments='{"city":"Edinburgh","country":"GB","units":"c"}', + arguments='{"city":"Edinburgh","country":"UK","units":"c"}', name='GetWeatherArgs', - parsed_arguments=GetWeatherArgs(city='Edinburgh', country='GB', units='c') + parsed_arguments=GetWeatherArgs(city='Edinburgh', country='UK', units='c') ), - id='call_EEaIYq8aTdiDWro8jILNl3XK', + id='call_Y6qJ7ofLgOrBnMD5WbVAeiRV', type='function' ) ] @@ -594,8 +674,8 @@ class GetStockPrice(BaseModel): ticker: str exchange: str - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -615,8 +695,9 @@ class GetStockPrice(BaseModel): ], ), content_snapshot=snapshot( - '{"id": "chatcmpl-9tXjcnIvzZDXRfLfbVTPNL5963GWw", "object": "chat.completion", "created": 1723024744, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_ECSuZ8gcNPPwgt24me91jHsJ", "type": "function", "function": {"name": "GetWeatherArgs", "arguments": "{\\"city\\": \\"Edinburgh\\", \\"country\\": \\"UK\\", \\"units\\": \\"c\\"}"}}, {"id": "call_Z3fM2sNBBGILhMtimk5Y3RQk", "type": "function", "function": {"name": "get_stock_price", "arguments": "{\\"ticker\\": \\"AAPL\\", \\"exchange\\": \\"NASDAQ\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 149, "completion_tokens": 60, "total_tokens": 209}, "system_fingerprint": "fp_845eaabc1f"}' + '{"id": "chatcmpl-ABfvyvfNWKcl7Ohqos4UFrmMs1v4C", "object": "chat.completion", "created": 1727346166, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_fdNz3vOBKYgOIpMdWotB9MjY", "type": "function", "function": {"name": "GetWeatherArgs", "arguments": "{\\"city\\": \\"Edinburgh\\", \\"country\\": \\"GB\\", \\"units\\": \\"c\\"}"}}, {"id": "call_h1DWI1POMJLb0KwIyQHWXD4p", "type": "function", "function": {"name": "get_stock_price", "arguments": "{\\"ticker\\": \\"AAPL\\", \\"exchange\\": \\"NASDAQ\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 149, "completion_tokens": 60, "total_tokens": 209, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_b40fb1c6fb"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) @@ -624,11 +705,13 @@ class GetStockPrice(BaseModel): assert print_obj(completion.choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[NoneType]( + ParsedChoice( finish_reason='tool_calls', index=0, logprobs=None, - message=ParsedChatCompletionMessage[NoneType]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, @@ -637,11 +720,11 @@ class GetStockPrice(BaseModel): tool_calls=[ ParsedFunctionToolCall( function=ParsedFunction( - arguments='{"city": "Edinburgh", "country": "UK", "units": "c"}', + arguments='{"city": "Edinburgh", "country": "GB", "units": "c"}', name='GetWeatherArgs', - parsed_arguments=GetWeatherArgs(city='Edinburgh', country='UK', units='c') + parsed_arguments=GetWeatherArgs(city='Edinburgh', country='GB', units='c') ), - id='call_ECSuZ8gcNPPwgt24me91jHsJ', + id='call_fdNz3vOBKYgOIpMdWotB9MjY', type='function' ), ParsedFunctionToolCall( @@ -650,7 +733,7 @@ class GetStockPrice(BaseModel): name='get_stock_price', parsed_arguments=GetStockPrice(exchange='NASDAQ', ticker='AAPL') ), - id='call_Z3fM2sNBBGILhMtimk5Y3RQk', + id='call_h1DWI1POMJLb0KwIyQHWXD4p', type='function' ) ] @@ -663,8 +746,8 @@ class GetStockPrice(BaseModel): @pytest.mark.respx(base_url=base_url) def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: - completion = _make_snapshot_request( - lambda c: c.beta.chat.completions.parse( + completion = make_snapshot_request( + lambda c: c.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[ { @@ -695,8 +778,9 @@ def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: ], ), content_snapshot=snapshot( - '{"id": "chatcmpl-9tXjfjETDIqeYvDjsuGACbwdY0xsr", "object": "chat.completion", "created": 1723024747, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_7ZZPctBXQWexQlIHSrIHMVUq", "type": "function", "function": {"name": "get_weather", "arguments": "{\\"city\\":\\"San Francisco\\",\\"state\\":\\"CA\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 48, "completion_tokens": 19, "total_tokens": 67}, "system_fingerprint": "fp_2a322c9ffc"}' + '{"id": "chatcmpl-ABfvzdvCI6RaIkiEFNjqGXCSYnlzf", "object": "chat.completion", "created": 1727346167, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_CUdUoJpsWWVdxXntucvnol1M", "type": "function", "function": {"name": "get_weather", "arguments": "{\\"city\\":\\"San Francisco\\",\\"state\\":\\"CA\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 48, "completion_tokens": 19, "total_tokens": 67, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_5050236cbd"}' ), + path="/chat/completions", mock_client=client, respx_mock=respx_mock, ) @@ -704,11 +788,13 @@ def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: assert print_obj(completion.choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[NoneType]( + ParsedChoice( finish_reason='tool_calls', index=0, logprobs=None, - message=ParsedChatCompletionMessage[NoneType]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, @@ -721,7 +807,7 @@ def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: name='get_weather', parsed_arguments={'city': 'San Francisco', 'state': 'CA'} ), - id='call_7ZZPctBXQWexQlIHSrIHMVUq', + id='call_CUdUoJpsWWVdxXntucvnol1M', type='function' ) ] @@ -736,7 +822,7 @@ def test_parse_non_strict_tools(client: OpenAI) -> None: with pytest.raises( ValueError, match="`get_weather` is not strict. Only `strict` function tools can be auto-parsed" ): - client.beta.chat.completions.parse( + client.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[], tools=[ @@ -751,54 +837,165 @@ def test_parse_non_strict_tools(client: OpenAI) -> None: ) -@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -def test_parse_method_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: - checking_client: OpenAI | AsyncOpenAI = client if sync else async_client +@pytest.mark.respx(base_url=base_url) +def test_parse_pydantic_raw_response(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: + class Location(BaseModel): + city: str + temperature: float + units: Literal["c", "f"] - assert_signatures_in_sync( - checking_client.chat.completions.create, - checking_client.beta.chat.completions.parse, - exclude_params={"response_format", "stream"}, + response = make_snapshot_request( + lambda c: c.chat.completions.with_raw_response.parse( + model="gpt-4o-2024-08-06", + messages=[ + { + "role": "user", + "content": "What's the weather like in SF?", + }, + ], + response_format=Location, + ), + content_snapshot=snapshot( + '{"id": "chatcmpl-ABrDYCa8W1w66eUxKDO8TQF1m6trT", "object": "chat.completion", "created": 1727389540, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":58,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 79, "completion_tokens": 14, "total_tokens": 93, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_5050236cbd"}' + ), + path="/chat/completions", + mock_client=client, + respx_mock=respx_mock, ) + assert response.http_request.headers.get("x-stainless-helper-method") == "chat.completions.parse" + completion = response.parse() + message = completion.choices[0].message + assert message.parsed is not None + assert isinstance(message.parsed.city, str) + assert print_obj(completion, monkeypatch) == snapshot( + """\ +ParsedChatCompletion( + choices=[ + ParsedChoice( + finish_reason='stop', + index=0, + logprobs=None, + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":58,"units":"f"}', + function_call=None, + parsed=Location(city='San Francisco', temperature=58.0, units='f'), + refusal=None, + role='assistant', + tool_calls=None + ) + ) + ], + created=1727389540, + id='chatcmpl-ABrDYCa8W1w66eUxKDO8TQF1m6trT', + model='gpt-4o-2024-08-06', + moderation=None, + object='chat.completion', + service_tier=None, + system_fingerprint='fp_5050236cbd', + usage=CompletionUsage( + completion_tokens=14, + completion_tokens_details=CompletionTokensDetails( + accepted_prediction_tokens=None, + audio_tokens=None, + reasoning_tokens=0, + rejected_prediction_tokens=None + ), + prompt_tokens=79, + prompt_tokens_details=None, + total_tokens=93 + ) +) +""" + ) -def _make_snapshot_request( - func: Callable[[OpenAI], _T], - *, - content_snapshot: Any, - respx_mock: MockRouter, - mock_client: OpenAI, -) -> _T: - live = os.environ.get("OPENAI_LIVE") == "1" - if live: - def _on_response(response: httpx.Response) -> None: - # update the content snapshot - assert json.dumps(json.loads(response.read())) == content_snapshot +@pytest.mark.respx(base_url=base_url) +@pytest.mark.asyncio +async def test_async_parse_pydantic_raw_response( + async_client: AsyncOpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch +) -> None: + class Location(BaseModel): + city: str + temperature: float + units: Literal["c", "f"] - respx_mock.stop() + response = await make_async_snapshot_request( + lambda c: c.chat.completions.with_raw_response.parse( + model="gpt-4o-2024-08-06", + messages=[ + { + "role": "user", + "content": "What's the weather like in SF?", + }, + ], + response_format=Location, + ), + content_snapshot=snapshot( + '{"id": "chatcmpl-ABrDQWOiw0PK5JOsxl1D9ooeQgznq", "object": "chat.completion", "created": 1727389532, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":65,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 79, "completion_tokens": 14, "total_tokens": 93, "completion_tokens_details": {"reasoning_tokens": 0}}, "system_fingerprint": "fp_5050236cbd"}' + ), + path="/chat/completions", + mock_client=async_client, + respx_mock=respx_mock, + ) + assert response.http_request.headers.get("x-stainless-helper-method") == "chat.completions.parse" - client = OpenAI( - http_client=httpx.Client( - event_hooks={ - "response": [_on_response], - } - ) - ) - else: - respx_mock.post("/chat/completions").mock( - return_value=httpx.Response( - 200, - content=content_snapshot._old_value, - headers={"content-type": "application/json"}, + completion = response.parse() + message = completion.choices[0].message + assert message.parsed is not None + assert isinstance(message.parsed.city, str) + assert print_obj(completion, monkeypatch) == snapshot( + """\ +ParsedChatCompletion( + choices=[ + ParsedChoice( + finish_reason='stop', + index=0, + logprobs=None, + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":65,"units":"f"}', + function_call=None, + parsed=Location(city='San Francisco', temperature=65.0, units='f'), + refusal=None, + role='assistant', + tool_calls=None ) ) + ], + created=1727389532, + id='chatcmpl-ABrDQWOiw0PK5JOsxl1D9ooeQgznq', + model='gpt-4o-2024-08-06', + moderation=None, + object='chat.completion', + service_tier=None, + system_fingerprint='fp_5050236cbd', + usage=CompletionUsage( + completion_tokens=14, + completion_tokens_details=CompletionTokensDetails( + accepted_prediction_tokens=None, + audio_tokens=None, + reasoning_tokens=0, + rejected_prediction_tokens=None + ), + prompt_tokens=79, + prompt_tokens_details=None, + total_tokens=93 + ) +) +""" + ) - client = mock_client - - result = func(client) - if live: - client.close() +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +def test_parse_method_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: + checking_client: OpenAI | AsyncOpenAI = client if sync else async_client - return result + assert_signatures_in_sync( + checking_client.chat.completions.create, + checking_client.chat.completions.parse, + exclude_params={"response_format", "stream"}, + ) diff --git a/tests/lib/chat/test_completions_streaming.py b/tests/lib/chat/test_completions_streaming.py index 5ad1f084d2..598a41ee2b 100644 --- a/tests/lib/chat/test_completions_streaming.py +++ b/tests/lib/chat/test_completions_streaming.py @@ -9,22 +9,29 @@ import pytest from respx import MockRouter from pydantic import BaseModel -from inline_snapshot import external, snapshot, outsource +from inline_snapshot import ( + external, + snapshot, + outsource, # pyright: ignore[reportUnknownVariableType] + get_snapshot_value, +) import openai from openai import OpenAI, AsyncOpenAI -from openai._utils import assert_signatures_in_sync +from openai._utils import consume_sync_iterator, assert_signatures_in_sync from openai._compat import model_copy +from openai.types.chat import ChatCompletionChunk from openai.lib.streaming.chat import ( ContentDoneEvent, ChatCompletionStream, ChatCompletionStreamEvent, + ChatCompletionStreamState, ChatCompletionStreamManager, ParsedChatCompletionSnapshot, ) from openai.lib._parsing._completions import ResponseFormatT -from ._utils import print_obj +from ..utils import print_obj from ...conftest import base_url _T = TypeVar("_T") @@ -33,13 +40,13 @@ # # you can update them with # -# `OPENAI_LIVE=1 pytest --inline-snapshot=fix` +# `OPENAI_LIVE=1 pytest --inline-snapshot=fix -p no:xdist -o addopts=""` @pytest.mark.respx(base_url=base_url) def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -48,7 +55,7 @@ def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pyte }, ], ), - content_snapshot=snapshot(external("038a5c69c34c*.bin")), + content_snapshot=snapshot(external("e2aad469b71d*.bin")), mock_client=client, respx_mock=respx_mock, ) @@ -56,19 +63,20 @@ def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pyte assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[NoneType]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[NoneType]( - content="I'm unable to provide real-time updates, including current weather information. For the latest -weather in San Francisco, I recommend checking a reliable weather website or app such as the Weather Channel, BBC -Weather, or a local San Francisco news station.", + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content="I'm unable to provide real-time weather updates. To get the current weather in San Francisco, I +recommend checking a reliable weather website or a weather app.", function_call=None, parsed=None, refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) ] @@ -76,10 +84,9 @@ def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pyte ) assert print_obj(listener.get_event_by_type("content.done"), monkeypatch) == snapshot( """\ -ContentDoneEvent[NoneType]( - content="I'm unable to provide real-time updates, including current weather information. For the latest weather in -San Francisco, I recommend checking a reliable weather website or app such as the Weather Channel, BBC Weather, or a -local San Francisco news station.", +ContentDoneEvent( + content="I'm unable to provide real-time weather updates. To get the current weather in San Francisco, I recommend +checking a reliable weather website or a weather app.", parsed=None, type='content.done' ) @@ -101,7 +108,7 @@ def on_event(stream: ChatCompletionStream[Location], event: ChatCompletionStream done_snapshots.append(model_copy(stream.current_completion_snapshot, deep=True)) listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -111,7 +118,7 @@ def on_event(stream: ChatCompletionStream[Location], event: ChatCompletionStream ], response_format=Location, ), - content_snapshot=snapshot(external("15ae68f793c7*.bin")), + content_snapshot=snapshot(external("7e5ea4d12e7c*.bin")), mock_client=client, respx_mock=respx_mock, on_event=on_event, @@ -133,37 +140,51 @@ def on_event(stream: ChatCompletionStream[Location], event: ChatCompletionStream assert print_obj(listener.stream.get_final_completion(), monkeypatch) == snapshot( """\ -ParsedChatCompletion[Location]( +ParsedChatCompletion( choices=[ - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[Location]( - content='{"city":"San Francisco","temperature":68,"units":"f"}', + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":61,"units":"f"}', function_call=None, - parsed=Location(city='San Francisco', temperature=68.0, units='f'), + parsed=Location(city='San Francisco', temperature=61.0, units='f'), refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) ], - created=1723024750, - id='chatcmpl-9tXji2y8kKxlOO3muVvfdJ7ECJVlD', + created=1727346169, + id='chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF', model='gpt-4o-2024-08-06', + moderation=None, object='chat.completion', service_tier=None, - system_fingerprint='fp_845eaabc1f', - usage=CompletionUsage(completion_tokens=14, completion_tokens_details=None, prompt_tokens=17, total_tokens=31) + system_fingerprint='fp_5050236cbd', + usage=CompletionUsage( + completion_tokens=14, + completion_tokens_details=CompletionTokensDetails( + accepted_prediction_tokens=None, + audio_tokens=None, + reasoning_tokens=0, + rejected_prediction_tokens=None + ), + prompt_tokens=79, + prompt_tokens_details=None, + total_tokens=93 + ) ) """ ) assert print_obj(listener.get_event_by_type("content.done"), monkeypatch) == snapshot( """\ -ContentDoneEvent[Location]( - content='{"city":"San Francisco","temperature":68,"units":"f"}', - parsed=Location(city='San Francisco', temperature=68.0, units='f'), +ContentDoneEvent( + content='{"city":"San Francisco","temperature":61,"units":"f"}', + parsed=Location(city='San Francisco', temperature=61.0, units='f'), type='content.done' ) """ @@ -180,7 +201,7 @@ class Location(BaseModel): units: Literal["c", "f"] listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -191,7 +212,7 @@ class Location(BaseModel): n=3, response_format=Location, ), - content_snapshot=snapshot(external("a0c4f0be184e*.bin")), + content_snapshot=snapshot(external("a491adda08c3*.bin")), mock_client=client, respx_mock=respx_mock, ) @@ -211,36 +232,22 @@ class Location(BaseModel): "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", "content.delta", @@ -249,10 +256,6 @@ class Location(BaseModel): "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", "content.delta", @@ -261,139 +264,106 @@ class Location(BaseModel): "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", "content.delta", "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", - "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", - "refusal.delta", + "content.delta", "chunk", "content.done", "chunk", "content.done", "chunk", - "refusal.done", + "content.done", "chunk", ] ) assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[Location]( - content='{"city":"San Francisco","temperature":63,"units":"f"}', + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":65,"units":"f"}', function_call=None, - parsed=Location(city='San Francisco', temperature=63.0, units='f'), + parsed=Location(city='San Francisco', temperature=65.0, units='f'), refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ), - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=1, logprobs=None, - message=ParsedChatCompletionMessage[Location]( - content='{"city":"San Francisco","temperature":58.6,"units":"f"}', + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":61,"units":"f"}', function_call=None, - parsed=Location(city='San Francisco', temperature=58.6, units='f'), + parsed=Location(city='San Francisco', temperature=61.0, units='f'), refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ), - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=2, logprobs=None, - message=ParsedChatCompletionMessage[Location]( - content=None, + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='{"city":"San Francisco","temperature":59,"units":"f"}', function_call=None, - parsed=None, - refusal="I'm sorry, but I can't accurately provide the current weather for San Francisco as my data is up to -October 2023. You can try checking a reliable weather website or app for real-time updates.", + parsed=Location(city='San Francisco', temperature=59.0, units='f'), + refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) ] @@ -410,7 +380,7 @@ class Location(BaseModel): with pytest.raises(openai.LengthFinishReasonError): _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -421,7 +391,7 @@ class Location(BaseModel): max_tokens=1, response_format=Location, ), - content_snapshot=snapshot(external("69363a555f8e*.bin")), + content_snapshot=snapshot(external("4cc50a6135d2*.bin")), mock_client=client, respx_mock=respx_mock, ) @@ -435,7 +405,7 @@ class Location(BaseModel): units: Literal["c", "f"] listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -445,29 +415,31 @@ class Location(BaseModel): ], response_format=Location, ), - content_snapshot=snapshot(external("ca015b8b1eba*.bin")), + content_snapshot=snapshot(external("173417d55340*.bin")), mock_client=client, respx_mock=respx_mock, ) assert print_obj(listener.get_event_by_type("refusal.done"), monkeypatch) == snapshot("""\ -RefusalDoneEvent(refusal="I'm sorry, but I can't assist with that request.", type='refusal.done') +RefusalDoneEvent(refusal="I'm sorry, I can't assist with that request.", type='refusal.done') """) assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[Location]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, - refusal="I'm sorry, but I can't assist with that request.", + refusal="I'm sorry, I can't assist with that request.", role='assistant', - tool_calls=[] + tool_calls=None ) ) ] @@ -478,7 +450,7 @@ class Location(BaseModel): @pytest.mark.respx(base_url=base_url) def test_content_logprobs_events(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -488,7 +460,7 @@ def test_content_logprobs_events(client: OpenAI, respx_mock: MockRouter, monkeyp ], logprobs=True, ), - content_snapshot=snapshot(external("be1089999ca5*.bin")), + content_snapshot=snapshot(external("83b060bae42e*.bin")), mock_client=client, respx_mock=respx_mock, ) @@ -497,25 +469,25 @@ def test_content_logprobs_events(client: OpenAI, respx_mock: MockRouter, monkeyp [ LogprobsContentDeltaEvent( content=[ - ChatCompletionTokenLogprob(bytes=[70, 111, 111], logprob=-0.0067602484, token='Foo', top_logprobs=[]) + ChatCompletionTokenLogprob(bytes=[70, 111, 111], logprob=-0.0025094282, token='Foo', top_logprobs=[]) ], snapshot=[ - ChatCompletionTokenLogprob(bytes=[70, 111, 111], logprob=-0.0067602484, token='Foo', top_logprobs=[]) + ChatCompletionTokenLogprob(bytes=[70, 111, 111], logprob=-0.0025094282, token='Foo', top_logprobs=[]) ], type='logprobs.content.delta' ), LogprobsContentDeltaEvent( - content=[ChatCompletionTokenLogprob(bytes=[46], logprob=-2.4962392, token='.', top_logprobs=[])], + content=[ChatCompletionTokenLogprob(bytes=[33], logprob=-0.26638845, token='!', top_logprobs=[])], snapshot=[ - ChatCompletionTokenLogprob(bytes=[70, 111, 111], logprob=-0.0067602484, token='Foo', top_logprobs=[]), - ChatCompletionTokenLogprob(bytes=[46], logprob=-2.4962392, token='.', top_logprobs=[]) + ChatCompletionTokenLogprob(bytes=[70, 111, 111], logprob=-0.0025094282, token='Foo', top_logprobs=[]), + ChatCompletionTokenLogprob(bytes=[33], logprob=-0.26638845, token='!', top_logprobs=[]) ], type='logprobs.content.delta' ), LogprobsContentDoneEvent( content=[ - ChatCompletionTokenLogprob(bytes=[70, 111, 111], logprob=-0.0067602484, token='Foo', top_logprobs=[]), - ChatCompletionTokenLogprob(bytes=[46], logprob=-2.4962392, token='.', top_logprobs=[]) + ChatCompletionTokenLogprob(bytes=[70, 111, 111], logprob=-0.0025094282, token='Foo', top_logprobs=[]), + ChatCompletionTokenLogprob(bytes=[33], logprob=-0.26638845, token='!', top_logprobs=[]) ], type='logprobs.content.done' ) @@ -524,23 +496,25 @@ def test_content_logprobs_events(client: OpenAI, respx_mock: MockRouter, monkeyp assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot("""\ [ - ParsedChoice[NoneType]( + ParsedChoice( finish_reason='stop', index=0, logprobs=ChoiceLogprobs( content=[ - ChatCompletionTokenLogprob(bytes=[70, 111, 111], logprob=-0.0067602484, token='Foo', top_logprobs=[]), - ChatCompletionTokenLogprob(bytes=[46], logprob=-2.4962392, token='.', top_logprobs=[]) + ChatCompletionTokenLogprob(bytes=[70, 111, 111], logprob=-0.0025094282, token='Foo', top_logprobs=[]), + ChatCompletionTokenLogprob(bytes=[33], logprob=-0.26638845, token='!', top_logprobs=[]) ], refusal=None ), - message=ParsedChatCompletionMessage[NoneType]( - content='Foo.', + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='Foo!', function_call=None, parsed=None, refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) ] @@ -555,7 +529,7 @@ class Location(BaseModel): units: Literal["c", "f"] listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -566,7 +540,7 @@ class Location(BaseModel): logprobs=True, response_format=Location, ), - content_snapshot=snapshot(external("0a00cd46c610*.bin")), + content_snapshot=snapshot(external("569c877e6942*.bin")), mock_client=client, respx_mock=respx_mock, ) @@ -583,67 +557,76 @@ class Location(BaseModel): 'logprobs.refusal.delta', 'logprobs.refusal.delta', 'logprobs.refusal.delta', + 'logprobs.refusal.delta', 'logprobs.refusal.done' ] """) assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot("""\ [ - ParsedChoice[Location]( + ParsedChoice( finish_reason='stop', index=0, logprobs=ChoiceLogprobs( content=None, refusal=[ - ChatCompletionTokenLogprob(bytes=[73, 39, 109], logprob=-0.0016157961, token="I'm", top_logprobs=[]), + ChatCompletionTokenLogprob(bytes=[73, 39, 109], logprob=-0.0012038043, token="I'm", top_logprobs=[]), + ChatCompletionTokenLogprob( + bytes=[32, 118, 101, 114, 121], + logprob=-0.8438816, + token=' very', + top_logprobs=[] + ), ChatCompletionTokenLogprob( bytes=[32, 115, 111, 114, 114, 121], - logprob=-0.78663874, + logprob=-3.4121115e-06, token=' sorry', top_logprobs=[] ), - ChatCompletionTokenLogprob(bytes=[44], logprob=-7.79144e-05, token=',', top_logprobs=[]), - ChatCompletionTokenLogprob(bytes=[32, 73], logprob=-0.5234622, token=' I', top_logprobs=[]), + ChatCompletionTokenLogprob(bytes=[44], logprob=-3.3809047e-05, token=',', top_logprobs=[]), + ChatCompletionTokenLogprob( + bytes=[32, 98, 117, 116], + logprob=-0.038048144, + token=' but', + top_logprobs=[] + ), + ChatCompletionTokenLogprob(bytes=[32, 73], logprob=-0.0016109125, token=' I', top_logprobs=[]), ChatCompletionTokenLogprob( - bytes=[32, 99, 97, 110, 110, 111, 116], - logprob=-0.52499557, - token=' cannot', + bytes=[32, 99, 97, 110, 39, 116], + logprob=-0.0073532974, + token=" can't", top_logprobs=[] ), ChatCompletionTokenLogprob( bytes=[32, 97, 115, 115, 105, 115, 116], - logprob=-0.015198289, + logprob=-0.0020837625, token=' assist', top_logprobs=[] ), ChatCompletionTokenLogprob( bytes=[32, 119, 105, 116, 104], - logprob=-0.00071648485, + logprob=-0.00318354, token=' with', top_logprobs=[] ), ChatCompletionTokenLogprob( bytes=[32, 116, 104, 97, 116], - logprob=-0.008114983, + logprob=-0.0017186158, token=' that', top_logprobs=[] ), - ChatCompletionTokenLogprob( - bytes=[32, 114, 101, 113, 117, 101, 115, 116], - logprob=-0.0013802331, - token=' request', - top_logprobs=[] - ), - ChatCompletionTokenLogprob(bytes=[46], logprob=-3.4121115e-06, token='.', top_logprobs=[]) + ChatCompletionTokenLogprob(bytes=[46], logprob=-0.57687104, token='.', top_logprobs=[]) ] ), - message=ParsedChatCompletionMessage[Location]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, - refusal="I'm sorry, I cannot assist with that request.", + refusal="I'm very sorry, but I can't assist with that.", role='assistant', - tool_calls=[] + tool_calls=None ) ) ] @@ -658,7 +641,7 @@ class GetWeatherArgs(BaseModel): units: Literal["c", "f"] = "c" listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -670,7 +653,7 @@ class GetWeatherArgs(BaseModel): openai.pydantic_function_tool(GetWeatherArgs), ], ), - content_snapshot=snapshot(external("24aaf30663f9*.bin")), + content_snapshot=snapshot(external("c6aa7e397b71*.bin")), mock_client=client, respx_mock=respx_mock, ) @@ -678,11 +661,13 @@ class GetWeatherArgs(BaseModel): assert print_obj(listener.stream.current_completion_snapshot.choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[object]( + ParsedChoice( finish_reason='tool_calls', index=0, logprobs=None, - message=ParsedChatCompletionMessage[object]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, @@ -691,11 +676,11 @@ class GetWeatherArgs(BaseModel): tool_calls=[ ParsedFunctionToolCall( function=ParsedFunction( - arguments='{"city":"Edinburgh","country":"GB","units":"c"}', + arguments='{"city":"Edinburgh","country":"UK","units":"c"}', name='GetWeatherArgs', - parsed_arguments=GetWeatherArgs(city='Edinburgh', country='GB', units='c') + parsed_arguments=GetWeatherArgs(city='Edinburgh', country='UK', units='c') ), - id='call_7PhhveOvvpPK53s1fV8TWhoV', + id='call_c91SqDXlYFuETYv8mUHzz6pp', index=0, type='function' ) @@ -709,11 +694,13 @@ class GetWeatherArgs(BaseModel): assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[NoneType]( + ParsedChoice( finish_reason='tool_calls', index=0, logprobs=None, - message=ParsedChatCompletionMessage[NoneType]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, @@ -722,11 +709,11 @@ class GetWeatherArgs(BaseModel): tool_calls=[ ParsedFunctionToolCall( function=ParsedFunction( - arguments='{"city":"Edinburgh","country":"GB","units":"c"}', + arguments='{"city":"Edinburgh","country":"UK","units":"c"}', name='GetWeatherArgs', - parsed_arguments=GetWeatherArgs(city='Edinburgh', country='GB', units='c') + parsed_arguments=GetWeatherArgs(city='Edinburgh', country='UK', units='c') ), - id='call_7PhhveOvvpPK53s1fV8TWhoV', + id='call_c91SqDXlYFuETYv8mUHzz6pp', index=0, type='function' ) @@ -752,7 +739,7 @@ class GetStockPrice(BaseModel): exchange: str listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -771,7 +758,7 @@ class GetStockPrice(BaseModel): ), ], ), - content_snapshot=snapshot(external("453df473e962*.bin")), + content_snapshot=snapshot(external("f82268f2fefd*.bin")), mock_client=client, respx_mock=respx_mock, ) @@ -779,11 +766,13 @@ class GetStockPrice(BaseModel): assert print_obj(listener.stream.current_completion_snapshot.choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[object]( + ParsedChoice( finish_reason='tool_calls', index=0, logprobs=None, - message=ParsedChatCompletionMessage[object]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, @@ -792,11 +781,11 @@ class GetStockPrice(BaseModel): tool_calls=[ ParsedFunctionToolCall( function=ParsedFunction( - arguments='{"city": "Edinburgh", "country": "UK", "units": "c"}', + arguments='{"city": "Edinburgh", "country": "GB", "units": "c"}', name='GetWeatherArgs', - parsed_arguments=GetWeatherArgs(city='Edinburgh', country='UK', units='c') + parsed_arguments=GetWeatherArgs(city='Edinburgh', country='GB', units='c') ), - id='call_lQnnsesjFMWMQ5IeWPHzR4th', + id='call_JMW1whyEaYG438VE1OIflxA2', index=0, type='function' ), @@ -806,7 +795,7 @@ class GetStockPrice(BaseModel): name='get_stock_price', parsed_arguments=GetStockPrice(exchange='NASDAQ', ticker='AAPL') ), - id='call_2xjOUgaCdiwAcl9ZBL9LyMUU', + id='call_DNYTawLBoN8fj3KN6qU9N1Ou', index=1, type='function' ) @@ -822,11 +811,11 @@ class GetStockPrice(BaseModel): [ ParsedFunctionToolCall( function=ParsedFunction( - arguments='{"city": "Edinburgh", "country": "UK", "units": "c"}', + arguments='{"city": "Edinburgh", "country": "GB", "units": "c"}', name='GetWeatherArgs', - parsed_arguments=GetWeatherArgs(city='Edinburgh', country='UK', units='c') + parsed_arguments=GetWeatherArgs(city='Edinburgh', country='GB', units='c') ), - id='call_lQnnsesjFMWMQ5IeWPHzR4th', + id='call_JMW1whyEaYG438VE1OIflxA2', index=0, type='function' ), @@ -836,7 +825,7 @@ class GetStockPrice(BaseModel): name='get_stock_price', parsed_arguments=GetStockPrice(exchange='NASDAQ', ticker='AAPL') ), - id='call_2xjOUgaCdiwAcl9ZBL9LyMUU', + id='call_DNYTawLBoN8fj3KN6qU9N1Ou', index=1, type='function' ) @@ -848,7 +837,7 @@ class GetStockPrice(BaseModel): @pytest.mark.respx(base_url=base_url) def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -878,7 +867,7 @@ def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: } ], ), - content_snapshot=snapshot(external("83d3d003e6fd*.bin")), + content_snapshot=snapshot(external("a247c49c5fcd*.bin")), mock_client=client, respx_mock=respx_mock, ) @@ -886,11 +875,13 @@ def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: assert print_obj(listener.stream.current_completion_snapshot.choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[object]( + ParsedChoice( finish_reason='tool_calls', index=0, logprobs=None, - message=ParsedChatCompletionMessage[object]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, @@ -903,7 +894,7 @@ def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: name='get_weather', parsed_arguments={'city': 'San Francisco', 'state': 'CA'} ), - id='call_pVHYsU0gmSfX5TqxOyVbB2ma', + id='call_CTf1nWJLqSeRgDqaCG27xZ74', index=0, type='function' ) @@ -918,7 +909,7 @@ def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: @pytest.mark.respx(base_url=base_url) def test_non_pydantic_response_format(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[ { @@ -928,7 +919,7 @@ def test_non_pydantic_response_format(client: OpenAI, respx_mock: MockRouter, mo ], response_format={"type": "json_object"}, ), - content_snapshot=snapshot(external("0898f3d1651e*.bin")), + content_snapshot=snapshot(external("d61558011839*.bin")), mock_client=client, respx_mock=respx_mock, ) @@ -936,20 +927,24 @@ def test_non_pydantic_response_format(client: OpenAI, respx_mock: MockRouter, mo assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[NoneType]( + ParsedChoice( finish_reason='stop', index=0, logprobs=None, - message=ParsedChatCompletionMessage[NoneType]( - content='\\n {\\n "location": "San Francisco, CA",\\n "forecast_date": "2023-11-02",\\n "weather": {\\n -"temperature": {\\n "current": "N/A",\\n "high": "N/A",\\n "low": "N/A"\\n },\\n "condition": -"N/A",\\n "humidity": "N/A",\\n "wind_speed": "N/A"\\n },\\n "note": "Please check a reliable weather -service for the most current information."\\n }', + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content='\\n {\\n "location": "San Francisco, CA",\\n "weather": {\\n "temperature": "18°C",\\n +"condition": "Partly Cloudy",\\n "humidity": "72%",\\n "windSpeed": "15 km/h",\\n "windDirection": "NW"\\n +},\\n "forecast": [\\n {\\n "day": "Monday",\\n "high": "20°C",\\n "low": "14°C",\\n +"condition": "Sunny"\\n },\\n {\\n "day": "Tuesday",\\n "high": "19°C",\\n "low": "15°C",\\n +"condition": "Mostly Cloudy"\\n },\\n {\\n "day": "Wednesday",\\n "high": "18°C",\\n "low": +"14°C",\\n "condition": "Cloudy"\\n }\\n ]\\n }\\n', function_call=None, parsed=None, refusal=None, role='assistant', - tool_calls=[] + tool_calls=None ) ) ] @@ -962,7 +957,7 @@ def test_allows_non_strict_tools_but_no_parsing( client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch ) -> None: listener = _make_stream_snapshot_request( - lambda c: c.beta.chat.completions.stream( + lambda c: c.chat.completions.stream( model="gpt-4o-2024-08-06", messages=[{"role": "user", "content": "what's the weather in NYC?"}], tools=[ @@ -975,7 +970,7 @@ def test_allows_non_strict_tools_but_no_parsing( } ], ), - content_snapshot=snapshot(external("dae1b261f197*.bin")), + content_snapshot=snapshot(external("2018feb66ae1*.bin")), mock_client=client, respx_mock=respx_mock, ) @@ -993,11 +988,13 @@ def test_allows_non_strict_tools_but_no_parsing( assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot( """\ [ - ParsedChoice[NoneType]( + ParsedChoice( finish_reason='tool_calls', index=0, logprobs=None, - message=ParsedChatCompletionMessage[NoneType]( + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, content=None, function_call=None, parsed=None, @@ -1010,7 +1007,7 @@ def test_allows_non_strict_tools_but_no_parsing( name='get_weather', parsed_arguments=None ), - id='call_5uxEBMFySqqQGu02I5QHA8k6', + id='call_4XzlGBLtUe9dy3GVNV4jhq7h', index=0, type='function' ) @@ -1022,13 +1019,63 @@ def test_allows_non_strict_tools_but_no_parsing( ) +@pytest.mark.respx(base_url=base_url) +def test_chat_completion_state_helper(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None: + state = ChatCompletionStreamState() + + def streamer(client: OpenAI) -> Iterator[ChatCompletionChunk]: + stream = client.chat.completions.create( + model="gpt-4o-2024-08-06", + messages=[ + { + "role": "user", + "content": "What's the weather like in SF?", + }, + ], + stream=True, + ) + for chunk in stream: + state.handle_chunk(chunk) + yield chunk + + _make_raw_stream_snapshot_request( + streamer, + content_snapshot=snapshot(external("e2aad469b71d*.bin")), + mock_client=client, + respx_mock=respx_mock, + ) + + assert print_obj(state.get_final_completion().choices, monkeypatch) == snapshot( + """\ +[ + ParsedChoice( + finish_reason='stop', + index=0, + logprobs=None, + message=ParsedChatCompletionMessage( + annotations=None, + audio=None, + content="I'm unable to provide real-time weather updates. To get the current weather in San Francisco, I +recommend checking a reliable weather website or a weather app.", + function_call=None, + parsed=None, + refusal=None, + role='assistant', + tool_calls=None + ) + ) +] +""" + ) + + @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) def test_stream_method_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: checking_client: OpenAI | AsyncOpenAI = client if sync else async_client assert_signatures_in_sync( checking_client.chat.completions.create, - checking_client.beta.chat.completions.stream, + checking_client.chat.completions.stream, exclude_params={"response_format", "stream"}, ) @@ -1082,7 +1129,7 @@ def _on_response(response: httpx.Response) -> None: respx_mock.post("/chat/completions").mock( return_value=httpx.Response( 200, - content=content_snapshot._old_value._load_value(), + content=get_snapshot_value(content_snapshot), headers={"content-type": "text/event-stream"}, ) ) @@ -1100,3 +1147,44 @@ def _on_response(response: httpx.Response) -> None: client.close() return listener + + +def _make_raw_stream_snapshot_request( + func: Callable[[OpenAI], Iterator[ChatCompletionChunk]], + *, + content_snapshot: Any, + respx_mock: MockRouter, + mock_client: OpenAI, +) -> None: + live = os.environ.get("OPENAI_LIVE") == "1" + if live: + + def _on_response(response: httpx.Response) -> None: + # update the content snapshot + assert outsource(response.read()) == content_snapshot + + respx_mock.stop() + + client = OpenAI( + http_client=httpx.Client( + event_hooks={ + "response": [_on_response], + } + ) + ) + else: + respx_mock.post("/chat/completions").mock( + return_value=httpx.Response( + 200, + content=get_snapshot_value(content_snapshot), + headers={"content-type": "text/event-stream"}, + ) + ) + + client = mock_client + + stream = func(client) + consume_sync_iterator(stream) + + if live: + client.close() diff --git a/tests/lib/responses/__init__.py b/tests/lib/responses/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/lib/responses/test_responses.py b/tests/lib/responses/test_responses.py new file mode 100644 index 0000000000..8e5f16df95 --- /dev/null +++ b/tests/lib/responses/test_responses.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from typing_extensions import TypeVar + +import pytest +from respx import MockRouter +from inline_snapshot import snapshot + +from openai import OpenAI, AsyncOpenAI +from openai._utils import assert_signatures_in_sync + +from ...conftest import base_url +from ..snapshots import make_snapshot_request + +_T = TypeVar("_T") + +# all the snapshots in this file are auto-generated from the live API +# +# you can update them with +# +# `OPENAI_LIVE=1 pytest --inline-snapshot=fix -p no:xdist -o addopts=""` + + +@pytest.mark.respx(base_url=base_url) +def test_output_text(client: OpenAI, respx_mock: MockRouter) -> None: + response = make_snapshot_request( + lambda c: c.responses.create( + model="gpt-4o-mini", + input="What's the weather like in SF?", + ), + content_snapshot=snapshot( + '{"id": "resp_689a0b2545288193953c892439b42e2800b2e36c65a1fd4b", "object": "response", "created_at": 1754925861, "status": "completed", "background": false, "error": null, "incomplete_details": null, "instructions": null, "max_output_tokens": null, "max_tool_calls": null, "model": "gpt-4o-mini-2024-07-18", "output": [{"id": "msg_689a0b2637b08193ac478e568f49e3f900b2e36c65a1fd4b", "type": "message", "status": "completed", "content": [{"type": "output_text", "annotations": [], "logprobs": [], "text": "I can\'t provide real-time updates, but you can easily check the current weather in San Francisco using a weather website or app. Typically, San Francisco has cool, foggy summers and mild winters, so it\'s good to be prepared for variable weather!"}], "role": "assistant"}], "parallel_tool_calls": true, "previous_response_id": null, "prompt_cache_key": null, "reasoning": {"effort": null, "summary": null}, "safety_identifier": null, "service_tier": "default", "store": true, "temperature": 1.0, "text": {"format": {"type": "text"}, "verbosity": "medium"}, "tool_choice": "auto", "tools": [], "top_logprobs": 0, "top_p": 1.0, "truncation": "disabled", "usage": {"input_tokens": 14, "input_tokens_details": {"cached_tokens": 0}, "output_tokens": 50, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 64}, "user": null, "metadata": {}}' + ), + path="/responses", + mock_client=client, + respx_mock=respx_mock, + ) + + assert response.output_text == snapshot( + "I can't provide real-time updates, but you can easily check the current weather in San Francisco using a weather website or app. Typically, San Francisco has cool, foggy summers and mild winters, so it's good to be prepared for variable weather!" + ) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +def test_stream_method_definition_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: + checking_client: OpenAI | AsyncOpenAI = client if sync else async_client + + assert_signatures_in_sync( + checking_client.responses.create, + checking_client.responses.stream, + exclude_params={"stream", "tools"}, + ) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +def test_parse_method_definition_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: + checking_client: OpenAI | AsyncOpenAI = client if sync else async_client + + assert_signatures_in_sync( + checking_client.responses.create, + checking_client.responses.parse, + exclude_params={"tools"}, + ) diff --git a/tests/lib/snapshots.py b/tests/lib/snapshots.py new file mode 100644 index 0000000000..91222acda1 --- /dev/null +++ b/tests/lib/snapshots.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +import os +import json +from typing import Any, Callable, Awaitable +from typing_extensions import TypeVar + +import httpx +from respx import MockRouter +from inline_snapshot import get_snapshot_value + +from openai import OpenAI, AsyncOpenAI + +_T = TypeVar("_T") + + +def make_snapshot_request( + func: Callable[[OpenAI], _T], + *, + content_snapshot: Any, + respx_mock: MockRouter, + mock_client: OpenAI, + path: str, +) -> _T: + live = os.environ.get("OPENAI_LIVE") == "1" + if live: + + def _on_response(response: httpx.Response) -> None: + # update the content snapshot + assert json.dumps(json.loads(response.read())) == content_snapshot + + respx_mock.stop() + + client = OpenAI( + http_client=httpx.Client( + event_hooks={ + "response": [_on_response], + } + ) + ) + else: + respx_mock.post(path).mock( + return_value=httpx.Response( + 200, + content=get_snapshot_value(content_snapshot), + headers={"content-type": "application/json"}, + ) + ) + + client = mock_client + + result = func(client) + + if live: + client.close() + + return result + + +async def make_async_snapshot_request( + func: Callable[[AsyncOpenAI], Awaitable[_T]], + *, + content_snapshot: Any, + respx_mock: MockRouter, + mock_client: AsyncOpenAI, + path: str, +) -> _T: + live = os.environ.get("OPENAI_LIVE") == "1" + if live: + + async def _on_response(response: httpx.Response) -> None: + # update the content snapshot + assert json.dumps(json.loads(await response.aread())) == content_snapshot + + respx_mock.stop() + + client = AsyncOpenAI( + http_client=httpx.AsyncClient( + event_hooks={ + "response": [_on_response], + } + ) + ) + else: + respx_mock.post(path).mock( + return_value=httpx.Response( + 200, + content=get_snapshot_value(content_snapshot), + headers={"content-type": "application/json"}, + ) + ) + + client = mock_client + + result = await func(client) + + if live: + await client.close() + + return result diff --git a/tests/lib/test_assistants.py b/tests/lib/test_assistants.py index 67d021ec35..08ea9300c3 100644 --- a/tests/lib/test_assistants.py +++ b/tests/lib/test_assistants.py @@ -11,7 +11,7 @@ def test_create_and_run_poll_method_definition_in_sync(sync: bool, client: OpenA checking_client: OpenAI | AsyncOpenAI = client if sync else async_client assert_signatures_in_sync( - checking_client.beta.threads.create_and_run, + checking_client.beta.threads.create_and_run, # pyright: ignore[reportDeprecated] checking_client.beta.threads.create_and_run_poll, exclude_params={"stream"}, ) @@ -22,7 +22,7 @@ def test_create_and_run_stream_method_definition_in_sync(sync: bool, client: Ope checking_client: OpenAI | AsyncOpenAI = client if sync else async_client assert_signatures_in_sync( - checking_client.beta.threads.create_and_run, + checking_client.beta.threads.create_and_run, # pyright: ignore[reportDeprecated] checking_client.beta.threads.create_and_run_stream, exclude_params={"stream"}, ) @@ -33,8 +33,8 @@ def test_run_stream_method_definition_in_sync(sync: bool, client: OpenAI, async_ checking_client: OpenAI | AsyncOpenAI = client if sync else async_client assert_signatures_in_sync( - checking_client.beta.threads.runs.create, - checking_client.beta.threads.runs.stream, + checking_client.beta.threads.runs.create, # pyright: ignore[reportDeprecated] + checking_client.beta.threads.runs.stream, # pyright: ignore[reportDeprecated] exclude_params={"stream"}, ) @@ -44,7 +44,7 @@ def test_create_and_poll_method_definition_in_sync(sync: bool, client: OpenAI, a checking_client: OpenAI | AsyncOpenAI = client if sync else async_client assert_signatures_in_sync( - checking_client.beta.threads.runs.create, - checking_client.beta.threads.runs.create_and_poll, + checking_client.beta.threads.runs.create, # pyright: ignore[reportDeprecated] + checking_client.beta.threads.runs.create_and_poll, # pyright: ignore[reportDeprecated] exclude_params={"stream"}, ) diff --git a/tests/lib/test_audio.py b/tests/lib/test_audio.py new file mode 100644 index 0000000000..93ed3a33b2 --- /dev/null +++ b/tests/lib/test_audio.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +import sys +import inspect +import typing_extensions +from typing import get_args + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import evaluate_forwardref +from openai._utils import assert_signatures_in_sync +from openai._compat import is_literal_type +from openai._utils._typing import is_union_type +from openai.types.audio_response_format import AudioResponseFormat + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +def test_translation_create_overloads_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: + checking_client: OpenAI | AsyncOpenAI = client if sync else async_client + + fn = checking_client.audio.translations.create + overload_response_formats: set[str] = set() + + for i, overload in enumerate(typing_extensions.get_overloads(fn)): + assert_signatures_in_sync( + fn, + overload, + exclude_params={"response_format", "stream"}, + description=f" for overload {i}", + ) + + sig = inspect.signature(overload) + typ = evaluate_forwardref( + sig.parameters["response_format"].annotation, + globalns=sys.modules[fn.__module__].__dict__, + ) + if is_union_type(typ): + for arg in get_args(typ): + if not is_literal_type(arg): + continue + + overload_response_formats.update(get_args(arg)) + elif is_literal_type(typ): + overload_response_formats.update(get_args(typ)) + + # 'diarized_json' applies only to transcriptions, not translations. + src_response_formats: set[str] = set(get_args(AudioResponseFormat)) - {"diarized_json"} + diff = src_response_formats.difference(overload_response_formats) + assert len(diff) == 0, f"some response format options don't have overloads" + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +def test_transcription_create_overloads_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: + checking_client: OpenAI | AsyncOpenAI = client if sync else async_client + + fn = checking_client.audio.transcriptions.create + overload_response_formats: set[str] = set() + + for i, overload in enumerate(typing_extensions.get_overloads(fn)): + sig = inspect.signature(overload) + typ = evaluate_forwardref( + sig.parameters["response_format"].annotation, + globalns=sys.modules[fn.__module__].__dict__, + ) + + exclude_params = {"response_format", "stream"} + # known_speaker_names and known_speaker_references are only supported by diarized_json + if not (is_literal_type(typ) and set(get_args(typ)) == {"diarized_json"}): + exclude_params.update({"known_speaker_names", "known_speaker_references"}) + + # diarized_json does not support these parameters + if is_literal_type(typ) and set(get_args(typ)) == {"diarized_json"}: + exclude_params.update({"include", "prompt", "timestamp_granularities"}) + + assert_signatures_in_sync( + fn, + overload, + exclude_params=exclude_params, + description=f" for overload {i}", + ) + if is_union_type(typ): + for arg in get_args(typ): + if not is_literal_type(arg): + continue + + overload_response_formats.update(get_args(arg)) + elif is_literal_type(typ): + overload_response_formats.update(get_args(typ)) + + src_response_formats: set[str] = set(get_args(AudioResponseFormat)) + diff = src_response_formats.difference(overload_response_formats) + assert len(diff) == 0, f"some response format options don't have overloads" diff --git a/tests/lib/test_azure.py b/tests/lib/test_azure.py index a9d3478350..3e1d783e2c 100644 --- a/tests/lib/test_azure.py +++ b/tests/lib/test_azure.py @@ -1,3 +1,6 @@ +from __future__ import annotations + +import logging from typing import Union, cast from typing_extensions import Literal, Protocol @@ -5,6 +8,10 @@ import pytest from respx import MockRouter +from openai import OpenAIError +from tests.utils import update_env +from openai._types import Omit +from openai._utils import SensitiveHeadersFilter, is_dict from openai._models import FinalRequestOptions from openai.lib.azure import AzureOpenAI, AsyncAzureOpenAI @@ -72,6 +79,154 @@ def test_client_copying_override_options(client: Client) -> None: assert copied._custom_query == {"api-version": "2022-05-01"} +def test_enforce_credentials_false_sync() -> None: + with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): + AzureOpenAI( + api_version="2024-02-01", + api_key=None, + azure_ad_token=None, + azure_ad_token_provider=None, + azure_endpoint="https://example-resource.azure.openai.com", + _enforce_credentials=False, + ) + + +@pytest.mark.respx() +def test_enforce_credentials_false_sync_uses_default_api_key_header(respx_mock: MockRouter) -> None: + respx_mock.post( + "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" + ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + + with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): + client = AzureOpenAI( + api_version="2024-02-01", + api_key=None, + azure_ad_token=None, + azure_ad_token_provider=None, + azure_endpoint="https://example-resource.azure.openai.com", + default_headers={"api-key": "manual-api-key"}, + _enforce_credentials=False, + ) + client.chat.completions.create(messages=[], model="gpt-4") + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.headers.get("api-key") == "manual-api-key" + assert calls[0].request.headers.get("Authorization") is None + + +@pytest.mark.respx() +def test_enforce_credentials_false_sync_uses_request_authorization_header(respx_mock: MockRouter) -> None: + respx_mock.post( + "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" + ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + + with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): + client = AzureOpenAI( + api_version="2024-02-01", + api_key=None, + azure_ad_token=None, + azure_ad_token_provider=None, + azure_endpoint="https://example-resource.azure.openai.com", + _enforce_credentials=False, + ) + client.chat.completions.create( + messages=[], + model="gpt-4", + extra_headers={"authorization": "Bearer manual-token"}, + ) + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.headers.get("Authorization") == "Bearer manual-token" + assert calls[0].request.headers.get("api-key") is None + + +def test_enforce_credentials_true_sync() -> None: + with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): + with pytest.raises(OpenAIError, match="Missing credentials"): + AzureOpenAI( + api_version="2024-02-01", + api_key=None, + azure_ad_token=None, + azure_ad_token_provider=None, + azure_endpoint="https://example-resource.azure.openai.com", + ) + + +def test_enforce_credentials_false_async() -> None: + with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key=None, + azure_ad_token=None, + azure_ad_token_provider=None, + azure_endpoint="https://example-resource.azure.openai.com", + _enforce_credentials=False, + ) + + +@pytest.mark.asyncio +@pytest.mark.respx() +async def test_enforce_credentials_false_async_uses_default_api_key_header(respx_mock: MockRouter) -> None: + respx_mock.post( + "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" + ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + + with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): + client = AsyncAzureOpenAI( + api_version="2024-02-01", + api_key=None, + azure_ad_token=None, + azure_ad_token_provider=None, + azure_endpoint="https://example-resource.azure.openai.com", + default_headers={"api-key": "manual-api-key"}, + _enforce_credentials=False, + ) + await client.chat.completions.create(messages=[], model="gpt-4") + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.headers.get("api-key") == "manual-api-key" + assert calls[0].request.headers.get("Authorization") is None + + +@pytest.mark.asyncio +@pytest.mark.respx() +async def test_enforce_credentials_false_async_uses_request_authorization_header(respx_mock: MockRouter) -> None: + respx_mock.post( + "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01" + ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + + with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): + client = AsyncAzureOpenAI( + api_version="2024-02-01", + api_key=None, + azure_ad_token=None, + azure_ad_token_provider=None, + azure_endpoint="https://example-resource.azure.openai.com", + _enforce_credentials=False, + ) + await client.chat.completions.create( + messages=[], + model="gpt-4", + extra_headers={"authorization": "Bearer manual-token"}, + ) + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.headers.get("Authorization") == "Bearer manual-token" + assert calls[0].request.headers.get("api-key") is None + + +def test_enforce_credentials_true_async() -> None: + with update_env(AZURE_OPENAI_API_KEY=Omit(), AZURE_OPENAI_AD_TOKEN=Omit()): + with pytest.raises(OpenAIError, match="Missing credentials"): + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key=None, + azure_ad_token=None, + azure_ad_token_provider=None, + azure_endpoint="https://example-resource.azure.openai.com", + ) + + @pytest.mark.respx() def test_client_token_provider_refresh_sync(respx_mock: MockRouter) -> None: respx_mock.post( @@ -148,3 +303,653 @@ def token_provider() -> str: assert calls[0].request.headers.get("Authorization") == "Bearer first" assert calls[1].request.headers.get("Authorization") == "Bearer second" + + +class TestAzureLogging: + @pytest.fixture(autouse=True) + def logger_with_filter(self) -> logging.Logger: + logger = logging.getLogger("openai") + logger.setLevel(logging.DEBUG) + logger.addFilter(SensitiveHeadersFilter()) + return logger + + @pytest.mark.respx() + def test_azure_api_key_redacted(self, respx_mock: MockRouter, caplog: pytest.LogCaptureFixture) -> None: + respx_mock.post( + "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-06-01" + ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + + client = AzureOpenAI( + api_version="2024-06-01", + api_key="example_api_key", + azure_endpoint="https://example-resource.azure.openai.com", + ) + + with caplog.at_level(logging.DEBUG): + client.chat.completions.create(messages=[], model="gpt-4") + + for record in caplog.records: + if is_dict(record.args) and record.args.get("headers") and is_dict(record.args["headers"]): + assert record.args["headers"]["api-key"] == "" + + @pytest.mark.respx() + def test_azure_bearer_token_redacted(self, respx_mock: MockRouter, caplog: pytest.LogCaptureFixture) -> None: + respx_mock.post( + "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-06-01" + ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + + client = AzureOpenAI( + api_version="2024-06-01", + azure_ad_token="example_token", + azure_endpoint="https://example-resource.azure.openai.com", + ) + + with caplog.at_level(logging.DEBUG): + client.chat.completions.create(messages=[], model="gpt-4") + + for record in caplog.records: + if is_dict(record.args) and record.args.get("headers") and is_dict(record.args["headers"]): + assert record.args["headers"]["Authorization"] == "" + + @pytest.mark.asyncio + @pytest.mark.respx() + async def test_azure_api_key_redacted_async(self, respx_mock: MockRouter, caplog: pytest.LogCaptureFixture) -> None: + respx_mock.post( + "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-06-01" + ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + + client = AsyncAzureOpenAI( + api_version="2024-06-01", + api_key="example_api_key", + azure_endpoint="https://example-resource.azure.openai.com", + ) + + with caplog.at_level(logging.DEBUG): + await client.chat.completions.create(messages=[], model="gpt-4") + + for record in caplog.records: + if is_dict(record.args) and record.args.get("headers") and is_dict(record.args["headers"]): + assert record.args["headers"]["api-key"] == "" + + @pytest.mark.asyncio + @pytest.mark.respx() + async def test_azure_bearer_token_redacted_async( + self, respx_mock: MockRouter, caplog: pytest.LogCaptureFixture + ) -> None: + respx_mock.post( + "https://example-resource.azure.openai.com/openai/deployments/gpt-4/chat/completions?api-version=2024-06-01" + ).mock(return_value=httpx.Response(200, json={"model": "gpt-4"})) + + client = AsyncAzureOpenAI( + api_version="2024-06-01", + azure_ad_token="example_token", + azure_endpoint="https://example-resource.azure.openai.com", + ) + + with caplog.at_level(logging.DEBUG): + await client.chat.completions.create(messages=[], model="gpt-4") + + for record in caplog.records: + if is_dict(record.args) and record.args.get("headers") and is_dict(record.args["headers"]): + assert record.args["headers"]["Authorization"] == "" + + +@pytest.mark.parametrize( + "client,base_url,api,json_data,expected", + [ + # Deployment-based endpoints + # AzureOpenAI: No deployment specified + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/chat/completions", + {"model": "deployment-body"}, + "https://example-resource.azure.openai.com/openai/deployments/deployment-body/chat/completions?api-version=2024-02-01", + ), + # AzureOpenAI: Deployment specified + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployment-client", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployment-client/", + "/chat/completions", + {"model": "deployment-body"}, + "https://example-resource.azure.openai.com/openai/deployments/deployment-client/chat/completions?api-version=2024-02-01", + ), + # AzureOpenAI: "deployments" in the DNS name + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://deployments.example-resource.azure.openai.com", + ), + "https://deployments.example-resource.azure.openai.com/openai/", + "/chat/completions", + {"model": "deployment-body"}, + "https://deployments.example-resource.azure.openai.com/openai/deployments/deployment-body/chat/completions?api-version=2024-02-01", + ), + # AzureOpenAI: Deployment called deployments + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployments", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployments/", + "/chat/completions", + {"model": "deployment-body"}, + "https://example-resource.azure.openai.com/openai/deployments/deployments/chat/completions?api-version=2024-02-01", + ), + # AzureOpenAI: base_url and azure_deployment specified; ignored b/c not supported + ( + AzureOpenAI( # type: ignore + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/", + azure_deployment="deployment-client", + ), + "https://example.azure-api.net/PTU/", + "/chat/completions", + {"model": "deployment-body"}, + "https://example.azure-api.net/PTU/deployments/deployment-body/chat/completions?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: No deployment specified + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/chat/completions", + {"model": "deployment-body"}, + "https://example-resource.azure.openai.com/openai/deployments/deployment-body/chat/completions?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: Deployment specified + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployment-client", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployment-client/", + "/chat/completions", + {"model": "deployment-body"}, + "https://example-resource.azure.openai.com/openai/deployments/deployment-client/chat/completions?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: "deployments" in the DNS name + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://deployments.example-resource.azure.openai.com", + ), + "https://deployments.example-resource.azure.openai.com/openai/", + "/chat/completions", + {"model": "deployment-body"}, + "https://deployments.example-resource.azure.openai.com/openai/deployments/deployment-body/chat/completions?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: Deployment called deployments + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployments", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployments/", + "/chat/completions", + {"model": "deployment-body"}, + "https://example-resource.azure.openai.com/openai/deployments/deployments/chat/completions?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: base_url and azure_deployment specified; azure_deployment ignored b/c not supported + ( + AsyncAzureOpenAI( # type: ignore + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/", + azure_deployment="deployment-client", + ), + "https://example.azure-api.net/PTU/", + "/chat/completions", + {"model": "deployment-body"}, + "https://example.azure-api.net/PTU/deployments/deployment-body/chat/completions?api-version=2024-02-01", + ), + ], +) +def test_prepare_url_deployment_endpoint( + client: Client, base_url: str, api: str, json_data: dict[str, str], expected: str +) -> None: + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url=api, + json_data=json_data, + ) + ) + assert req.url == expected + assert client.base_url == base_url + + +@pytest.mark.parametrize( + "client,base_url,api,json_data,expected", + [ + # Non-deployment endpoints + # AzureOpenAI: No deployment specified + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/models", + {}, + "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01", + ), + # AzureOpenAI: No deployment specified + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/assistants", + {"model": "deployment-body"}, + "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01", + ), + # AzureOpenAI: Deployment specified + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployment-client", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployment-client/", + "/models", + {}, + "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01", + ), + # AzureOpenAI: Deployment specified + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployment-client", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployment-client/", + "/assistants", + {"model": "deployment-body"}, + "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01", + ), + # AzureOpenAI: "deployments" in the DNS name + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://deployments.example-resource.azure.openai.com", + ), + "https://deployments.example-resource.azure.openai.com/openai/", + "/models", + {}, + "https://deployments.example-resource.azure.openai.com/openai/models?api-version=2024-02-01", + ), + # AzureOpenAI: Deployment called "deployments" + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployments", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployments/", + "/models", + {}, + "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01", + ), + # AzureOpenAI: base_url and azure_deployment specified; azure_deployment ignored b/c not supported + ( + AzureOpenAI( # type: ignore + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/", + azure_deployment="deployment-client", + ), + "https://example.azure-api.net/PTU/", + "/models", + {}, + "https://example.azure-api.net/PTU/models?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: No deployment specified + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/models", + {}, + "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: No deployment specified + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/assistants", + {"model": "deployment-body"}, + "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: Deployment specified + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployment-client", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployment-client/", + "/models", + {}, + "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: Deployment specified + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployment-client", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployment-client/", + "/assistants", + {"model": "deployment-body"}, + "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: "deployments" in the DNS name + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://deployments.example-resource.azure.openai.com", + ), + "https://deployments.example-resource.azure.openai.com/openai/", + "/models", + {}, + "https://deployments.example-resource.azure.openai.com/openai/models?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: Deployment called "deployments" + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployments", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployments/", + "/models", + {}, + "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01", + ), + # AsyncAzureOpenAI: base_url and azure_deployment specified; azure_deployment ignored b/c not supported + ( + AsyncAzureOpenAI( # type: ignore + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/", + azure_deployment="deployment-client", + ), + "https://example.azure-api.net/PTU/", + "/models", + {}, + "https://example.azure-api.net/PTU/models?api-version=2024-02-01", + ), + ], +) +def test_prepare_url_nondeployment_endpoint( + client: Client, base_url: str, api: str, json_data: dict[str, str], expected: str +) -> None: + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url=api, + json_data=json_data, + ) + ) + assert req.url == expected + assert client.base_url == base_url + + +@pytest.mark.parametrize( + "client,base_url,json_data,expected", + [ + # Realtime endpoint + # AzureOpenAI: No deployment specified + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + {"model": "deployment-body"}, + "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-body", + ), + # AzureOpenAI: Deployment specified + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployment-client", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployment-client/", + {"model": "deployment-body"}, + "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-client", + ), + # AzureOpenAI: "deployments" in the DNS name + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://deployments.azure.openai.com", + ), + "https://deployments.azure.openai.com/openai/", + {"model": "deployment-body"}, + "wss://deployments.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-body", + ), + # AzureOpenAI: Deployment called "deployments" + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployments", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployments/", + {"model": "deployment-body"}, + "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployments", + ), + # AzureOpenAI: base_url and azure_deployment specified; azure_deployment ignored b/c not supported + ( + AzureOpenAI( # type: ignore + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/", + azure_deployment="my-deployment", + ), + "https://example.azure-api.net/PTU/", + {"model": "deployment-body"}, + "wss://example.azure-api.net/PTU/realtime?api-version=2024-02-01&deployment=deployment-body", + ), + # AzureOpenAI: websocket_base_url specified + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + websocket_base_url="wss://example-resource.azure.openai.com/base", + ), + "https://example-resource.azure.openai.com/openai/", + {"model": "deployment-body"}, + "wss://example-resource.azure.openai.com/base/realtime?api-version=2024-02-01&deployment=deployment-body", + ), + ], +) +def test_prepare_url_realtime(client: AzureOpenAI, base_url: str, json_data: dict[str, str], expected: str) -> None: + url, _ = client._configure_realtime(json_data["model"], {}) + assert str(url) == expected + assert client.base_url == base_url + + +@pytest.mark.parametrize( + "client,base_url,json_data,expected", + [ + # AsyncAzureOpenAI: No deployment specified + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + {"model": "deployment-body"}, + "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-body", + ), + # AsyncAzureOpenAI: Deployment specified + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployment-client", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployment-client/", + {"model": "deployment-body"}, + "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-client", + ), + # AsyncAzureOpenAI: "deployments" in the DNS name + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://deployments.azure.openai.com", + ), + "https://deployments.azure.openai.com/openai/", + {"model": "deployment-body"}, + "wss://deployments.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-body", + ), + # AsyncAzureOpenAI: Deployment called "deployments" + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="deployments", + ), + "https://example-resource.azure.openai.com/openai/deployments/deployments/", + {"model": "deployment-body"}, + "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployments", + ), + # AsyncAzureOpenAI: base_url and azure_deployment specified; azure_deployment ignored b/c not supported + ( + AsyncAzureOpenAI( # type: ignore + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/", + azure_deployment="deployment-client", + ), + "https://example.azure-api.net/PTU/", + {"model": "deployment-body"}, + "wss://example.azure-api.net/PTU/realtime?api-version=2024-02-01&deployment=deployment-body", + ), + # AsyncAzureOpenAI: websocket_base_url specified + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + websocket_base_url="wss://example-resource.azure.openai.com/base", + ), + "https://example-resource.azure.openai.com/openai/", + {"model": "deployment-body"}, + "wss://example-resource.azure.openai.com/base/realtime?api-version=2024-02-01&deployment=deployment-body", + ), + ], +) +async def test_prepare_url_realtime_async( + client: AsyncAzureOpenAI, base_url: str, json_data: dict[str, str], expected: str +) -> None: + url, _ = await client._configure_realtime(json_data["model"], {}) + assert str(url) == expected + assert client.base_url == base_url + + +def test_client_sets_base_url(client: Client) -> None: + client = AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="my-deployment", + ) + assert client.base_url == "https://example-resource.azure.openai.com/openai/deployments/my-deployment/" + + # (not recommended) user sets base_url to target different deployment + client.base_url = "https://example-resource.azure.openai.com/openai/deployments/different-deployment/" + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url="/chat/completions", + json_data={"model": "placeholder"}, + ) + ) + assert ( + req.url + == "https://example-resource.azure.openai.com/openai/deployments/different-deployment/chat/completions?api-version=2024-02-01" + ) + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url="/models", + json_data={}, + ) + ) + assert req.url == "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01" + + # (not recommended) user sets base_url to remove deployment + client.base_url = "https://example-resource.azure.openai.com/openai/" + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url="/chat/completions", + json_data={"model": "deployment"}, + ) + ) + assert ( + req.url + == "https://example-resource.azure.openai.com/openai/deployments/deployment/chat/completions?api-version=2024-02-01" + ) + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url="/models", + json_data={}, + ) + ) + assert req.url == "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01" diff --git a/tests/lib/test_bedrock.py b/tests/lib/test_bedrock.py new file mode 100644 index 0000000000..dab9abd1cf --- /dev/null +++ b/tests/lib/test_bedrock.py @@ -0,0 +1,488 @@ +from __future__ import annotations + +import json +from typing import Any, Union, Protocol, cast + +import httpx +import pytest +from httpx import URL +from respx import MockRouter + +from openai import OpenAIError, NotFoundError +from tests.utils import update_env +from openai._types import Omit +from openai.lib.bedrock import BedrockOpenAI, AsyncBedrockOpenAI + +Client = Union[BedrockOpenAI, AsyncBedrockOpenAI] + +RESPONSE_BODY: dict[str, Any] = { + "id": "resp_123", + "object": "response", + "created_at": 0, + "status": "completed", + "background": False, + "error": None, + "incomplete_details": None, + "instructions": None, + "max_output_tokens": None, + "max_tool_calls": None, + "model": "gpt-4o", + "output": [], + "parallel_tool_calls": True, + "previous_response_id": None, + "prompt_cache_key": None, + "reasoning": {"effort": None, "summary": None}, + "safety_identifier": None, + "service_tier": "default", + "store": True, + "temperature": 1.0, + "text": {"format": {"type": "text"}, "verbosity": "medium"}, + "tool_choice": "auto", + "tools": [], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 0, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens": 0, + "output_tokens_details": {"reasoning_tokens": 0}, + "total_tokens": 0, + }, + "user": None, + "metadata": {}, +} +COMPACTED_RESPONSE_BODY: dict[str, Any] = { + "id": "resp_123", + "created_at": 0, + "object": "response.compaction", + "output": [], + "usage": RESPONSE_BODY["usage"], +} +INPUT_ITEMS_BODY: dict[str, Any] = { + "object": "list", + "data": [], + "first_id": "item_123", + "last_id": "item_123", + "has_more": False, +} +INPUT_TOKENS_BODY: dict[str, Any] = { + "object": "response.input_tokens", + "input_tokens": 1, +} + + +class MockRequestCall(Protocol): + request: httpx.Request + + +def make_sync_client(**kwargs: Any) -> BedrockOpenAI: + return BedrockOpenAI(http_client=httpx.Client(trust_env=False), **kwargs) + + +def make_async_client(**kwargs: Any) -> AsyncBedrockOpenAI: + return AsyncBedrockOpenAI(http_client=httpx.AsyncClient(trust_env=False), **kwargs) + + +def response_created_sse() -> str: + event: dict[str, Any] = {"type": "response.created", "sequence_number": 0, "response": RESPONSE_BODY} + return f"event: response.created\ndata: {json.dumps(event)}\n\n" + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_region_derived_base_url(client_cls: type[Client]) -> None: + with update_env(AWS_BEDROCK_BASE_URL=Omit(), AWS_REGION="us-east-1", AWS_DEFAULT_REGION=Omit()): + client = ( + make_sync_client(api_key="token") if client_cls is BedrockOpenAI else make_async_client(api_key="token") + ) + + assert client.base_url == URL("https://bedrock-mantle.us-east-1.api.aws/openai/v1/") + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_bedrock_config_precedence(client_cls: type[Client]) -> None: + with update_env( + AWS_BEDROCK_BASE_URL="https://env.example.com/openai/v1", + AWS_BEARER_TOKEN_BEDROCK="env token", + AWS_REGION="us-east-1", + AWS_DEFAULT_REGION="us-west-2", + ): + client = ( + make_sync_client( + base_url="https://explicit.example.com/openai/v1/responses", + api_key="explicit token", + ) + if client_cls is BedrockOpenAI + else make_async_client( + base_url="https://explicit.example.com/openai/v1/responses", + api_key="explicit token", + ) + ) + + assert client.base_url == URL("https://explicit.example.com/openai/v1/") + assert client.api_key == "explicit token" + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_bedrock_region_precedence(client_cls: type[Client]) -> None: + with update_env(AWS_BEDROCK_BASE_URL=Omit(), AWS_REGION="us-east-1", AWS_DEFAULT_REGION="us-west-2"): + explicit_region_client = ( + make_sync_client(aws_region="eu-west-1", api_key="token") + if client_cls is BedrockOpenAI + else make_async_client(aws_region="eu-west-1", api_key="token") + ) + aws_region_client = ( + make_sync_client(api_key="token") if client_cls is BedrockOpenAI else make_async_client(api_key="token") + ) + + with update_env(AWS_BEDROCK_BASE_URL=Omit(), AWS_REGION=Omit(), AWS_DEFAULT_REGION="us-west-2"): + default_region_client = ( + make_sync_client(api_key="token") if client_cls is BedrockOpenAI else make_async_client(api_key="token") + ) + + assert explicit_region_client.base_url == URL("https://bedrock-mantle.eu-west-1.api.aws/openai/v1/") + assert aws_region_client.base_url == URL("https://bedrock-mantle.us-east-1.api.aws/openai/v1/") + assert default_region_client.base_url == URL("https://bedrock-mantle.us-west-2.api.aws/openai/v1/") + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_normalizes_responses_url(client_cls: type[Client]) -> None: + client = ( + make_sync_client(base_url="https://example.com/openai/v1/responses", api_key="token") + if client_cls is BedrockOpenAI + else make_async_client(base_url="https://example.com/openai/v1/responses", api_key="token") + ) + + assert client.base_url == URL("https://example.com/openai/v1/") + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_requires_endpoint_configuration(client_cls: type[Client]) -> None: + with update_env(AWS_BEDROCK_BASE_URL=Omit(), AWS_REGION=Omit(), AWS_DEFAULT_REGION=Omit()): + with pytest.raises(OpenAIError, match="Must provide one of the `base_url` or `aws_region`"): + client_cls(api_key="token") + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_does_not_use_openai_api_key(client_cls: type[Client]) -> None: + with update_env( + OPENAI_API_KEY="openai token", + AWS_BEARER_TOKEN_BEDROCK=Omit(), + AWS_BEDROCK_BASE_URL="https://example.com/openai/v1", + ): + with pytest.raises(OpenAIError, match="AWS_BEARER_TOKEN_BEDROCK"): + client_cls() + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_rejects_static_token_and_provider(client_cls: type[Client]) -> None: + with pytest.raises(OpenAIError, match="mutually exclusive"): + client_cls( + base_url="https://example.com/openai/v1", + api_key="token", + bedrock_token_provider=lambda: "provider token", + ) + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_requires_refreshable_tokens_to_use_provider_option(client_cls: type[Client]) -> None: + with pytest.raises(OpenAIError, match="bedrock_token_provider"): + client_cls( + base_url="https://example.com/openai/v1", + api_key=lambda: "provider token", # type: ignore[arg-type] + ) + + +@pytest.mark.respx() +def test_token_provider_refresh_sync(respx_mock: MockRouter) -> None: + respx_mock.post("https://example.com/openai/v1/responses").mock( + side_effect=[ + httpx.Response(500, json={"error": "server error"}), + httpx.Response(200, json=RESPONSE_BODY), + ] + ) + tokens = iter(["first", "second"]) + client = BedrockOpenAI( + base_url="https://example.com/openai/v1", + bedrock_token_provider=lambda: next(tokens), + http_client=httpx.Client(trust_env=False), + max_retries=1, + ) + + client.responses.create(model="gpt-4o", input="hello") + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.headers["Authorization"] == "Bearer first" + assert calls[1].request.headers["Authorization"] == "Bearer second" + + +@pytest.mark.asyncio +@pytest.mark.respx() +async def test_token_provider_refresh_async(respx_mock: MockRouter) -> None: + respx_mock.post("https://example.com/openai/v1/responses").mock( + side_effect=[ + httpx.Response(500, json={"error": "server error"}), + httpx.Response(200, json=RESPONSE_BODY), + ] + ) + tokens = iter(["first", "second"]) + client = AsyncBedrockOpenAI( + base_url="https://example.com/openai/v1", + bedrock_token_provider=lambda: next(tokens), + http_client=httpx.AsyncClient(trust_env=False), + max_retries=1, + ) + + await client.responses.create(model="gpt-4o", input="hello") + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.headers["Authorization"] == "Bearer first" + assert calls[1].request.headers["Authorization"] == "Bearer second" + + +def test_preserves_token_provider_across_with_options() -> None: + client = BedrockOpenAI( + base_url="https://example.com/openai/v1", + bedrock_token_provider=lambda: "provider token", + http_client=httpx.Client(trust_env=False), + ) + + copied_client = client.with_options(timeout=1) + + assert copied_client._refresh_api_key() == "provider token" + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_with_options_api_key_replaces_token_provider(client_cls: type[Client]) -> None: + client = ( + make_sync_client( + base_url="https://example.com/openai/v1", + bedrock_token_provider=lambda: "provider token", + ) + if client_cls is BedrockOpenAI + else make_async_client( + base_url="https://example.com/openai/v1", + bedrock_token_provider=lambda: "provider token", + ) + ) + + copied_client = client.with_options(api_key="static token") + + assert copied_client.api_key == "static token" + assert copied_client._bedrock_token_provider is None + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_with_options_aws_region_recomputes_region_derived_base_url(client_cls: type[Client]) -> None: + with update_env(AWS_BEDROCK_BASE_URL=Omit(), AWS_REGION=Omit(), AWS_DEFAULT_REGION=Omit()): + client = ( + make_sync_client(aws_region="us-east-1", api_key="token") + if client_cls is BedrockOpenAI + else make_async_client(aws_region="us-east-1", api_key="token") + ) + + copied_client = client.with_options(aws_region="eu-west-1") + + assert copied_client.aws_region == "eu-west-1" + assert copied_client.base_url == URL("https://bedrock-mantle.eu-west-1.api.aws/openai/v1/") + + +@pytest.mark.parametrize("client_cls", [BedrockOpenAI, AsyncBedrockOpenAI]) +def test_with_options_aws_region_keeps_explicit_base_url(client_cls: type[Client]) -> None: + client = ( + make_sync_client(base_url="https://example.com/openai/v1", aws_region="us-east-1", api_key="token") + if client_cls is BedrockOpenAI + else make_async_client(base_url="https://example.com/openai/v1", aws_region="us-east-1", api_key="token") + ) + + copied_client = client.with_options(aws_region="eu-west-1") + + assert copied_client.aws_region == "eu-west-1" + assert copied_client.base_url == URL("https://example.com/openai/v1/") + + +@pytest.mark.parametrize( + "copy_kwargs", + [ + {"admin_api_key": "admin token"}, + {"workload_identity": cast(Any, {})}, + ], +) +def test_rejects_non_bedrock_copy_auth(copy_kwargs: dict[str, Any]) -> None: + client = make_sync_client(base_url="https://example.com/openai/v1", api_key="token") + + with pytest.raises(OpenAIError, match="only supports Bedrock bearer token authentication"): + client.with_options(**copy_kwargs) + + +@pytest.mark.respx() +def test_passes_non_responses_resources_through(respx_mock: MockRouter) -> None: + respx_mock.post("https://example.com/openai/v1/chat/completions").mock( + return_value=httpx.Response( + 404, + json={"error": {"message": "AWS does not support chat completions here"}}, + headers={"x-request-id": "req_chat"}, + ) + ) + client = make_sync_client(base_url="https://example.com/openai/v1", api_key="token") + + with pytest.raises(NotFoundError, match="AWS does not support chat completions here") as exc: + client.chat.completions.create(model="gpt-4o", messages=[]) + + assert exc.value.request_id == "req_chat" + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.url == URL("https://example.com/openai/v1/chat/completions") + + +@pytest.mark.asyncio +@pytest.mark.respx() +async def test_passes_non_responses_resources_through_async(respx_mock: MockRouter) -> None: + respx_mock.post("https://example.com/openai/v1/chat/completions").mock( + return_value=httpx.Response( + 404, + json={"error": {"message": "AWS does not support chat completions here"}}, + headers={"x-request-id": "req_chat"}, + ) + ) + client = make_async_client(base_url="https://example.com/openai/v1", api_key="token") + + with pytest.raises(NotFoundError, match="AWS does not support chat completions here") as exc: + await client.chat.completions.create(model="gpt-4o", messages=[]) + + assert exc.value.request_id == "req_chat" + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.url == URL("https://example.com/openai/v1/chat/completions") + + +@pytest.mark.respx() +def test_passes_responses_features_through(respx_mock: MockRouter) -> None: + respx_mock.post("https://example.com/openai/v1/responses").mock( + return_value=httpx.Response(200, json=RESPONSE_BODY) + ) + client = make_sync_client(base_url="https://example.com/openai/v1", api_key="token") + + response = client.responses.create( + model="gpt-4o", + input="hello", + tools=[{"type": "web_search_preview"}], # type: ignore[list-item] + ) + + assert response.id == "resp_123" + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert json.loads(calls[0].request.content)["tools"] == [{"type": "web_search_preview"}] + + +@pytest.mark.respx() +def test_passes_admin_security_routes_through(respx_mock: MockRouter) -> None: + respx_mock.get("https://example.com/openai/v1/organization/invites").mock( + return_value=httpx.Response( + 404, + json={"error": {"message": "AWS does not support organization invites here"}}, + headers={"x-request-id": "req_admin"}, + ) + ) + client = make_sync_client(base_url="https://example.com/openai/v1", api_key="token") + + with pytest.raises(NotFoundError, match="AWS does not support organization invites here"): + list(client.admin.organization.invites.list()) + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.headers["Authorization"] == "Bearer token" + + +@pytest.mark.respx() +def test_refreshes_token_provider_for_admin_security_routes(respx_mock: MockRouter) -> None: + respx_mock.get("https://example.com/openai/v1/organization/invites").mock( + side_effect=[ + httpx.Response(500, json={"error": "server error"}), + httpx.Response( + 404, + json={"error": {"message": "AWS does not support organization invites here"}}, + headers={"x-request-id": "req_admin"}, + ), + ] + ) + tokens = iter(["first", "second"]) + client = BedrockOpenAI( + base_url="https://example.com/openai/v1", + bedrock_token_provider=lambda: next(tokens), + http_client=httpx.Client(trust_env=False), + max_retries=1, + ) + + with pytest.raises(NotFoundError, match="AWS does not support organization invites here"): + list(client.admin.organization.invites.list()) + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert calls[0].request.headers["Authorization"] == "Bearer first" + assert calls[1].request.headers["Authorization"] == "Bearer second" + + +@pytest.mark.respx() +def test_allows_responses_http_methods(respx_mock: MockRouter) -> None: + respx_mock.post("https://example.com/openai/v1/responses").mock( + return_value=httpx.Response(200, json=RESPONSE_BODY) + ) + respx_mock.get("https://example.com/openai/v1/responses/resp_123?starting_after=1&stream=true").mock( + return_value=httpx.Response(200, text=response_created_sse(), headers={"Content-Type": "text/event-stream"}) + ) + respx_mock.get("https://example.com/openai/v1/responses/resp_123?stream=true").mock( + return_value=httpx.Response(200, text=response_created_sse(), headers={"Content-Type": "text/event-stream"}) + ) + respx_mock.get("https://example.com/openai/v1/responses/resp_123").mock( + return_value=httpx.Response(200, json=RESPONSE_BODY) + ) + respx_mock.post("https://example.com/openai/v1/responses/resp_123/cancel").mock( + return_value=httpx.Response(200, json=RESPONSE_BODY) + ) + respx_mock.post("https://example.com/openai/v1/responses/compact").mock( + return_value=httpx.Response(200, json=COMPACTED_RESPONSE_BODY) + ) + respx_mock.get("https://example.com/openai/v1/responses/resp_123/input_items").mock( + return_value=httpx.Response(200, json=INPUT_ITEMS_BODY) + ) + respx_mock.post("https://example.com/openai/v1/responses/input_tokens").mock( + return_value=httpx.Response(200, json=INPUT_TOKENS_BODY) + ) + client = make_sync_client(base_url="https://example.com/openai/v1", api_key="token") + + assert client.responses.create(model="gpt-4o", input="hello", background=True).id == "resp_123" + assert client.responses.retrieve("resp_123").id == "resp_123" + assert [event.type for event in client.responses.retrieve("resp_123", starting_after=1, stream=True)] == [ + "response.created" + ] + assert [event.type for event in client.responses.retrieve("resp_123", stream=True)] == ["response.created"] + assert client.responses.cancel("resp_123").id == "resp_123" + assert client.responses.compact(model="gpt-4o").object == "response.compaction" + assert list(client.responses.input_items.list("resp_123")) == [] + assert client.responses.input_tokens.count(model="gpt-4o", input="hello").input_tokens == 1 + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert {call.request.headers["Authorization"] for call in calls} == {"Bearer token"} + + +@pytest.mark.respx() +def test_allows_sse_and_response_wrappers(respx_mock: MockRouter) -> None: + respx_mock.post("https://example.com/openai/v1/responses").mock( + side_effect=[ + httpx.Response(200, text=response_created_sse(), headers={"Content-Type": "text/event-stream"}), + httpx.Response(200, json=RESPONSE_BODY), + httpx.Response(200, json=RESPONSE_BODY), + ] + ) + client = make_sync_client(base_url="https://example.com/openai/v1", api_key="token") + + events = list(client.responses.create(model="gpt-4o", input="hello", stream=True)) + assert [event.type for event in events] == ["response.created"] + + raw_response = client.responses.with_raw_response.create(model="gpt-4o", input="hello") + assert raw_response.parse().id == "resp_123" + + with client.responses.with_streaming_response.create(model="gpt-4o", input="hello") as response: + assert response.parse().id == "resp_123" + + +def test_does_not_guard_responses_connect() -> None: + client = make_sync_client(base_url="https://example.com/openai/v1", api_key="token") + + assert client.responses.connect() is not None diff --git a/tests/lib/test_old_api.py b/tests/lib/test_old_api.py index 261b8acb94..bdb2a5398d 100644 --- a/tests/lib/test_old_api.py +++ b/tests/lib/test_old_api.py @@ -6,7 +6,7 @@ def test_basic_attribute_access_works() -> None: for attr in dir(openai): - dir(getattr(openai, attr)) + getattr(openai, attr) def test_helpful_error_is_raised() -> None: diff --git a/tests/lib/test_pydantic.py b/tests/lib/test_pydantic.py index 99b9e96d21..754a15151c 100644 --- a/tests/lib/test_pydantic.py +++ b/tests/lib/test_pydantic.py @@ -6,13 +6,14 @@ from inline_snapshot import snapshot import openai -from openai._compat import PYDANTIC_V2 +from openai._compat import PYDANTIC_V1 +from openai.lib._pydantic import to_strict_json_schema from .schema_types.query import Query def test_most_types() -> None: - if PYDANTIC_V2: + if not PYDANTIC_V1: assert openai.pydantic_function_tool(Query)["function"] == snapshot( { "name": "Query", @@ -180,7 +181,7 @@ class ColorDetection(BaseModel): def test_enums() -> None: - if PYDANTIC_V2: + if not PYDANTIC_V1: assert openai.pydantic_function_tool(ColorDetection)["function"] == snapshot( { "name": "ColorDetection", @@ -235,3 +236,176 @@ def test_enums() -> None: }, } ) + + +class Star(BaseModel): + name: str = Field(description="The name of the star.") + + +class Galaxy(BaseModel): + name: str = Field(description="The name of the galaxy.") + largest_star: Star = Field(description="The largest star in the galaxy.") + + +class Universe(BaseModel): + name: str = Field(description="The name of the universe.") + galaxy: Galaxy = Field(description="A galaxy in the universe.") + + +def test_nested_inline_ref_expansion() -> None: + if not PYDANTIC_V1: + assert to_strict_json_schema(Universe) == snapshot( + { + "title": "Universe", + "type": "object", + "$defs": { + "Star": { + "title": "Star", + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the star.", + } + }, + "required": ["name"], + "additionalProperties": False, + }, + "Galaxy": { + "title": "Galaxy", + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the galaxy.", + }, + "largest_star": { + "title": "Star", + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the star.", + } + }, + "required": ["name"], + "description": "The largest star in the galaxy.", + "additionalProperties": False, + }, + }, + "required": ["name", "largest_star"], + "additionalProperties": False, + }, + }, + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the universe.", + }, + "galaxy": { + "title": "Galaxy", + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the galaxy.", + }, + "largest_star": { + "title": "Star", + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the star.", + } + }, + "required": ["name"], + "description": "The largest star in the galaxy.", + "additionalProperties": False, + }, + }, + "required": ["name", "largest_star"], + "description": "A galaxy in the universe.", + "additionalProperties": False, + }, + }, + "required": ["name", "galaxy"], + "additionalProperties": False, + } + ) + else: + assert to_strict_json_schema(Universe) == snapshot( + { + "title": "Universe", + "type": "object", + "definitions": { + "Star": { + "title": "Star", + "type": "object", + "properties": { + "name": {"title": "Name", "description": "The name of the star.", "type": "string"} + }, + "required": ["name"], + "additionalProperties": False, + }, + "Galaxy": { + "title": "Galaxy", + "type": "object", + "properties": { + "name": {"title": "Name", "description": "The name of the galaxy.", "type": "string"}, + "largest_star": { + "title": "Largest Star", + "description": "The largest star in the galaxy.", + "type": "object", + "properties": { + "name": {"title": "Name", "description": "The name of the star.", "type": "string"} + }, + "required": ["name"], + "additionalProperties": False, + }, + }, + "required": ["name", "largest_star"], + "additionalProperties": False, + }, + }, + "properties": { + "name": { + "title": "Name", + "description": "The name of the universe.", + "type": "string", + }, + "galaxy": { + "title": "Galaxy", + "description": "A galaxy in the universe.", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the galaxy.", + "type": "string", + }, + "largest_star": { + "title": "Largest Star", + "description": "The largest star in the galaxy.", + "type": "object", + "properties": { + "name": {"title": "Name", "description": "The name of the star.", "type": "string"} + }, + "required": ["name"], + "additionalProperties": False, + }, + }, + "required": ["name", "largest_star"], + "additionalProperties": False, + }, + }, + "required": ["name", "galaxy"], + "additionalProperties": False, + } + ) diff --git a/tests/lib/utils.py b/tests/lib/utils.py new file mode 100644 index 0000000000..0efdfca968 --- /dev/null +++ b/tests/lib/utils.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import re +from typing import Any, Iterable +from typing_extensions import TypeAlias + +import pytest +import pydantic + +from ..utils import rich_print_str + +ReprArgs: TypeAlias = "Iterable[tuple[str | None, Any]]" + + +def print_obj(obj: object, monkeypatch: pytest.MonkeyPatch) -> str: + """Pretty print an object to a string""" + + # monkeypatch pydantic model printing so that model fields + # are always printed in the same order so we can reliably + # use this for snapshot tests + original_repr = pydantic.BaseModel.__repr_args__ + + def __repr_args__(self: pydantic.BaseModel) -> ReprArgs: + return sorted(original_repr(self), key=lambda arg: arg[0] or arg) + + with monkeypatch.context() as m: + m.setattr(pydantic.BaseModel, "__repr_args__", __repr_args__) + + string = rich_print_str(obj) + + # Pydantic v1 and v2 have different implementations of __repr__ and print out + # generics differently, so we strip out generic type parameters to ensure + # consistent snapshot tests across both versions + return re.sub(r"([A-Za-z_]\w*)\[[^\[\]]+\](?=\()", r"\1", string) diff --git a/tests/test_auth.py b/tests/test_auth.py new file mode 100644 index 0000000000..17e9cd814c --- /dev/null +++ b/tests/test_auth.py @@ -0,0 +1,186 @@ +import json +from typing import cast +from pathlib import Path + +import httpx +import respx +import pytest +from respx.models import Call +from inline_snapshot import snapshot + +from openai import OpenAI, OAuthError +from openai.auth._workload import ( + gcp_id_token_provider, + k8s_service_account_token_provider, + azure_managed_identity_token_provider, +) + + +@respx.mock +def test_basic_auth(): + respx.post("https://auth.openai.com/oauth/token").mock( + return_value=httpx.Response( + 200, + json={ + "access_token": "fake_access_token", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 3600, + }, + ) + ) + + respx.get("https://api.openai.com/v1/models").mock( + return_value=httpx.Response(200, json={"data": [], "object": "list"}) + ) + + client = OpenAI( + workload_identity={ + "identity_provider_id": "idp_123", + "service_account_id": "sa_123", + "provider": { + "get_token": lambda: "fake_subject_token", + "token_type": "jwt", + }, + }, + ) + + client.models.list() + + assert len(respx.calls) == 2 + token_call = cast(Call, respx.calls[0]) + api_call = cast(Call, respx.calls[1]) + + assert token_call.request.url == "https://auth.openai.com/oauth/token" + assert api_call.request.headers.get("Authorization") == "Bearer fake_access_token" + + +@respx.mock +def test_workload_identity_exchange_payload_and_cache() -> None: + provider_call_count = 0 + + def provider() -> str: + nonlocal provider_call_count + provider_call_count += 1 + return "fake_subject_token" + + exchange_route = respx.post("https://auth.openai.com/oauth/token").mock( + return_value=httpx.Response( + 200, + json={ + "access_token": "fake_access_token", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 3600, + }, + ) + ) + api_route = respx.get("https://api.openai.com/v1/models").mock( + return_value=httpx.Response(200, json={"data": [], "object": "list"}) + ) + + client = OpenAI( + workload_identity={ + "identity_provider_id": "idp_123", + "service_account_id": "sa_123", + "provider": { + "get_token": provider, + "token_type": "jwt", + }, + }, + ) + + client.models.list() + client.models.list() + + assert provider_call_count == 1 + assert exchange_route.call_count == 1 + assert api_route.call_count == 2 + + exchange_request = cast(respx.models.Call, exchange_route.calls[0]).request + assert json.loads(exchange_request.content) == snapshot( + { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "subject_token": "fake_subject_token", + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "identity_provider_id": "idp_123", + "service_account_id": "sa_123", + } + ) + + assert ( + cast(respx.models.Call, api_route.calls[0]).request.headers.get("Authorization") == "Bearer fake_access_token" + ) + assert ( + cast(respx.models.Call, api_route.calls[1]).request.headers.get("Authorization") == "Bearer fake_access_token" + ) + + +@respx.mock +def test_workload_identity_exchange_error() -> None: + exchange_route = respx.post("https://auth.openai.com/oauth/token").mock( + return_value=httpx.Response( + 401, + json={ + "error": "invalid_grant", + "error_description": "No service account mapping found for the provided service_account_id.", + }, + ) + ) + api_route = respx.get("https://api.openai.com/v1/models").mock( + return_value=httpx.Response(200, json={"data": [], "object": "list"}) + ) + + client = OpenAI( + workload_identity={ + "identity_provider_id": "idp_123", + "service_account_id": "sa_123", + "provider": { + "get_token": lambda: "fake_subject_token", + "token_type": "jwt", + }, + }, + ) + + with pytest.raises(OAuthError) as exc: + client.models.list() + + assert exc.value.message == "No service account mapping found for the provided service_account_id." + assert exc.value.error == "invalid_grant" + assert exc.value.status_code == 401 + assert exchange_route.call_count == 1 + assert api_route.call_count == 0 + + +def test_k8s_service_account_token_provider(tmp_path: Path) -> None: + token_file = tmp_path / "token" + token_file.write_text("my-k8s-token") + + provider = k8s_service_account_token_provider(token_file) + + assert provider["token_type"] == "jwt" + assert provider["get_token"]() == "my-k8s-token" + + +@respx.mock +def test_azure_managed_identity_token_provider() -> None: + respx.get("http://169.254.169.254/metadata/identity/oauth2/token").mock( + return_value=httpx.Response(200, json={"access_token": "azure-token"}) + ) + + provider = azure_managed_identity_token_provider() + + assert provider["token_type"] == "jwt" + assert provider["get_token"]() == "azure-token" + + +@respx.mock +def test_gcp_id_token_provider() -> None: + respx.get("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity").mock( + return_value=httpx.Response(200, text="gcp-token") + ) + + provider = gcp_id_token_provider() + + assert provider["token_type"] == "id" + assert provider["get_token"]() == "gcp-token" diff --git a/tests/test_client.py b/tests/test_client.py index 054ae0ff4e..2d8955a58e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -4,30 +4,54 @@ import gc import os +import sys import json import asyncio import inspect +import dataclasses import tracemalloc -from typing import Any, Union, cast +from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast from unittest import mock +from typing_extensions import Literal, AsyncIterator, override import httpx import pytest from respx import MockRouter from pydantic import ValidationError +from respx.models import Call as MockRequestCall -from openai import OpenAI, AsyncOpenAI, APIResponseValidationError +from openai import OpenAI, AsyncOpenAI, OpenAIError, APIResponseValidationError +from openai.auth import WorkloadIdentity from openai._types import Omit +from openai._utils import asyncify from openai._models import BaseModel, FinalRequestOptions -from openai._constants import RAW_RESPONSE_HEADER from openai._streaming import Stream, AsyncStream -from openai._exceptions import OpenAIError, APIStatusError, APITimeoutError, APIResponseValidationError -from openai._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options +from openai._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError +from openai._base_client import ( + DEFAULT_TIMEOUT, + HTTPX_DEFAULT_TIMEOUT, + BaseClient, + OtherPlatform, + DefaultHttpxClient, + DefaultAsyncHttpxClient, + get_platform, + make_request_options, +) from .utils import update_env +T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") api_key = "My API Key" +admin_api_key = "My Admin API Key" +workload_identity: WorkloadIdentity = { + "identity_provider_id": "provider_123", + "service_account_id": "service_account_123", + "provider": { + "get_token": lambda: "external-subject-token", + "token_type": "jwt", + }, +} def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: @@ -40,6 +64,57 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 +def mirror_request_content(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, content=request.content) + + +# note: we can't use the httpx.MockTransport class as it consumes the request +# body itself, which means we can't test that the body is read lazily +class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport): + def __init__( + self, + handler: Callable[[httpx.Request], httpx.Response] + | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]], + ) -> None: + self.handler = handler + + @override + def handle_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function" + assert inspect.isfunction(self.handler), "handler must be a function" + return self.handler(request) + + @override + async def handle_async_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function" + return await self.handler(request) + + +@dataclasses.dataclass +class Counter: + value: int = 0 + + +def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + +async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + def _get_open_connections(client: OpenAI | AsyncOpenAI) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -49,55 +124,61 @@ def _get_open_connections(client: OpenAI | AsyncOpenAI) -> int: class TestOpenAI: - client = OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) - @pytest.mark.respx(base_url=base_url) - def test_raw_response(self, respx_mock: MockRouter) -> None: + def test_raw_response(self, respx_mock: MockRouter, client: OpenAI) -> None: respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.post("/foo", cast_to=httpx.Response) + response = client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} @pytest.mark.respx(base_url=base_url) - def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None: + def test_raw_response_for_binary(self, respx_mock: MockRouter, client: OpenAI) -> None: respx_mock.post("/foo").mock( return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) - response = self.client.post("/foo", cast_to=httpx.Response) + response = client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} - def test_copy(self) -> None: - copied = self.client.copy() - assert id(copied) != id(self.client) + def test_copy(self, client: OpenAI) -> None: + copied = client.copy() + assert id(copied) != id(client) - copied = self.client.copy(api_key="another My API Key") + copied = client.copy(api_key="another My API Key") assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" + assert client.api_key == "My API Key" + + copied = client.copy(admin_api_key="another My Admin API Key") + assert copied.admin_api_key == "another My Admin API Key" + assert client.admin_api_key == "My Admin API Key" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, client: OpenAI) -> None: # options that have a default are overridden correctly - copied = self.client.copy(max_retries=7) + copied = client.copy(max_retries=7) assert copied.max_retries == 7 - assert self.client.max_retries == 2 + assert client.max_retries == 2 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 assert copied.max_retries == 7 # timeout - assert isinstance(self.client.timeout, httpx.Timeout) - copied = self.client.copy(timeout=None) + assert isinstance(client.timeout, httpx.Timeout) + copied = client.copy(timeout=None) assert copied.timeout is None - assert isinstance(self.client.timeout, httpx.Timeout) + assert isinstance(client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: client = OpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, ) assert client.default_headers["X-Foo"] == "bar" @@ -128,10 +209,15 @@ def test_copy_default_headers(self) -> None: match="`default_headers` and `set_default_headers` arguments are mutually exclusive", ): client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + client.close() def test_copy_default_query(self) -> None: client = OpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + default_query={"foo": "bar"}, ) assert _get_params(client)["foo"] == "bar" @@ -165,13 +251,15 @@ def test_copy_default_query(self) -> None: ): client.copy(set_default_query={}, default_query={"foo": "Bar"}) - def test_copy_signature(self) -> None: + client.close() + + def test_copy_signature(self, client: OpenAI) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. - self.client.__init__, # type: ignore[misc] + client.__init__, # type: ignore[misc] ) - copy_signature = inspect.signature(self.client.copy) + copy_signature = inspect.signature(client.copy) exclude_params = {"transport", "proxies", "_strict_response_validation"} for name in init_signature.parameters.keys(): @@ -181,12 +269,13 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" - def test_copy_build_request(self) -> None: + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") + def test_copy_build_request(self, client: OpenAI) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -243,99 +332,270 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + def test_request_timeout(self, client: OpenAI) -> None: + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT - request = self.client._build_request( - FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) - ) + request = client._build_request(FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(100.0) def test_client_timeout_option(self) -> None: - client = OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0)) + client = OpenAI( + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + timeout=httpx.Timeout(0), + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(0) + client.close() + def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used with httpx.Client(timeout=None) as http_client: client = OpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(None) + client.close() + # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: client = OpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT + client.close() + # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = OpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default + client.close() + async def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): async with httpx.AsyncClient() as http_client: OpenAI( base_url=base_url, api_key=api_key, + admin_api_key=admin_api_key, _strict_response_validation=True, http_client=cast(Any, http_client), ) def test_default_headers_option(self) -> None: - client = OpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + test_client = OpenAI( + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = OpenAI( + test_client2 = OpenAI( base_url=base_url, api_key=api_key, + admin_api_key=admin_api_key, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", "X-Stainless-Lang": "my-overriding-header", }, ) - request = client2._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" + test_client.close() + test_client2.close() + def test_validate_headers(self) -> None: - client = OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + client = OpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True + ) + options = client._prepare_options(FinalRequestOptions(method="get", url="/foo")) + request = client._build_request(options) + assert request.headers.get("Authorization") == f"Bearer {api_key}" - with pytest.raises(OpenAIError): - with update_env(**{"OPENAI_API_KEY": Omit()}): - client2 = OpenAI(base_url=base_url, api_key=None, _strict_response_validation=True) - _ = client2 + admin_request = client._build_request( + FinalRequestOptions( + method="get", + url="/organization/projects", + security={"admin_api_key_auth": True}, + ) + ) + assert admin_request.headers.get("Authorization") == f"Bearer {admin_api_key}" + + with update_env(**{"OPENAI_API_KEY": Omit()}): + admin_only = OpenAI( + base_url=base_url, + api_key=None, + admin_api_key=admin_api_key, + _strict_response_validation=True, + ) + admin_only_request = admin_only._build_request( + FinalRequestOptions( + method="get", + url="/organization/projects", + security={"admin_api_key_auth": True}, + ) + ) + assert admin_only_request.headers.get("Authorization") == f"Bearer {admin_api_key}" + + with pytest.raises( + TypeError, + match="Could not resolve authentication method", + ): + admin_only._build_request( + FinalRequestOptions( + method="post", + url="/responses", + security={"bearer_auth": True}, + ) + ) + + with update_env( + **{ + "OPENAI_API_KEY": Omit(), + "OPENAI_ADMIN_KEY": Omit(), + } + ): + no_credentials = OpenAI( + base_url=base_url, + api_key=None, + admin_api_key=None, + _enforce_credentials=False, + _strict_response_validation=True, + ) + lowercase_auth_request = no_credentials._build_request( + FinalRequestOptions(method="get", url="/foo", headers={"authorization": "Bearer custom"}) + ) + assert lowercase_auth_request.headers.get("Authorization") == "Bearer custom" + + omitted_auth_request = no_credentials._build_request( + FinalRequestOptions(method="get", url="/foo", headers={"authorization": Omit()}) + ) + assert "Authorization" not in omitted_auth_request.headers + + with update_env( + **{ + "OPENAI_API_KEY": Omit(), + "OPENAI_ADMIN_KEY": Omit(), + } + ): + with pytest.raises(OpenAIError, match="Missing credentials"): + OpenAI(base_url=base_url, api_key=None, admin_api_key=None, _strict_response_validation=True) + + @pytest.mark.respx(base_url=base_url) + def test_api_key_provider_preserves_admin_auth(self, respx_mock: MockRouter) -> None: + respx_mock.get("/organization/projects").mock(return_value=httpx.Response(200, json={"ok": True})) + + provider_called = False + + def api_key_provider() -> str: + nonlocal provider_called + provider_called = True + return "dynamic-api-key" + + client = OpenAI(base_url=base_url, api_key=api_key_provider, admin_api_key=admin_api_key) + response = client.get( + "/organization/projects", + cast_to=httpx.Response, + options={"security": {"admin_api_key_auth": True}}, + ) + + assert response.request.headers.get("Authorization") == f"Bearer {admin_api_key}" + assert provider_called is False + + def test_api_key_provider_does_not_fill_admin_auth(self) -> None: + provider_called = False + + def api_key_provider() -> str: + nonlocal provider_called + provider_called = True + return "dynamic-api-key" + + with update_env(OPENAI_ADMIN_KEY=Omit()): + client = OpenAI(base_url=base_url, api_key=api_key_provider, admin_api_key=None) + with pytest.raises(TypeError, match="Could not resolve authentication method"): + client.get( + "/organization/projects", + cast_to=httpx.Response, + options={"security": {"admin_api_key_auth": True}}, + ) + + assert provider_called is False + + @pytest.mark.respx(base_url=base_url) + def test_workload_identity_preserves_admin_auth(self, respx_mock: MockRouter) -> None: + respx_mock.get("/organization/projects").mock(return_value=httpx.Response(200, json={"ok": True})) + + client = OpenAI(base_url=base_url, workload_identity=workload_identity, admin_api_key=admin_api_key) + response = client.get( + "/organization/projects", + cast_to=httpx.Response, + options={"security": {"admin_api_key_auth": True}}, + ) + + assert response.request.headers.get("Authorization") == f"Bearer {admin_api_key}" + + def test_workload_identity_is_mutually_exclusive_with_api_key(self) -> None: + with pytest.raises( + OpenAIError, + match="The `api_key` and `workload_identity` arguments are mutually exclusive", + ): + OpenAI( + base_url=base_url, + api_key=api_key, + workload_identity=workload_identity, # type: ignore[reportArgumentType] + organization="org_123", + _strict_response_validation=True, + ) def test_default_query_option(self) -> None: client = OpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + default_query={"query_param": "bar"}, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) @@ -345,14 +605,40 @@ def test_default_query_option(self) -> None: FinalRequestOptions( method="get", url="/foo", - params={"foo": "baz", "query_param": "overriden"}, + params={"foo": "baz", "query_param": "overridden"}, ) ) url = httpx.URL(request.url) - assert dict(url.params) == {"foo": "baz", "query_param": "overriden"} + assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} - def test_request_extra_json(self) -> None: - request = self.client._build_request( + client.close() + + def test_hardcoded_query_params_in_url(self, client: OpenAI) -> None: + request = client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/foo?beta=true", + params={"limit": "10", "page": "abc"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/files/a%2Fb?beta=true", + params={"limit": "10"}, + ) + ) + assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" + + def test_request_extra_json(self, client: OpenAI) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -363,7 +649,7 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": False} - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -374,7 +660,7 @@ def test_request_extra_json(self) -> None: assert data == {"baz": False} # `extra_json` takes priority over `json_data` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -385,8 +671,8 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": None} - def test_request_extra_headers(self) -> None: - request = self.client._build_request( + def test_request_extra_headers(self, client: OpenAI) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -396,7 +682,7 @@ def test_request_extra_headers(self) -> None: assert request.headers.get("X-Foo") == "Foo" # `extra_headers` takes priority over `default_headers` when keys clash - request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request( + request = client.with_options(default_headers={"X-Bar": "true"})._build_request( FinalRequestOptions( method="post", url="/foo", @@ -407,8 +693,8 @@ def test_request_extra_headers(self) -> None: ) assert request.headers.get("X-Bar") == "false" - def test_request_extra_query(self) -> None: - request = self.client._build_request( + def test_request_extra_query(self, client: OpenAI) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -421,7 +707,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo"} # if both `query` and `extra_query` are given, they are merged - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -435,7 +721,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2"} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -451,7 +737,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, client: OpenAI) -> None: request = client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, @@ -478,7 +764,72 @@ def test_multipart_repeating_array(self, client: OpenAI) -> None: ] @pytest.mark.respx(base_url=base_url) - def test_basic_union_response(self, respx_mock: MockRouter) -> None: + def test_binary_content_upload(self, respx_mock: MockRouter, client: OpenAI) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + def test_binary_content_upload_with_iterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_sync_iterator([file_content], counter=counter) + + def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=request.read()) + + with OpenAI( + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), + ) as client: + response = client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: OpenAI) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + @pytest.mark.respx(base_url=base_url) + def test_basic_union_response(self, respx_mock: MockRouter, client: OpenAI) -> None: class Model1(BaseModel): name: str @@ -487,12 +838,12 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - def test_union_response_different_types(self, respx_mock: MockRouter) -> None: + def test_union_response_different_types(self, respx_mock: MockRouter, client: OpenAI) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -503,18 +854,18 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 @pytest.mark.respx(base_url=base_url) - def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None: + def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: OpenAI) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -530,30 +881,43 @@ class Model(BaseModel): ) ) - response = self.client.get("/foo", cast_to=Model) + response = client.get("/foo", cast_to=Model) assert isinstance(response, Model) assert response.foo == 2 def test_base_url_setter(self) -> None: - client = OpenAI(base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True) + client = OpenAI( + base_url="https://example.com/from_init", + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + ) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] assert client.base_url == "https://example.com/from_setter/" + client.close() + def test_base_url_env(self) -> None: with update_env(OPENAI_BASE_URL="http://localhost:5000/from/env"): - client = OpenAI(api_key=api_key, _strict_response_validation=True) + client = OpenAI(api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ - OpenAI(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), OpenAI( base_url="http://localhost:5000/custom/path/", api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + ), + OpenAI( + base_url="http://localhost:5000/custom/path/", + api_key=api_key, + admin_api_key=admin_api_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -569,14 +933,21 @@ def test_base_url_trailing_slash(self, client: OpenAI) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", [ - OpenAI(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), OpenAI( base_url="http://localhost:5000/custom/path/", api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + ), + OpenAI( + base_url="http://localhost:5000/custom/path/", + api_key=api_key, + admin_api_key=admin_api_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -592,14 +963,21 @@ def test_base_url_no_trailing_slash(self, client: OpenAI) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", [ - OpenAI(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), OpenAI( base_url="http://localhost:5000/custom/path/", api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + ), + OpenAI( + base_url="http://localhost:5000/custom/path/", + api_key=api_key, + admin_api_key=admin_api_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -615,50 +993,61 @@ def test_absolute_request_url(self, client: OpenAI) -> None: ), ) assert request.url == "https://myapi.com/foo" + client.close() def test_copied_client_does_not_close_http(self) -> None: - client = OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) - assert not client.is_closed() + test_client = OpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True + ) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied - assert not client.is_closed() + assert not test_client.is_closed() def test_client_context_manager(self) -> None: - client = OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) - with client as c2: - assert c2 is client + test_client = OpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True + ) + with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + def test_client_response_validation_error(self, respx_mock: MockRouter, client: OpenAI) -> None: class Model(BaseModel): foo: str respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) with pytest.raises(APIResponseValidationError) as exc: - self.client.get("/foo", cast_to=Model) + client.get("/foo", cast_to=Model) assert isinstance(exc.value.__cause__, ValidationError) def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None)) + OpenAI( + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + max_retries=cast(Any, None), + ) @pytest.mark.respx(base_url=base_url) - def test_default_stream_cls(self, respx_mock: MockRouter) -> None: + def test_default_stream_cls(self, respx_mock: MockRouter, client: OpenAI) -> None: class Model(BaseModel): name: str respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - stream = self.client.post("/foo", cast_to=Model, stream=True, stream_cls=Stream[Model]) + stream = client.post("/foo", cast_to=Model, stream=True, stream_cls=Stream[Model]) assert isinstance(stream, Stream) stream.response.close() @@ -669,16 +1058,23 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) + strict_client = OpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True + ) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = OpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=False + ) - response = client.get("/foo", cast_to=Model) + response = non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + strict_client.close() + non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -697,12 +1093,13 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], + [-1100, "", 8], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) - + def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, client: OpenAI + ) -> None: headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) calculated = client._calculate_retry_timeout(remaining_retries, options, headers) @@ -710,60 +1107,50 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: OpenAI) -> None: respx_mock.post("/chat/completions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.post( - "/chat/completions", - body=cast( - object, - dict( - messages=[ - { - "role": "user", - "content": "Say this is a test", - } - ], - model="gpt-3.5-turbo", - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - - assert _get_open_connections(self.client) == 0 + client.chat.completions.with_streaming_response.create( + messages=[ + { + "content": "string", + "role": "developer", + } + ], + model="gpt-5.4", + ).__enter__() + + assert _get_open_connections(client) == 0 @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: OpenAI) -> None: respx_mock.post("/chat/completions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.post( - "/chat/completions", - body=cast( - object, - dict( - messages=[ - { - "role": "user", - "content": "Say this is a test", - } - ], - model="gpt-3.5-turbo", - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - - assert _get_open_connections(self.client) == 0 + client.chat.completions.with_streaming_response.create( + messages=[ + { + "content": "string", + "role": "developer", + } + ], + model="gpt-5.4", + ).__enter__() + assert _get_open_connections(client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retries_taken(self, client: OpenAI, failures_before_success: int, respx_mock: MockRouter) -> None: + @pytest.mark.parametrize("failure_mode", ["status", "exception"]) + def test_retries_taken( + self, + client: OpenAI, + failures_before_success: int, + failure_mode: Literal["status", "exception"], + respx_mock: MockRouter, + ) -> None: client = client.with_options(max_retries=4) nb_retries = 0 @@ -772,6 +1159,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: nonlocal nb_retries if nb_retries < failures_before_success: nb_retries += 1 + if failure_mode == "exception": + raise RuntimeError("oops") return httpx.Response(500) return httpx.Response(200) @@ -781,13 +1170,78 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", ) assert response.retries_taken == failures_before_success + assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + def test_omit_retry_count_header( + self, client: OpenAI, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + + response = client.chat.completions.with_raw_response.create( + messages=[ + { + "content": "string", + "role": "developer", + } + ], + model="gpt-5.4", + extra_headers={"x-stainless-retry-count": Omit()}, + ) + + assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + def test_overwrite_retry_count_header( + self, client: OpenAI, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + + response = client.chat.completions.with_raw_response.create( + messages=[ + { + "content": "string", + "role": "developer", + } + ], + model="gpt-5.4", + extra_headers={"x-stainless-retry-count": "42"}, + ) + + assert response.http_request.headers.get("x-stainless-retry-count") == "42" @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @@ -812,66 +1266,182 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", ) as response: assert response.retries_taken == failures_before_success - - -class TestAsyncOpenAI: - client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) + assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) + + client = DefaultHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_raw_response(self, respx_mock: MockRouter) -> None: - respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + def test_follow_redirects(self, respx_mock: MockRouter, client: OpenAI) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) - response = await self.client.post("/foo", cast_to=httpx.Response) + response = client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) assert response.status_code == 200 - assert isinstance(response, httpx.Response) - assert response.json() == {"foo": "bar"} + assert response.json() == {"status": "ok"} @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None: - respx_mock.post("/foo").mock( - return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') + def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: OpenAI) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) ) - response = await self.client.post("/foo", cast_to=httpx.Response) - assert response.status_code == 200 - assert isinstance(response, httpx.Response) - assert response.json() == {"foo": "bar"} + with pytest.raises(APIStatusError) as exc_info: + client.post("/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response) - def test_copy(self) -> None: - copied = self.client.copy() - assert id(copied) != id(self.client) + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" - copied = self.client.copy(api_key="another My API Key") + def test_api_key_before_after_refresh_provider(self) -> None: + client = OpenAI(base_url=base_url, api_key=lambda: "test_bearer_token") + + assert client.api_key == "" + assert "Authorization" not in client.auth_headers + + client._refresh_api_key() + + assert client.api_key == "test_bearer_token" + assert client.auth_headers.get("Authorization") == "Bearer test_bearer_token" + + def test_api_key_before_after_refresh_str(self) -> None: + client = OpenAI(base_url=base_url, api_key="test_api_key") + + assert client.auth_headers.get("Authorization") == "Bearer test_api_key" + client._refresh_api_key() + + assert client.auth_headers.get("Authorization") == "Bearer test_api_key" + + @pytest.mark.respx() + def test_api_key_refresh_on_retry(self, respx_mock: MockRouter) -> None: + respx_mock.post(base_url + "/chat/completions").mock( + side_effect=[ + httpx.Response(500, json={"error": "server error"}), + httpx.Response(200, json={"foo": "bar"}), + ] + ) + + counter = 0 + + def token_provider() -> str: + nonlocal counter + + counter += 1 + + if counter == 1: + return "first" + + return "second" + + client = OpenAI(base_url=base_url, api_key=token_provider) + client.chat.completions.create(messages=[], model="gpt-4") + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 2 + + assert calls[0].request.headers.get("Authorization") == "Bearer first" + assert calls[1].request.headers.get("Authorization") == "Bearer second" + + def test_copy_auth(self) -> None: + client = OpenAI(base_url=base_url, api_key=lambda: "test_bearer_token_1").copy( + api_key=lambda: "test_bearer_token_2" + ) + client._refresh_api_key() + assert client.auth_headers == {"Authorization": "Bearer test_bearer_token_2"} + + +class TestAsyncOpenAI: + @pytest.mark.respx(base_url=base_url) + async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + response = await async_client.post("/foo", cast_to=httpx.Response) + assert response.status_code == 200 + assert isinstance(response, httpx.Response) + assert response.json() == {"foo": "bar"} + + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + respx_mock.post("/foo").mock( + return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') + ) + + response = await async_client.post("/foo", cast_to=httpx.Response) + assert response.status_code == 200 + assert isinstance(response, httpx.Response) + assert response.json() == {"foo": "bar"} + + def test_copy(self, async_client: AsyncOpenAI) -> None: + copied = async_client.copy() + assert id(copied) != id(async_client) + + copied = async_client.copy(api_key="another My API Key") assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" + assert async_client.api_key == "My API Key" - def test_copy_default_options(self) -> None: + copied = async_client.copy(admin_api_key="another My Admin API Key") + assert copied.admin_api_key == "another My Admin API Key" + assert async_client.admin_api_key == "My Admin API Key" + + def test_copy_default_options(self, async_client: AsyncOpenAI) -> None: # options that have a default are overridden correctly - copied = self.client.copy(max_retries=7) + copied = async_client.copy(max_retries=7) assert copied.max_retries == 7 - assert self.client.max_retries == 2 + assert async_client.max_retries == 2 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 assert copied.max_retries == 7 # timeout - assert isinstance(self.client.timeout, httpx.Timeout) - copied = self.client.copy(timeout=None) + assert isinstance(async_client.timeout, httpx.Timeout) + copied = async_client.copy(timeout=None) assert copied.timeout is None - assert isinstance(self.client.timeout, httpx.Timeout) + assert isinstance(async_client.timeout, httpx.Timeout) - def test_copy_default_headers(self) -> None: + async def test_copy_default_headers(self) -> None: client = AsyncOpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, ) assert client.default_headers["X-Foo"] == "bar" @@ -902,10 +1472,15 @@ def test_copy_default_headers(self) -> None: match="`default_headers` and `set_default_headers` arguments are mutually exclusive", ): client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + await client.close() - def test_copy_default_query(self) -> None: + async def test_copy_default_query(self) -> None: client = AsyncOpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + default_query={"foo": "bar"}, ) assert _get_params(client)["foo"] == "bar" @@ -939,13 +1514,15 @@ def test_copy_default_query(self) -> None: ): client.copy(set_default_query={}, default_query={"foo": "Bar"}) - def test_copy_signature(self) -> None: + await client.close() + + def test_copy_signature(self, async_client: AsyncOpenAI) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. - self.client.__init__, # type: ignore[misc] + async_client.__init__, # type: ignore[misc] ) - copy_signature = inspect.signature(self.client.copy) + copy_signature = inspect.signature(async_client.copy) exclude_params = {"transport", "proxies", "_strict_response_validation"} for name in init_signature.parameters.keys(): @@ -955,12 +1532,13 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" - def test_copy_build_request(self) -> None: + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") + def test_copy_build_request(self, async_client: AsyncOpenAI) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = async_client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -1017,12 +1595,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - async def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + async def test_request_timeout(self, async_client: AsyncOpenAI) -> None: + request = async_client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT - request = self.client._build_request( + request = async_client._build_request( FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) ) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1030,88 +1608,245 @@ async def test_request_timeout(self) -> None: async def test_client_timeout_option(self) -> None: client = AsyncOpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0) + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + timeout=httpx.Timeout(0), ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(0) + await client.close() + async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used async with httpx.AsyncClient(timeout=None) as http_client: client = AsyncOpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(None) + await client.close() + # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: client = AsyncOpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT + await client.close() + # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = AsyncOpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default + await client.close() + def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): with httpx.Client() as http_client: AsyncOpenAI( base_url=base_url, api_key=api_key, + admin_api_key=admin_api_key, _strict_response_validation=True, http_client=cast(Any, http_client), ) - def test_default_headers_option(self) -> None: - client = AsyncOpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + async def test_default_headers_option(self) -> None: + test_client = AsyncOpenAI( + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = AsyncOpenAI( + test_client2 = AsyncOpenAI( base_url=base_url, api_key=api_key, + admin_api_key=admin_api_key, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", "X-Stainless-Lang": "my-overriding-header", }, ) - request = client2._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" - def test_validate_headers(self) -> None: - client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + await test_client.close() + await test_client2.close() + + async def test_validate_headers(self) -> None: + client = AsyncOpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True + ) + options = await client._prepare_options(FinalRequestOptions(method="get", url="/foo")) + request = client._build_request(options) assert request.headers.get("Authorization") == f"Bearer {api_key}" - with pytest.raises(OpenAIError): - with update_env(**{"OPENAI_API_KEY": Omit()}): - client2 = AsyncOpenAI(base_url=base_url, api_key=None, _strict_response_validation=True) - _ = client2 + admin_request = client._build_request( + FinalRequestOptions( + method="get", + url="/organization/projects", + security={"admin_api_key_auth": True}, + ) + ) + assert admin_request.headers.get("Authorization") == f"Bearer {admin_api_key}" - def test_default_query_option(self) -> None: + with update_env(**{"OPENAI_API_KEY": Omit()}): + admin_only = AsyncOpenAI( + base_url=base_url, + api_key=None, + admin_api_key=admin_api_key, + _strict_response_validation=True, + ) + admin_only_request = admin_only._build_request( + FinalRequestOptions( + method="get", + url="/organization/projects", + security={"admin_api_key_auth": True}, + ) + ) + assert admin_only_request.headers.get("Authorization") == f"Bearer {admin_api_key}" + + with pytest.raises( + TypeError, + match="Could not resolve authentication method", + ): + admin_only._build_request( + FinalRequestOptions( + method="post", + url="/responses", + security={"bearer_auth": True}, + ) + ) + + with update_env( + **{ + "OPENAI_API_KEY": Omit(), + "OPENAI_ADMIN_KEY": Omit(), + } + ): + no_credentials = AsyncOpenAI( + base_url=base_url, + api_key=None, + admin_api_key=None, + _enforce_credentials=False, + _strict_response_validation=True, + ) + lowercase_auth_request = no_credentials._build_request( + FinalRequestOptions(method="get", url="/foo", headers={"authorization": "Bearer custom"}) + ) + assert lowercase_auth_request.headers.get("Authorization") == "Bearer custom" + + omitted_auth_request = no_credentials._build_request( + FinalRequestOptions(method="get", url="/foo", headers={"authorization": Omit()}) + ) + assert "Authorization" not in omitted_auth_request.headers + + with update_env( + **{ + "OPENAI_API_KEY": Omit(), + "OPENAI_ADMIN_KEY": Omit(), + } + ): + with pytest.raises(OpenAIError, match="Missing credentials"): + AsyncOpenAI(base_url=base_url, api_key=None, admin_api_key=None, _strict_response_validation=True) + + @pytest.mark.respx(base_url=base_url) + async def test_api_key_provider_preserves_admin_auth(self, respx_mock: MockRouter) -> None: + respx_mock.get("/organization/projects").mock(return_value=httpx.Response(200, json={"ok": True})) + + provider_called = False + + async def api_key_provider() -> str: + nonlocal provider_called + provider_called = True + return "dynamic-api-key" + + client = AsyncOpenAI(base_url=base_url, api_key=api_key_provider, admin_api_key=admin_api_key) + response = await client.get( + "/organization/projects", + cast_to=httpx.Response, + options={"security": {"admin_api_key_auth": True}}, + ) + + assert response.request.headers.get("Authorization") == f"Bearer {admin_api_key}" + assert provider_called is False + + async def test_api_key_provider_does_not_fill_admin_auth(self) -> None: + provider_called = False + + async def api_key_provider() -> str: + nonlocal provider_called + provider_called = True + return "dynamic-api-key" + + with update_env(OPENAI_ADMIN_KEY=Omit()): + client = AsyncOpenAI(base_url=base_url, api_key=api_key_provider, admin_api_key=None) + with pytest.raises(TypeError, match="Could not resolve authentication method"): + await client.get( + "/organization/projects", + cast_to=httpx.Response, + options={"security": {"admin_api_key_auth": True}}, + ) + + assert provider_called is False + + @pytest.mark.respx(base_url=base_url) + async def test_workload_identity_preserves_admin_auth(self, respx_mock: MockRouter) -> None: + respx_mock.get("/organization/projects").mock(return_value=httpx.Response(200, json={"ok": True})) + + client = AsyncOpenAI(base_url=base_url, workload_identity=workload_identity, admin_api_key=admin_api_key) + response = await client.get( + "/organization/projects", + cast_to=httpx.Response, + options={"security": {"admin_api_key_auth": True}}, + ) + + assert response.request.headers.get("Authorization") == f"Bearer {admin_api_key}" + + async def test_default_query_option(self) -> None: client = AsyncOpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + default_query={"query_param": "bar"}, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) @@ -1121,14 +1856,40 @@ def test_default_query_option(self) -> None: FinalRequestOptions( method="get", url="/foo", - params={"foo": "baz", "query_param": "overriden"}, + params={"foo": "baz", "query_param": "overridden"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} + + await client.close() + + async def test_hardcoded_query_params_in_url(self, async_client: AsyncOpenAI) -> None: + request = async_client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true"} + + request = async_client._build_request( + FinalRequestOptions( + method="get", + url="/foo?beta=true", + params={"limit": "10", "page": "abc"}, ) ) url = httpx.URL(request.url) - assert dict(url.params) == {"foo": "baz", "query_param": "overriden"} + assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} - def test_request_extra_json(self) -> None: - request = self.client._build_request( + request = async_client._build_request( + FinalRequestOptions( + method="get", + url="/files/a%2Fb?beta=true", + params={"limit": "10"}, + ) + ) + assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" + + def test_request_extra_json(self, client: OpenAI) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1139,7 +1900,7 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": False} - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1150,7 +1911,7 @@ def test_request_extra_json(self) -> None: assert data == {"baz": False} # `extra_json` takes priority over `json_data` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1161,8 +1922,8 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": None} - def test_request_extra_headers(self) -> None: - request = self.client._build_request( + def test_request_extra_headers(self, client: OpenAI) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1172,7 +1933,7 @@ def test_request_extra_headers(self) -> None: assert request.headers.get("X-Foo") == "Foo" # `extra_headers` takes priority over `default_headers` when keys clash - request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request( + request = client.with_options(default_headers={"X-Bar": "true"})._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1183,8 +1944,8 @@ def test_request_extra_headers(self) -> None: ) assert request.headers.get("X-Bar") == "false" - def test_request_extra_query(self) -> None: - request = self.client._build_request( + def test_request_extra_query(self, client: OpenAI) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1197,7 +1958,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo"} # if both `query` and `extra_query` are given, they are merged - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1211,7 +1972,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2"} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1227,7 +1988,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, async_client: AsyncOpenAI) -> None: request = async_client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, @@ -1254,7 +2015,74 @@ def test_multipart_repeating_array(self, async_client: AsyncOpenAI) -> None: ] @pytest.mark.respx(base_url=base_url) - async def test_basic_union_response(self, respx_mock: MockRouter) -> None: + async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = await async_client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + async def test_binary_content_upload_with_asynciterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_async_iterator([file_content], counter=counter) + + async def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=await request.aread()) + + async with AsyncOpenAI( + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), + ) as client: + response = await client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload_with_body_is_deprecated( + self, respx_mock: MockRouter, async_client: AsyncOpenAI + ) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = await async_client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + @pytest.mark.respx(base_url=base_url) + async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: class Model1(BaseModel): name: str @@ -1263,12 +2091,12 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - async def test_union_response_different_types(self, respx_mock: MockRouter) -> None: + async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -1279,18 +2107,20 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 @pytest.mark.respx(base_url=base_url) - async def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None: + async def test_non_application_json_content_type_for_json_data( + self, respx_mock: MockRouter, async_client: AsyncOpenAI + ) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -1306,13 +2136,16 @@ class Model(BaseModel): ) ) - response = await self.client.get("/foo", cast_to=Model) + response = await async_client.get("/foo", cast_to=Model) assert isinstance(response, Model) assert response.foo == 2 - def test_base_url_setter(self) -> None: + async def test_base_url_setter(self) -> None: client = AsyncOpenAI( - base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True + base_url="https://example.com/from_init", + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, ) assert client.base_url == "https://example.com/from_init/" @@ -1320,27 +2153,33 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" - def test_base_url_env(self) -> None: + await client.close() + + async def test_base_url_env(self) -> None: with update_env(OPENAI_BASE_URL="http://localhost:5000/from/env"): - client = AsyncOpenAI(api_key=api_key, _strict_response_validation=True) + client = AsyncOpenAI(api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ AsyncOpenAI( - base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, ), AsyncOpenAI( base_url="http://localhost:5000/custom/path/", api_key=api_key, + admin_api_key=admin_api_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: AsyncOpenAI) -> None: + async def test_base_url_trailing_slash(self, client: AsyncOpenAI) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1349,23 +2188,28 @@ def test_base_url_trailing_slash(self, client: AsyncOpenAI) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", [ AsyncOpenAI( - base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, ), AsyncOpenAI( base_url="http://localhost:5000/custom/path/", api_key=api_key, + admin_api_key=admin_api_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: AsyncOpenAI) -> None: + async def test_base_url_no_trailing_slash(self, client: AsyncOpenAI) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1374,23 +2218,28 @@ def test_base_url_no_trailing_slash(self, client: AsyncOpenAI) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", [ AsyncOpenAI( - base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, ), AsyncOpenAI( base_url="http://localhost:5000/custom/path/", api_key=api_key, + admin_api_key=admin_api_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: AsyncOpenAI) -> None: + async def test_absolute_request_url(self, client: AsyncOpenAI) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1399,76 +2248,89 @@ def test_absolute_request_url(self, client: AsyncOpenAI) -> None: ), ) assert request.url == "https://myapi.com/foo" + await client.close() async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) - assert not client.is_closed() + test_client = AsyncOpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True + ) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied await asyncio.sleep(0.2) - assert not client.is_closed() + assert not test_client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) - async with client as c2: - assert c2 is client + test_client = AsyncOpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True + ) + async with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + async def test_client_response_validation_error(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: class Model(BaseModel): foo: str respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) with pytest.raises(APIResponseValidationError) as exc: - await self.client.get("/foo", cast_to=Model) + await async_client.get("/foo", cast_to=Model) assert isinstance(exc.value.__cause__, ValidationError) async def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): AsyncOpenAI( - base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None) + base_url=base_url, + api_key=api_key, + admin_api_key=admin_api_key, + _strict_response_validation=True, + max_retries=cast(Any, None), ) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_default_stream_cls(self, respx_mock: MockRouter) -> None: + async def test_default_stream_cls(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: class Model(BaseModel): name: str respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - stream = await self.client.post("/foo", cast_to=Model, stream=True, stream_cls=AsyncStream[Model]) + stream = await async_client.post("/foo", cast_to=Model, stream=True, stream_cls=AsyncStream[Model]) assert isinstance(stream, AsyncStream) await stream.response.aclose() @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: class Model(BaseModel): name: str respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) + strict_client = AsyncOpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=True + ) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = AsyncOpenAI( + base_url=base_url, api_key=api_key, admin_api_key=admin_api_key, _strict_response_validation=False + ) - response = await client.get("/foo", cast_to=Model) + response = await non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + await strict_client.close() + await non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -1487,76 +2349,63 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], + [-1100, "", 8], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - @pytest.mark.asyncio - async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) - + async def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncOpenAI + ) -> None: headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) - calculated = client._calculate_retry_timeout(remaining_retries, options, headers) + calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers) assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: respx_mock.post("/chat/completions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.post( - "/chat/completions", - body=cast( - object, - dict( - messages=[ - { - "role": "user", - "content": "Say this is a test", - } - ], - model="gpt-3.5-turbo", - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - - assert _get_open_connections(self.client) == 0 + await async_client.chat.completions.with_streaming_response.create( + messages=[ + { + "content": "string", + "role": "developer", + } + ], + model="gpt-5.4", + ).__aenter__() + + assert _get_open_connections(async_client) == 0 @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: respx_mock.post("/chat/completions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.post( - "/chat/completions", - body=cast( - object, - dict( - messages=[ - { - "role": "user", - "content": "Say this is a test", - } - ], - model="gpt-3.5-turbo", - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - - assert _get_open_connections(self.client) == 0 + await async_client.chat.completions.with_streaming_response.create( + messages=[ + { + "content": "string", + "role": "developer", + } + ], + model="gpt-5.4", + ).__aenter__() + assert _get_open_connections(async_client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio + @pytest.mark.parametrize("failure_mode", ["status", "exception"]) async def test_retries_taken( - self, async_client: AsyncOpenAI, failures_before_success: int, respx_mock: MockRouter + self, + async_client: AsyncOpenAI, + failures_before_success: int, + failure_mode: Literal["status", "exception"], + respx_mock: MockRouter, ) -> None: client = async_client.with_options(max_retries=4) @@ -1566,6 +2415,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: nonlocal nb_retries if nb_retries < failures_before_success: nb_retries += 1 + if failure_mode == "exception": + raise RuntimeError("oops") return httpx.Response(500) return httpx.Response(200) @@ -1575,18 +2426,82 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", ) assert response.retries_taken == failures_before_success + assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + async def test_omit_retry_count_header( + self, async_client: AsyncOpenAI, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = async_client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + + response = await client.chat.completions.with_raw_response.create( + messages=[ + { + "content": "string", + "role": "developer", + } + ], + model="gpt-5.4", + extra_headers={"x-stainless-retry-count": Omit()}, + ) + + assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + async def test_overwrite_retry_count_header( + self, async_client: AsyncOpenAI, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = async_client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/chat/completions").mock(side_effect=retry_handler) + + response = await client.chat.completions.with_raw_response.create( + messages=[ + { + "content": "string", + "role": "developer", + } + ], + model="gpt-5.4", + extra_headers={"x-stainless-retry-count": "42"}, + ) + + assert response.http_request.headers.get("x-stainless-retry-count") == "42" @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_retries_taken_new_response_class( self, async_client: AsyncOpenAI, failures_before_success: int, respx_mock: MockRouter ) -> None: @@ -1607,9 +2522,430 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: messages=[ { "content": "string", - "role": "system", + "role": "developer", } ], - model="gpt-4o", + model="gpt-5.4", ) as response: assert response.retries_taken == failures_before_success + assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + async def test_get_platform(self) -> None: + platform = await asyncify(get_platform)() + assert isinstance(platform, (str, OtherPlatform)) + + async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) + + client = DefaultAsyncHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + async def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultAsyncHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = await async_client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncOpenAI) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + await async_client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" + + async def test_api_key_before_after_refresh_provider(self) -> None: + async def mock_api_key_provider(): + return "test_bearer_token" + + client = AsyncOpenAI(base_url=base_url, api_key=mock_api_key_provider) + + assert client.api_key == "" + assert "Authorization" not in client.auth_headers + + await client._refresh_api_key() + + assert client.api_key == "test_bearer_token" + assert client.auth_headers.get("Authorization") == "Bearer test_bearer_token" + + async def test_api_key_before_after_refresh_str(self) -> None: + client = AsyncOpenAI(base_url=base_url, api_key="test_api_key") + + assert client.auth_headers.get("Authorization") == "Bearer test_api_key" + await client._refresh_api_key() + + assert client.auth_headers.get("Authorization") == "Bearer test_api_key" + + @pytest.mark.respx() + async def test_bearer_token_refresh_async(self, respx_mock: MockRouter) -> None: + respx_mock.post(base_url + "/chat/completions").mock( + side_effect=[ + httpx.Response(500, json={"error": "server error"}), + httpx.Response(200, json={"foo": "bar"}), + ] + ) + + counter = 0 + + async def token_provider() -> str: + nonlocal counter + + counter += 1 + + if counter == 1: + return "first" + + return "second" + + client = AsyncOpenAI(base_url=base_url, api_key=token_provider) + await client.chat.completions.create(messages=[], model="gpt-4") + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 2 + + assert calls[0].request.headers.get("Authorization") == "Bearer first" + assert calls[1].request.headers.get("Authorization") == "Bearer second" + + async def test_copy_auth(self) -> None: + async def token_provider_1() -> str: + return "test_bearer_token_1" + + async def token_provider_2() -> str: + return "test_bearer_token_2" + + client = AsyncOpenAI(base_url=base_url, api_key=token_provider_1).copy(api_key=token_provider_2) + await client._refresh_api_key() + assert client.auth_headers == {"Authorization": "Bearer test_bearer_token_2"} + + +class TestWorkloadIdentity401Retry: + @pytest.mark.respx() + def test_workload_identity_401_retry(self, respx_mock: MockRouter) -> None: + provider_call_count = 0 + + def provider() -> str: + nonlocal provider_call_count + provider_call_count += 1 + return f"external-subject-token-{provider_call_count}" + + respx_mock.post("https://auth.openai.com/oauth/token").mock( + side_effect=[ + httpx.Response( + 200, + json={ + "access_token": "openai-access-token-1", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 3600, + }, + ), + httpx.Response( + 200, + json={ + "access_token": "openai-access-token-2", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 3600, + }, + ), + ] + ) + + respx_mock.post(base_url + "/chat/completions").mock( + side_effect=[ + httpx.Response(401, json={"error": {"message": "Unauthorized", "type": "invalid_request_error"}}), + httpx.Response( + 200, + json={ + "id": "chatcmpl-123", + "object": "chat.completion", + "created": 1234567890, + "model": "gpt-4", + "choices": [], + }, + ), + ] + ) + + with OpenAI( + base_url=base_url, + workload_identity={ + **workload_identity, + "provider": { + "get_token": provider, + "token_type": "jwt", + }, + }, + organization="org_123", + project="proj_123", + _strict_response_validation=True, + ) as client: + client.chat.completions.create(messages=[], model="gpt-4") + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 4 + + assert calls[0].request.url == httpx.URL("https://auth.openai.com/oauth/token") + assert calls[1].request.url == httpx.URL(base_url + "/chat/completions") + assert calls[1].request.headers.get("Authorization") == "Bearer openai-access-token-1" + + assert calls[2].request.url == httpx.URL("https://auth.openai.com/oauth/token") + + assert calls[3].request.url == httpx.URL(base_url + "/chat/completions") + assert calls[3].request.headers.get("Authorization") == "Bearer openai-access-token-2" + + assert provider_call_count == 2 + + @pytest.mark.respx() + def test_401_without_workload_identity_no_retry(self, respx_mock: MockRouter) -> None: + respx_mock.post(base_url + "/chat/completions").mock( + return_value=httpx.Response( + 401, json={"error": {"message": "Unauthorized", "type": "invalid_request_error"}} + ) + ) + + with OpenAI( + base_url=base_url, + api_key="test-api-key", + _strict_response_validation=True, + ) as client: + with pytest.raises(APIStatusError) as exc_info: + client.chat.completions.create(messages=[], model="gpt-4") + + assert exc_info.value.status_code == 401 + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 1 + + @pytest.mark.respx() + def test_non_401_errors_no_retry(self, respx_mock: MockRouter) -> None: + provider_call_count = 0 + + def provider() -> str: + nonlocal provider_call_count + provider_call_count += 1 + return "external-subject-token" + + respx_mock.post("https://auth.openai.com/oauth/token").mock( + return_value=httpx.Response( + 200, + json={ + "access_token": "openai-access-token-1", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 3600, + }, + ) + ) + + respx_mock.post(base_url + "/chat/completions").mock( + return_value=httpx.Response(403, json={"error": {"message": "Forbidden", "type": "invalid_request_error"}}) + ) + + with OpenAI( + base_url=base_url, + workload_identity={ + **workload_identity, + "provider": { + "get_token": provider, + "token_type": "jwt", + }, + }, + organization="org_123", + project="proj_123", + _strict_response_validation=True, + ) as client: + with pytest.raises(APIStatusError) as exc_info: + client.chat.completions.create(messages=[], model="gpt-4") + + assert exc_info.value.status_code == 403 + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 2 + + assert provider_call_count == 1 + + +class TestAsyncWorkloadIdentity401Retry: + @pytest.mark.respx() + async def test_workload_identity_401_retry(self, respx_mock: MockRouter) -> None: + provider_call_count = 0 + + def provider() -> str: + nonlocal provider_call_count + provider_call_count += 1 + return f"external-subject-token-{provider_call_count}" + + respx_mock.post("https://auth.openai.com/oauth/token").mock( + side_effect=[ + httpx.Response( + 200, + json={ + "access_token": "openai-access-token-1", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 3600, + }, + ), + httpx.Response( + 200, + json={ + "access_token": "openai-access-token-2", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 3600, + }, + ), + ] + ) + + respx_mock.post(base_url + "/chat/completions").mock( + side_effect=[ + httpx.Response(401, json={"error": {"message": "Unauthorized", "type": "invalid_request_error"}}), + httpx.Response( + 200, + json={ + "id": "chatcmpl-123", + "object": "chat.completion", + "created": 1234567890, + "model": "gpt-4", + "choices": [], + }, + ), + ] + ) + + async with AsyncOpenAI( + base_url=base_url, + workload_identity={ + **workload_identity, + "provider": { + "get_token": provider, + "token_type": "jwt", + }, + }, + organization="org_123", + project="proj_123", + _strict_response_validation=True, + ) as client: + await client.chat.completions.create(messages=[], model="gpt-4") + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 4 + + assert calls[0].request.url == httpx.URL("https://auth.openai.com/oauth/token") + assert calls[1].request.url == httpx.URL(base_url + "/chat/completions") + assert calls[1].request.headers.get("Authorization") == "Bearer openai-access-token-1" + + assert calls[2].request.url == httpx.URL("https://auth.openai.com/oauth/token") + + assert calls[3].request.url == httpx.URL(base_url + "/chat/completions") + assert calls[3].request.headers.get("Authorization") == "Bearer openai-access-token-2" + + assert provider_call_count == 2 + + @pytest.mark.respx() + async def test_401_without_workload_identity_no_retry(self, respx_mock: MockRouter) -> None: + respx_mock.post(base_url + "/chat/completions").mock( + return_value=httpx.Response( + 401, json={"error": {"message": "Unauthorized", "type": "invalid_request_error"}} + ) + ) + + async with AsyncOpenAI( + base_url=base_url, + api_key="test-api-key", + _strict_response_validation=True, + ) as client: + with pytest.raises(APIStatusError) as exc_info: + await client.chat.completions.create(messages=[], model="gpt-4") + + assert exc_info.value.status_code == 401 + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 1 + + @pytest.mark.respx() + async def test_non_401_errors_no_retry(self, respx_mock: MockRouter) -> None: + provider_call_count = 0 + + def provider() -> str: + nonlocal provider_call_count + provider_call_count += 1 + return "external-subject-token" + + respx_mock.post("https://auth.openai.com/oauth/token").mock( + return_value=httpx.Response( + 200, + json={ + "access_token": "openai-access-token-1", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 3600, + }, + ) + ) + + respx_mock.post(base_url + "/chat/completions").mock( + return_value=httpx.Response(403, json={"error": {"message": "Forbidden", "type": "invalid_request_error"}}) + ) + + async with AsyncOpenAI( + base_url=base_url, + workload_identity={ + **workload_identity, + "provider": { + "get_token": provider, + "token_type": "jwt", + }, + }, + organization="org_123", + project="proj_123", + _strict_response_validation=True, + ) as client: + with pytest.raises(APIStatusError) as exc_info: + await client.chat.completions.create(messages=[], model="gpt-4") + + assert exc_info.value.status_code == 403 + + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 2 + + assert provider_call_count == 1 diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py deleted file mode 100644 index 86a2adb1a2..0000000000 --- a/tests/test_deepcopy.py +++ /dev/null @@ -1,58 +0,0 @@ -from openai._utils import deepcopy_minimal - - -def assert_different_identities(obj1: object, obj2: object) -> None: - assert obj1 == obj2 - assert id(obj1) != id(obj2) - - -def test_simple_dict() -> None: - obj1 = {"foo": "bar"} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_dict() -> None: - obj1 = {"foo": {"bar": True}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - - -def test_complex_nested_dict() -> None: - obj1 = {"foo": {"bar": [{"hello": "world"}]}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"]) - assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0]) - - -def test_simple_list() -> None: - obj1 = ["a", "b", "c"] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_list() -> None: - obj1 = ["a", [1, 2, 3]] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1[1], obj2[1]) - - -class MyObject: ... - - -def test_ignores_other_types() -> None: - # custom classes - my_obj = MyObject() - obj1 = {"foo": my_obj} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert obj1["foo"] is my_obj - - # tuples - obj3 = ("a", "b") - obj4 = deepcopy_minimal(obj3) - assert obj3 is obj4 diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index 0f6fb04d7d..54490e133f 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -4,7 +4,7 @@ import pytest -from openai._types import FileTypes +from openai._types import FileTypes, ArrayFormat from openai._utils import extract_files @@ -35,6 +35,12 @@ def test_multiple_files() -> None: assert query == {"documents": [{}, {}]} +def test_top_level_file_array() -> None: + query = {"files": [b"file one", b"file two"], "title": "hello"} + assert extract_files(query, paths=[["files", ""]]) == [("files[]", b"file one"), ("files[]", b"file two")] + assert query == {"title": "hello"} + + @pytest.mark.parametrize( "query,paths,expected", [ @@ -62,3 +68,24 @@ def test_ignores_incorrect_paths( expected: list[tuple[str, FileTypes]], ) -> None: assert extract_files(query, paths=paths) == expected + + +@pytest.mark.parametrize( + "array_format,expected_top_level,expected_nested", + [ + ("brackets", [("files[]", b"a"), ("files[]", b"b")], [("items[][file]", b"a"), ("items[][file]", b"b")]), + ("repeat", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("comma", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("indices", [("files[0]", b"a"), ("files[1]", b"b")], [("items[0][file]", b"a"), ("items[1][file]", b"b")]), + ], +) +def test_array_format_controls_file_field_names( + array_format: ArrayFormat, + expected_top_level: list[tuple[str, FileTypes]], + expected_nested: list[tuple[str, FileTypes]], +) -> None: + top_level = {"files": [b"a", b"b"]} + assert extract_files(top_level, paths=[["files", ""]], array_format=array_format) == expected_top_level + + nested = {"items": [{"file": b"a"}, {"file": b"b"}]} + assert extract_files(nested, paths=[["items", "", "file"]], array_format=array_format) == expected_nested diff --git a/tests/test_files.py b/tests/test_files.py index 15d5c6a811..56445fb550 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -4,7 +4,8 @@ import pytest from dirty_equals import IsDict, IsList, IsBytes, IsTuple -from openai._files import to_httpx_files, async_to_httpx_files +from openai._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files +from openai._utils import extract_files readme_path = Path(__file__).parent.parent.joinpath("README.md") @@ -49,3 +50,99 @@ def test_string_not_allowed() -> None: "file": "foo", # type: ignore } ) + + +def assert_different_identities(obj1: object, obj2: object) -> None: + assert obj1 == obj2 + assert obj1 is not obj2 + + +class TestDeepcopyWithPaths: + def test_copies_top_level_dict(self) -> None: + original = {"file": b"data", "other": "value"} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + + def test_file_value_is_same_reference(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + assert result["file"] is file_bytes + + def test_list_popped_wholesale(self) -> None: + files = [b"f1", b"f2"] + original = {"files": files, "title": "t"} + result = deepcopy_with_paths(original, [["files", ""]]) + assert_different_identities(result, original) + result_files = result["files"] + assert isinstance(result_files, list) + assert_different_identities(result_files, files) + + def test_nested_array_path_copies_list_and_elements(self) -> None: + elem1 = {"file": b"f1", "extra": 1} + elem2 = {"file": b"f2", "extra": 2} + original = {"items": [elem1, elem2]} + result = deepcopy_with_paths(original, [["items", "", "file"]]) + assert_different_identities(result, original) + result_items = result["items"] + assert isinstance(result_items, list) + assert_different_identities(result_items, original["items"]) + assert_different_identities(result_items[0], elem1) + assert_different_identities(result_items[1], elem2) + + def test_empty_paths_returns_same_object(self) -> None: + original = {"foo": "bar"} + result = deepcopy_with_paths(original, []) + assert result is original + + def test_multiple_paths(self) -> None: + f1 = b"file1" + f2 = b"file2" + original = {"a": f1, "b": f2, "c": "unchanged"} + result = deepcopy_with_paths(original, [["a"], ["b"]]) + assert_different_identities(result, original) + assert result["a"] is f1 + assert result["b"] is f2 + assert result["c"] is original["c"] + + def test_extract_files_does_not_mutate_original_top_level(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes, "other": "value"} + + copied = deepcopy_with_paths(original, [["file"]]) + extracted = extract_files(copied, paths=[["file"]]) + + assert extracted == [("file", file_bytes)] + assert original == {"file": file_bytes, "other": "value"} + assert copied == {"other": "value"} + + def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None: + file1 = b"f1" + file2 = b"f2" + original = { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + + copied = deepcopy_with_paths(original, [["items", "", "file"]]) + extracted = extract_files(copied, paths=[["items", "", "file"]]) + + assert [entry for _, entry in extracted] == [file1, file2] + assert original == { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + assert copied == { + "items": [ + {"extra": 1}, + {"extra": 2}, + ], + "title": "example", + } diff --git a/tests/test_legacy_response.py b/tests/test_legacy_response.py index 3c2df53e58..9da1a80659 100644 --- a/tests/test_legacy_response.py +++ b/tests/test_legacy_response.py @@ -11,6 +11,8 @@ from openai._base_client import FinalRequestOptions from openai._legacy_response import LegacyAPIResponse +from .utils import rich_print_str + class PydanticModel(pydantic.BaseModel): ... @@ -32,6 +34,31 @@ def test_response_parse_mismatched_basemodel(client: OpenAI) -> None: response.parse(to=PydanticModel) +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +def test_response_parse_bool(client: OpenAI, content: str, expected: bool) -> None: + response = LegacyAPIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = response.parse(to=bool) + assert result is expected + + def test_response_parse_custom_stream(client: OpenAI) -> None: response = LegacyAPIResponse( raw=httpx.Response(200, content=b"foo"), @@ -66,6 +93,29 @@ def test_response_parse_custom_model(client: OpenAI) -> None: assert obj.bar == 2 +def test_response_basemodel_request_id(client: OpenAI) -> None: + response = LegacyAPIResponse( + raw=httpx.Response( + 200, + headers={"x-request-id": "my-req-id"}, + content=json.dumps({"foo": "hello!", "bar": 2}), + ), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse(to=CustomModel) + assert obj._request_id == "my-req-id" + assert obj.foo == "hello!" + assert obj.bar == 2 + assert obj.to_dict() == {"foo": "hello!", "bar": 2} + assert "_request_id" not in rich_print_str(obj) + assert "__exclude_fields__" not in rich_print_str(obj) + + def test_response_parse_annotated_type(client: OpenAI) -> None: response = LegacyAPIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), diff --git a/tests/test_models.py b/tests/test_models.py index b703444248..cc204bac1d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,15 +1,16 @@ import json -from typing import Any, Dict, List, Union, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Iterable, Optional, cast from datetime import datetime, timezone -from typing_extensions import Literal, Annotated +from collections import deque +from typing_extensions import Literal, Annotated, TypedDict, TypeAliasType import pytest import pydantic from pydantic import Field from openai._utils import PropertyInfo -from openai._compat import PYDANTIC_V2, parse_obj, model_dump, model_json -from openai._models import BaseModel, construct_type +from openai._compat import PYDANTIC_V1, parse_obj, model_dump, model_json +from openai._models import DISCRIMINATOR_CACHE, BaseModel, EagerIterable, construct_type class BasicModel(BaseModel): @@ -245,7 +246,7 @@ class Model(BaseModel): assert m.foo is True m = Model.construct(foo="CARD_HOLDER") - assert m.foo is "CARD_HOLDER" + assert m.foo == "CARD_HOLDER" m = Model.construct(foo={"bar": False}) assert isinstance(m.foo, Submodel1) @@ -294,12 +295,12 @@ class Model(BaseModel): assert cast(bool, m.foo) is True m = Model.construct(foo={"name": 3}) - if PYDANTIC_V2: - assert isinstance(m.foo, Submodel1) - assert m.foo.name == 3 # type: ignore - else: + if PYDANTIC_V1: assert isinstance(m.foo, Submodel2) assert m.foo.name == "3" + else: + assert isinstance(m.foo, Submodel1) + assert m.foo.name == 3 # type: ignore def test_list_of_unions() -> None: @@ -426,10 +427,10 @@ class Model(BaseModel): expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc) - if PYDANTIC_V2: - expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' - else: + if PYDANTIC_V1: expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}' + else: + expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' model = Model.construct(created_at="2019-12-27T18:11:19.117Z") assert model.created_at == expected @@ -492,12 +493,15 @@ class Model(BaseModel): resource_id: Optional[str] = None m = Model.construct() + assert m.resource_id is None assert "resource_id" not in m.model_fields_set m = Model.construct(resource_id=None) + assert m.resource_id is None assert "resource_id" in m.model_fields_set m = Model.construct(resource_id="foo") + assert m.resource_id == "foo" assert "resource_id" in m.model_fields_set @@ -520,19 +524,15 @@ class Model(BaseModel): assert m3.to_dict(exclude_none=True) == {} assert m3.to_dict(exclude_defaults=True) == {} - if PYDANTIC_V2: - - class Model2(BaseModel): - created_at: datetime + class Model2(BaseModel): + created_at: datetime - time_str = "2024-03-21T11:39:01.275859" - m4 = Model2.construct(created_at=time_str) - assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} - assert m4.to_dict(mode="json") == {"created_at": time_str} - else: - with pytest.raises(ValueError, match="mode is only supported in Pydantic v2"): - m.to_dict(mode="json") + time_str = "2024-03-21T11:39:01.275859" + m4 = Model2.construct(created_at=time_str) + assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} + assert m4.to_dict(mode="json") == {"created_at": time_str} + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_dict(warnings=False) @@ -557,10 +557,7 @@ class Model(BaseModel): assert m3.model_dump() == {"foo": None} assert m3.model_dump(exclude_none=True) == {} - if not PYDANTIC_V2: - with pytest.raises(ValueError, match="mode is only supported in Pydantic v2"): - m.model_dump(mode="json") - + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump(round_trip=True) @@ -568,6 +565,14 @@ class Model(BaseModel): m.model_dump(warnings=False) +def test_compat_method_no_error_for_warnings() -> None: + class Model(BaseModel): + foo: Optional[str] + + m = Model(foo="hello") + assert isinstance(model_dump(m, warnings=False), dict) + + def test_to_json() -> None: class Model(BaseModel): foo: Optional[str] = Field(alias="FOO", default=None) @@ -576,10 +581,10 @@ class Model(BaseModel): assert json.loads(m.to_json()) == {"FOO": "hello"} assert json.loads(m.to_json(use_api_names=False)) == {"foo": "hello"} - if PYDANTIC_V2: - assert m.to_json(indent=None) == '{"FOO":"hello"}' - else: + if PYDANTIC_V1: assert m.to_json(indent=None) == '{"FOO": "hello"}' + else: + assert m.to_json(indent=None) == '{"FOO":"hello"}' m2 = Model() assert json.loads(m2.to_json()) == {} @@ -591,7 +596,7 @@ class Model(BaseModel): assert json.loads(m3.to_json()) == {"FOO": None} assert json.loads(m3.to_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_json(warnings=False) @@ -618,7 +623,7 @@ class Model(BaseModel): assert json.loads(m3.model_dump_json()) == {"foo": None} assert json.loads(m3.model_dump_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump_json(round_trip=True) @@ -675,12 +680,12 @@ class B(BaseModel): ) assert isinstance(m, A) assert m.type == "a" - if PYDANTIC_V2: - assert m.data == 100 # type: ignore[comparison-overlap] - else: + if PYDANTIC_V1: # pydantic v1 automatically converts inputs to strings # if the expected type is a str assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] def test_discriminated_unions_unknown_variant() -> None: @@ -764,12 +769,12 @@ class B(BaseModel): ) assert isinstance(m, A) assert m.foo_type == "a" - if PYDANTIC_V2: - assert m.data == 100 # type: ignore[comparison-overlap] - else: + if PYDANTIC_V1: # pydantic v1 automatically converts inputs to strings # if the expected type is a str assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None: @@ -805,7 +810,7 @@ class B(BaseModel): UnionType = cast(Any, Union[A, B]) - assert not hasattr(UnionType, "__discriminator__") + assert not DISCRIMINATOR_CACHE.get(UnionType) m = construct_type( value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) @@ -814,7 +819,7 @@ class B(BaseModel): assert m.type == "b" assert m.data == "foo" # type: ignore[comparison-overlap] - discriminator = UnionType.__discriminator__ + discriminator = DISCRIMINATOR_CACHE.get(UnionType) assert discriminator is not None m = construct_type( @@ -826,4 +831,187 @@ class B(BaseModel): # if the discriminator details object stays the same between invocations then # we hit the cache - assert UnionType.__discriminator__ is discriminator + assert DISCRIMINATOR_CACHE.get(UnionType) is discriminator + + +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") +def test_type_alias_type() -> None: + Alias = TypeAliasType("Alias", str) # pyright: ignore + + class Model(BaseModel): + alias: Alias + union: Union[int, Alias] + + m = construct_type(value={"alias": "foo", "union": "bar"}, type_=Model) + assert isinstance(m, Model) + assert isinstance(m.alias, str) + assert m.alias == "foo" + assert isinstance(m.union, str) + assert m.union == "bar" + + +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") +def test_field_named_cls() -> None: + class Model(BaseModel): + cls: str + + m = construct_type(value={"cls": "foo"}, type_=Model) + assert isinstance(m, Model) + assert isinstance(m.cls, str) + + +def test_discriminated_union_case() -> None: + class A(BaseModel): + type: Literal["a"] + + data: bool + + class B(BaseModel): + type: Literal["b"] + + data: List[Union[A, object]] + + class ModelA(BaseModel): + type: Literal["modelA"] + + data: int + + class ModelB(BaseModel): + type: Literal["modelB"] + + required: str + + data: Union[A, B] + + # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required` + m = construct_type( + value={"type": "modelB", "data": {"type": "a", "data": True}}, + type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]), + ) + + assert isinstance(m, ModelB) + + +def test_nested_discriminated_union() -> None: + class InnerType1(BaseModel): + type: Literal["type_1"] + + class InnerModel(BaseModel): + inner_value: str + + class InnerType2(BaseModel): + type: Literal["type_2"] + some_inner_model: InnerModel + + class Type1(BaseModel): + base_type: Literal["base_type_1"] + value: Annotated[ + Union[ + InnerType1, + InnerType2, + ], + PropertyInfo(discriminator="type"), + ] + + class Type2(BaseModel): + base_type: Literal["base_type_2"] + + T = Annotated[ + Union[ + Type1, + Type2, + ], + PropertyInfo(discriminator="base_type"), + ] + + model = construct_type( + type_=T, + value={ + "base_type": "base_type_1", + "value": { + "type": "type_2", + }, + }, + ) + assert isinstance(model, Type1) + assert isinstance(model.value, InnerType2) + + +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2 for now") +def test_extra_properties() -> None: + class Item(BaseModel): + prop: int + + class Model(BaseModel): + __pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + other: str + + if TYPE_CHECKING: + + def __getattr__(self, attr: str) -> Item: ... + + model = construct_type( + type_=Model, + value={ + "a": {"prop": 1}, + "other": "foo", + }, + ) + assert isinstance(model, Model) + assert model.a.prop == 1 + assert isinstance(model.a, Item) + assert model.other == "foo" + + +# NOTE: Workaround for Pydantic Iterable behavior. +# Iterable fields are replaced with a ValidatorIterator and may be consumed +# during serialization, which can cause subsequent dumps to return empty data. +# See: https://github.com/pydantic/pydantic/issues/9541 +@pytest.mark.parametrize( + "data, expected_validated", + [ + ([1, 2, 3], [1, 2, 3]), + ((1, 2, 3), (1, 2, 3)), + (set([1, 2, 3]), set([1, 2, 3])), + (iter([1, 2, 3]), [1, 2, 3]), + ([], []), + ((x for x in [1, 2, 3]), [1, 2, 3]), + (map(lambda x: x, [1, 2, 3]), [1, 2, 3]), + (frozenset([1, 2, 3]), frozenset([1, 2, 3])), + (deque([1, 2, 3]), deque([1, 2, 3])), + ], + ids=["list", "tuple", "set", "iterator", "empty", "generator", "map", "frozenset", "deque"], +) +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2") +def test_iterable_construction(data: Iterable[int], expected_validated: Iterable[int]) -> None: + class TypeWithIterable(TypedDict): + items: EagerIterable[int] + + class Model(BaseModel): + data: TypeWithIterable + + m = Model.model_validate({"data": {"items": data}}) + assert m.data["items"] == expected_validated + + # Verify repeated dumps don't lose data (the original bug) + assert m.model_dump()["data"]["items"] == list(expected_validated) + assert m.model_dump()["data"]["items"] == list(expected_validated) + + +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2") +def test_iterable_construction_str_falls_back_to_list() -> None: + # str is iterable (over chars), but str(list_of_chars) produces the list's repr + # rather than reconstructing a string from items. We special-case str to fall + # back to list instead of attempting reconstruction. + class TypeWithIterable(TypedDict): + items: EagerIterable[str] + + class Model(BaseModel): + data: TypeWithIterable + + m = Model.model_validate({"data": {"items": "hello"}}) + + # falls back to list of chars rather than calling str(["h", "e", "l", "l", "o"]) + assert m.data["items"] == ["h", "e", "l", "l", "o"] + assert m.model_dump()["data"]["items"] == ["h", "e", "l", "l", "o"] diff --git a/tests/test_module_client.py b/tests/test_module_client.py index 6bab33a1d7..23bcb61716 100644 --- a/tests/test_module_client.py +++ b/tests/test_module_client.py @@ -14,9 +14,11 @@ def reset_state() -> None: openai._reset_client() - openai.api_key = None or "My API Key" + openai.api_key = None + openai.admin_api_key = None openai.organization = None openai.project = None + openai.webhook_secret = None openai.base_url = None openai.timeout = DEFAULT_TIMEOUT openai.max_retries = DEFAULT_MAX_RETRIES @@ -28,6 +30,8 @@ def reset_state() -> None: openai.azure_endpoint = None openai.azure_ad_token = None openai.azure_ad_token_provider = None + openai._bedrock_api_key = None + openai.bedrock_token_provider = None @pytest.fixture(autouse=True) @@ -100,6 +104,7 @@ def test_http_client_option() -> None: from typing import Iterator from openai.lib.azure import AzureOpenAI +from openai.lib.bedrock import BedrockOpenAI @contextlib.contextmanager @@ -182,3 +187,79 @@ def test_azure_azure_ad_token_provider_version_and_endpoint_env() -> None: assert isinstance(client, AzureOpenAI) assert client._azure_ad_token_provider is not None assert client._azure_ad_token_provider() == "token" + + +def test_bedrock_token_and_region_env() -> None: + with fresh_env(): + openai.api_type = "amazon-bedrock" + _os.environ["AWS_BEARER_TOKEN_BEDROCK"] = "example Bedrock token" + _os.environ["AWS_REGION"] = "us-west-2" + + client = openai.responses._client + assert isinstance(client, BedrockOpenAI) + assert client.base_url == URL("https://bedrock-mantle.us-west-2.api.aws/openai/v1/") + + +def test_bedrock_api_type_env() -> None: + with fresh_env(): + _os.environ["OPENAI_API_TYPE"] = "amazon-bedrock" + _os.environ["AWS_BEARER_TOKEN_BEDROCK"] = "example Bedrock token" + _os.environ["AWS_REGION"] = "us-west-2" + reset_state() + + client = openai.responses._client + assert isinstance(client, BedrockOpenAI) + assert openai.api_type == "amazon-bedrock" + + +def test_bedrock_api_type_uses_bedrock_credentials() -> None: + with fresh_env(): + openai.api_type = "amazon-bedrock" + _os.environ["OPENAI_API_KEY"] = "openai api key" + _os.environ["AWS_BEARER_TOKEN_BEDROCK"] = "example Bedrock token" + _os.environ["AWS_REGION"] = "us-west-2" + + client = openai.responses._client + assert isinstance(client, BedrockOpenAI) + assert client.api_key == "example Bedrock token" + assert openai.api_key is None + + +def test_bedrock_api_type_uses_explicit_module_api_key() -> None: + with fresh_env(): + openai.api_type = "amazon-bedrock" + openai.api_key = "explicit Bedrock token" + _os.environ["AWS_BEARER_TOKEN_BEDROCK"] = "env Bedrock token" + _os.environ["AWS_REGION"] = "us-west-2" + + client = openai.responses._client + assert isinstance(client, BedrockOpenAI) + assert client.api_key == "explicit Bedrock token" + assert openai.api_key == "explicit Bedrock token" + + +def test_bedrock_module_api_key_overrides_cached_env_token_after_load() -> None: + with fresh_env(): + openai.api_type = "amazon-bedrock" + _os.environ["AWS_BEARER_TOKEN_BEDROCK"] = "env Bedrock token" + _os.environ["AWS_REGION"] = "us-west-2" + + client = openai.responses._client + assert isinstance(client, BedrockOpenAI) + assert client.api_key == "env Bedrock token" + + openai.api_key = "new Bedrock token" + + assert client.api_key == "new Bedrock token" + + +def test_bedrock_api_type_uses_token_provider_without_mutating_module_api_key() -> None: + with fresh_env(): + openai.api_type = "amazon-bedrock" + openai.bedrock_token_provider = lambda: "provider Bedrock token" + _os.environ["AWS_REGION"] = "us-west-2" + + client = openai.responses._client + assert isinstance(client, BedrockOpenAI) + assert client._refresh_api_key() == "provider Bedrock token" + assert openai.api_key is None diff --git a/tests/test_response.py b/tests/test_response.py index b7d88bdbde..43f24c150d 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -18,6 +18,8 @@ from openai._streaming import Stream from openai._base_client import FinalRequestOptions +from .utils import rich_print_str + class ConcreteBaseAPIResponse(APIResponse[bytes]): ... @@ -156,6 +158,51 @@ async def test_async_response_parse_custom_model(async_client: AsyncOpenAI) -> N assert obj.bar == 2 +def test_response_basemodel_request_id(client: OpenAI) -> None: + response = APIResponse( + raw=httpx.Response( + 200, + headers={"x-request-id": "my-req-id"}, + content=json.dumps({"foo": "hello!", "bar": 2}), + ), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse(to=CustomModel) + assert obj._request_id == "my-req-id" + assert obj.foo == "hello!" + assert obj.bar == 2 + assert obj.to_dict() == {"foo": "hello!", "bar": 2} + assert "_request_id" not in rich_print_str(obj) + assert "__exclude_fields__" not in rich_print_str(obj) + + +@pytest.mark.asyncio +async def test_async_response_basemodel_request_id(client: OpenAI) -> None: + response = AsyncAPIResponse( + raw=httpx.Response( + 200, + headers={"x-request-id": "my-req-id"}, + content=json.dumps({"foo": "hello!", "bar": 2}), + ), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = await response.parse(to=CustomModel) + assert obj._request_id == "my-req-id" + assert obj.foo == "hello!" + assert obj.bar == 2 + assert obj.to_dict() == {"foo": "hello!", "bar": 2} + + def test_response_parse_annotated_type(client: OpenAI) -> None: response = APIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), @@ -190,6 +237,56 @@ async def test_async_response_parse_annotated_type(async_client: AsyncOpenAI) -> assert obj.bar == 2 +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +def test_response_parse_bool(client: OpenAI, content: str, expected: bool) -> None: + response = APIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = response.parse(to=bool) + assert result is expected + + +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +async def test_async_response_parse_bool(client: AsyncOpenAI, content: str, expected: bool) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = await response.parse(to=bool) + assert result is expected + + class OtherModel(BaseModel): a: str diff --git a/tests/test_send_queue.py b/tests/test_send_queue.py new file mode 100644 index 0000000000..61db916bc4 --- /dev/null +++ b/tests/test_send_queue.py @@ -0,0 +1,128 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import pytest + +from openai._exceptions import WebSocketQueueFullError +from openai._send_queue import SendQueue + + +class TestSendQueue: + def test_enqueue_and_drain(self) -> None: + q = SendQueue() + q.enqueue('{"type": "session.update"}') + q.enqueue('{"type": "response.create"}') + assert len(q) == 2 + + items = q.drain() + assert items == ['{"type": "session.update"}', '{"type": "response.create"}'] + assert len(q) == 0 + + def test_enqueue_respects_byte_limit(self) -> None: + q = SendQueue(max_bytes=10) + q.enqueue("12345") # 5 bytes, fits + with pytest.raises(WebSocketQueueFullError): + q.enqueue("123456") # 6 bytes, would exceed 10 + assert len(q) == 1 + + def test_drain_empties_queue(self) -> None: + q = SendQueue() + q.enqueue("hello") + q.drain() + assert len(q) == 0 + assert not q + + def test_bool(self) -> None: + q = SendQueue() + assert not q + q.enqueue("x") + assert q + + def test_flush_sync(self) -> None: + q = SendQueue() + q.enqueue("a") + q.enqueue("b") + q.enqueue("c") + + sent: list[str] = [] + q.flush_sync(sent.append) + assert sent == ["a", "b", "c"] + assert len(q) == 0 + + def test_flush_sync_requeues_on_failure(self) -> None: + q = SendQueue() + q.enqueue("a") + q.enqueue("b") + q.enqueue("c") + + sent: list[str] = [] + + def failing_send(data: str) -> None: + if data == "b": + raise RuntimeError("send failed") + sent.append(data) + + with pytest.raises(RuntimeError, match="send failed"): + q.flush_sync(failing_send) + + assert sent == ["a"] + # b and c should be re-queued + remaining = q.drain() + assert remaining == ["b", "c"] + + @pytest.mark.asyncio + async def test_flush_async(self) -> None: + q = SendQueue() + q.enqueue("a") + q.enqueue("b") + + sent: list[str] = [] + + async def async_send(data: str) -> None: + sent.append(data) + + await q.flush_async(async_send) + assert sent == ["a", "b"] + assert len(q) == 0 + + @pytest.mark.asyncio + async def test_flush_async_requeues_on_failure(self) -> None: + q = SendQueue() + q.enqueue("a") + q.enqueue("b") + q.enqueue("c") + + sent: list[str] = [] + + async def failing_send(data: str) -> None: + if data == "b": + raise RuntimeError("send failed") + sent.append(data) + + with pytest.raises(RuntimeError, match="send failed"): + await q.flush_async(failing_send) + + assert sent == ["a"] + remaining = q.drain() + assert remaining == ["b", "c"] + + def test_flush_sync_preserves_new_items_on_failure(self) -> None: + """If items are enqueued after flush starts and flush fails, + the re-queued items should come before the new items.""" + q = SendQueue() + q.enqueue("a") + q.enqueue("b") + + def failing_send(data: str) -> None: + if data == "b": + # Simulate another thread enqueuing during flush + q.enqueue("new") + raise RuntimeError("fail") + + with pytest.raises(RuntimeError): + q.flush_sync(failing_send) + + # "b" (failed) should come before "new" (added during flush) + remaining = q.drain() + assert remaining == ["b", "new"] diff --git a/tests/test_transform.py b/tests/test_transform.py index 1eb6cde9d6..bece75dfc7 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -2,20 +2,20 @@ import io import pathlib -from typing import Any, List, Union, TypeVar, Iterable, Optional, cast +from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast from datetime import date, datetime from typing_extensions import Required, Annotated, TypedDict import pytest -from openai._types import Base64FileInput +from openai._types import Base64FileInput, omit, not_given from openai._utils import ( PropertyInfo, transform as _transform, parse_datetime, async_transform as _async_transform, ) -from openai._compat import PYDANTIC_V2 +from openai._compat import PYDANTIC_V1 from openai._models import BaseModel _T = TypeVar("_T") @@ -177,17 +177,32 @@ class DateDict(TypedDict, total=False): foo: Annotated[date, PropertyInfo(format="iso8601")] +class DatetimeModel(BaseModel): + foo: datetime + + +class DateModel(BaseModel): + foo: Optional[date] + + @parametrize @pytest.mark.asyncio async def test_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") + tz = "+00:00" if PYDANTIC_V1 else "Z" assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] + assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz} # type: ignore[comparison-overlap] dt = dt.replace(tzinfo=None) assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] + assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] assert await transform({"foo": None}, DateDict, use_async) == {"foo": None} # type: ignore[comparison-overlap] + assert await transform(DateModel(foo=None), Any, use_async) == {"foo": None} # type: ignore assert await transform({"foo": date.fromisoformat("2023-02-23")}, DateDict, use_async) == {"foo": "2023-02-23"} # type: ignore[comparison-overlap] + assert await transform(DateModel(foo=date.fromisoformat("2023-02-23")), DateDict, use_async) == { + "foo": "2023-02-23" + } # type: ignore[comparison-overlap] @parametrize @@ -282,11 +297,11 @@ async def test_pydantic_unknown_field(use_async: bool) -> None: @pytest.mark.asyncio async def test_pydantic_mismatched_types(use_async: bool) -> None: model = MyModel.construct(foo=True) - if PYDANTIC_V2: + if PYDANTIC_V1: + params = await transform(model, Any, use_async) + else: with pytest.warns(UserWarning): params = await transform(model, Any, use_async) - else: - params = await transform(model, Any, use_async) assert cast(Any, params) == {"foo": True} @@ -294,11 +309,11 @@ async def test_pydantic_mismatched_types(use_async: bool) -> None: @pytest.mark.asyncio async def test_pydantic_mismatched_object_type(use_async: bool) -> None: model = MyModel.construct(foo=MyModel.construct(hello="world")) - if PYDANTIC_V2: + if PYDANTIC_V1: + params = await transform(model, Any, use_async) + else: with pytest.warns(UserWarning): params = await transform(model, Any, use_async) - else: - params = await transform(model, Any, use_async) assert cast(Any, params) == {"foo": {"hello": "world"}} @@ -373,6 +388,15 @@ def my_iter() -> Iterable[Baz8]: } +@parametrize +@pytest.mark.asyncio +async def test_dictionary_items(use_async: bool) -> None: + class DictItems(TypedDict): + foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] + + assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}} + + class TypedDictIterableUnionStr(TypedDict): foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")] @@ -408,3 +432,29 @@ async def test_base64_file_input(use_async: bool) -> None: assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == { "foo": "SGVsbG8sIHdvcmxkIQ==" } # type: ignore[comparison-overlap] + + +@parametrize +@pytest.mark.asyncio +async def test_transform_skipping(use_async: bool) -> None: + # lists of ints are left as-is + data = [1, 2, 3] + assert await transform(data, List[int], use_async) is data + + # iterables of ints are converted to a list + data = iter([1, 2, 3]) + assert await transform(data, Iterable[int], use_async) == [1, 2, 3] + + +@parametrize +@pytest.mark.asyncio +async def test_strips_notgiven(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": not_given}, Foo1, use_async) == {} + + +@parametrize +@pytest.mark.asyncio +async def test_strips_omit(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": omit}, Foo1, use_async) == {} diff --git a/tests/test_utils/test_datetime_parse.py b/tests/test_utils/test_datetime_parse.py new file mode 100644 index 0000000000..44c33a4ccb --- /dev/null +++ b/tests/test_utils/test_datetime_parse.py @@ -0,0 +1,110 @@ +""" +Copied from https://github.com/pydantic/pydantic/blob/v1.10.22/tests/test_datetime_parse.py +with modifications so it works without pydantic v1 imports. +""" + +from typing import Type, Union +from datetime import date, datetime, timezone, timedelta + +import pytest + +from openai._utils import parse_date, parse_datetime + + +def create_tz(minutes: int) -> timezone: + return timezone(timedelta(minutes=minutes)) + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + ("1494012444.883309", date(2017, 5, 5)), + (b"1494012444.883309", date(2017, 5, 5)), + (1_494_012_444.883_309, date(2017, 5, 5)), + ("1494012444", date(2017, 5, 5)), + (1_494_012_444, date(2017, 5, 5)), + (0, date(1970, 1, 1)), + ("2012-04-23", date(2012, 4, 23)), + (b"2012-04-23", date(2012, 4, 23)), + ("2012-4-9", date(2012, 4, 9)), + (date(2012, 4, 9), date(2012, 4, 9)), + (datetime(2012, 4, 9, 12, 15), date(2012, 4, 9)), + # Invalid inputs + ("x20120423", ValueError), + ("2012-04-56", ValueError), + (19_999_999_999, date(2603, 10, 11)), # just before watershed + (20_000_000_001, date(1970, 8, 20)), # just after watershed + (1_549_316_052, date(2019, 2, 4)), # nowish in s + (1_549_316_052_104, date(2019, 2, 4)), # nowish in ms + (1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs + (1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns + ("infinity", date(9999, 12, 31)), + ("inf", date(9999, 12, 31)), + (float("inf"), date(9999, 12, 31)), + ("infinity ", date(9999, 12, 31)), + (int("1" + "0" * 100), date(9999, 12, 31)), + (1e1000, date(9999, 12, 31)), + ("-infinity", date(1, 1, 1)), + ("-inf", date(1, 1, 1)), + ("nan", ValueError), + ], +) +def test_date_parsing(value: Union[str, bytes, int, float], result: Union[date, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_date(value) + else: + assert parse_date(value) == result + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + # values in seconds + ("1494012444.883309", datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + (1_494_012_444.883_309, datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + ("1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (b"1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (1_494_012_444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + # values in ms + ("1494012444000.883309", datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)), + ("-1494012444000.883309", datetime(1922, 8, 29, 4, 32, 35, 999117, tzinfo=timezone.utc)), + (1_494_012_444_000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + ("2012-04-23T09:15:00", datetime(2012, 4, 23, 9, 15)), + ("2012-4-9 4:8:16", datetime(2012, 4, 9, 4, 8, 16)), + ("2012-04-23T09:15:00Z", datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)), + ("2012-4-9 4:8:16-0320", datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))), + ("2012-04-23T10:20:30.400+02:30", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(150))), + ("2012-04-23T10:20:30.400+02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(120))), + ("2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (b"2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (datetime(2017, 5, 5), datetime(2017, 5, 5)), + (0, datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)), + # Invalid inputs + ("x20120423091500", ValueError), + ("2012-04-56T09:15:90", ValueError), + ("2012-04-23T11:05:00-25:00", ValueError), + (19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)), # just before watershed + (20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)), # just after watershed + (1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)), # nowish in s + (1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms + (1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs + (1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns + ("infinity", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf ", datetime(9999, 12, 31, 23, 59, 59, 999999)), + (1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)), + (float("inf"), datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("-infinity", datetime(1, 1, 1, 0, 0)), + ("-inf", datetime(1, 1, 1, 0, 0)), + ("nan", ValueError), + ], +) +def test_datetime_parsing(value: Union[str, bytes, int, float], result: Union[datetime, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_datetime(value) + else: + assert parse_datetime(value) == result diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py new file mode 100644 index 0000000000..240c64562f --- /dev/null +++ b/tests/test_utils/test_json.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import datetime +from typing import Union + +import pydantic + +from openai import _compat +from openai._utils._json import openapi_dumps + + +class TestOpenapiDumps: + def test_basic(self) -> None: + data = {"key": "value", "number": 42} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"key":"value","number":42}' + + def test_datetime_serialization(self) -> None: + dt = datetime.datetime(2023, 1, 1, 12, 0, 0) + data = {"datetime": dt} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}' + + def test_pydantic_model_serialization(self) -> None: + class User(pydantic.BaseModel): + first_name: str + last_name: str + age: int + + model_instance = User(first_name="John", last_name="Kramer", age=83) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}' + + def test_pydantic_model_with_default_values(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + score: int = 0 + + model_instance = User(name="Alice") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Alice"}}' + + def test_pydantic_model_with_default_values_overridden(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + + model_instance = User(name="Bob", role="admin", active=False) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}' + + def test_pydantic_model_with_alias(self) -> None: + class User(pydantic.BaseModel): + first_name: str = pydantic.Field(alias="firstName") + last_name: str = pydantic.Field(alias="lastName") + + model_instance = User(firstName="John", lastName="Doe") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}' + + def test_pydantic_model_with_alias_and_default(self) -> None: + class User(pydantic.BaseModel): + user_name: str = pydantic.Field(alias="userName") + user_role: str = pydantic.Field(default="member", alias="userRole") + is_active: bool = pydantic.Field(default=True, alias="isActive") + + model_instance = User(userName="charlie") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"charlie"}}' + + model_with_overrides = User(userName="diana", userRole="admin", isActive=False) + data = {"model": model_with_overrides} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}' + + def test_pydantic_model_with_nested_models_and_defaults(self) -> None: + class Address(pydantic.BaseModel): + street: str + city: str = "Unknown" + + class User(pydantic.BaseModel): + name: str + address: Address + verified: bool = False + + if _compat.PYDANTIC_V1: + # to handle forward references in Pydantic v1 + User.update_forward_refs(**locals()) # type: ignore[reportDeprecated] + + address = Address(street="123 Main St") + user = User(name="Diana", address=address) + data = {"user": user} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}' + + address_with_city = Address(street="456 Oak Ave", city="Boston") + user_verified = User(name="Eve", address=address_with_city, verified=True) + data = {"user": user_verified} + json_bytes = openapi_dumps(data) + assert ( + json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}' + ) + + def test_pydantic_model_with_optional_fields(self) -> None: + class User(pydantic.BaseModel): + name: str + email: Union[str, None] + phone: Union[str, None] + + model_with_none = User(name="Eve", email=None, phone=None) + data = {"model": model_with_none} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}' + + model_with_values = User(name="Frank", email="frank@example.com", phone=None) + data = {"model": model_with_values} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}' diff --git a/tests/test_utils/test_logging.py b/tests/test_utils/test_logging.py new file mode 100644 index 0000000000..cc018012e2 --- /dev/null +++ b/tests/test_utils/test_logging.py @@ -0,0 +1,100 @@ +import logging +from typing import Any, Dict, cast + +import pytest + +from openai._utils import SensitiveHeadersFilter + + +@pytest.fixture +def logger_with_filter() -> logging.Logger: + logger = logging.getLogger("test_logger") + logger.setLevel(logging.DEBUG) + logger.addFilter(SensitiveHeadersFilter()) + return logger + + +def test_keys_redacted(logger_with_filter: logging.Logger, caplog: pytest.LogCaptureFixture) -> None: + with caplog.at_level(logging.DEBUG): + logger_with_filter.debug( + "Request options: %s", + { + "method": "post", + "url": "chat/completions", + "headers": {"api-key": "12345", "Authorization": "Bearer token"}, + }, + ) + + log_record = cast(Dict[str, Any], caplog.records[0].args) + assert log_record["method"] == "post" + assert log_record["url"] == "chat/completions" + assert log_record["headers"]["api-key"] == "" + assert log_record["headers"]["Authorization"] == "" + assert ( + caplog.messages[0] + == "Request options: {'method': 'post', 'url': 'chat/completions', 'headers': {'api-key': '', 'Authorization': ''}}" + ) + + +def test_keys_redacted_case_insensitive(logger_with_filter: logging.Logger, caplog: pytest.LogCaptureFixture) -> None: + with caplog.at_level(logging.DEBUG): + logger_with_filter.debug( + "Request options: %s", + { + "method": "post", + "url": "chat/completions", + "headers": {"Api-key": "12345", "authorization": "Bearer token"}, + }, + ) + + log_record = cast(Dict[str, Any], caplog.records[0].args) + assert log_record["method"] == "post" + assert log_record["url"] == "chat/completions" + assert log_record["headers"]["Api-key"] == "" + assert log_record["headers"]["authorization"] == "" + assert ( + caplog.messages[0] + == "Request options: {'method': 'post', 'url': 'chat/completions', 'headers': {'Api-key': '', 'authorization': ''}}" + ) + + +def test_no_headers(logger_with_filter: logging.Logger, caplog: pytest.LogCaptureFixture) -> None: + with caplog.at_level(logging.DEBUG): + logger_with_filter.debug( + "Request options: %s", + {"method": "post", "url": "chat/completions"}, + ) + + log_record = cast(Dict[str, Any], caplog.records[0].args) + assert log_record["method"] == "post" + assert log_record["url"] == "chat/completions" + assert "api-key" not in log_record + assert "Authorization" not in log_record + assert caplog.messages[0] == "Request options: {'method': 'post', 'url': 'chat/completions'}" + + +def test_headers_without_sensitive_info(logger_with_filter: logging.Logger, caplog: pytest.LogCaptureFixture) -> None: + with caplog.at_level(logging.DEBUG): + logger_with_filter.debug( + "Request options: %s", + { + "method": "post", + "url": "chat/completions", + "headers": {"custom": "value"}, + }, + ) + + log_record = cast(Dict[str, Any], caplog.records[0].args) + assert log_record["method"] == "post" + assert log_record["url"] == "chat/completions" + assert log_record["headers"] == {"custom": "value"} + assert ( + caplog.messages[0] + == "Request options: {'method': 'post', 'url': 'chat/completions', 'headers': {'custom': 'value'}}" + ) + + +def test_standard_debug_msg(logger_with_filter: logging.Logger, caplog: pytest.LogCaptureFixture) -> None: + with caplog.at_level(logging.DEBUG): + logger_with_filter.debug("Sending HTTP Request: %s %s", "POST", "chat/completions") + assert caplog.messages[0] == "Sending HTTP Request: POST chat/completions" diff --git a/tests/test_utils/test_path.py b/tests/test_utils/test_path.py new file mode 100644 index 0000000000..420cd19973 --- /dev/null +++ b/tests/test_utils/test_path.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from typing import Any + +import pytest + +from openai._utils._path import path_template + + +@pytest.mark.parametrize( + "template, kwargs, expected", + [ + ("/v1/{id}", dict(id="abc"), "/v1/abc"), + ("/v1/{a}/{b}", dict(a="x", b="y"), "/v1/x/y"), + ("/v1/{a}{b}/path/{c}?val={d}#{e}", dict(a="x", b="y", c="z", d="u", e="v"), "/v1/xy/path/z?val=u#v"), + ("/{w}/{w}", dict(w="echo"), "/echo/echo"), + ("/v1/static", {}, "/v1/static"), + ("", {}, ""), + ("/v1/?q={n}&count=10", dict(n=42), "/v1/?q=42&count=10"), + ("/v1/{v}", dict(v=None), "/v1/null"), + ("/v1/{v}", dict(v=True), "/v1/true"), + ("/v1/{v}", dict(v=False), "/v1/false"), + ("/v1/{v}", dict(v=".hidden"), "/v1/.hidden"), # dot prefix ok + ("/v1/{v}", dict(v="file.txt"), "/v1/file.txt"), # dot in middle ok + ("/v1/{v}", dict(v="..."), "/v1/..."), # triple dot ok + ("/v1/{a}{b}", dict(a=".", b="txt"), "/v1/.txt"), # dot var combining with adjacent to be ok + ("/items?q={v}#{f}", dict(v=".", f=".."), "/items?q=.#.."), # dots in query/fragment are fine + ( + "/v1/{a}?query={b}", + dict(a="../../other/endpoint", b="a&bad=true"), + "/v1/..%2F..%2Fother%2Fendpoint?query=a%26bad%3Dtrue", + ), + ("/v1/{val}", dict(val="a/b/c"), "/v1/a%2Fb%2Fc"), + ("/v1/{val}", dict(val="a/b/c?query=value"), "/v1/a%2Fb%2Fc%3Fquery=value"), + ("/v1/{val}", dict(val="a/b/c?query=value&bad=true"), "/v1/a%2Fb%2Fc%3Fquery=value&bad=true"), + ("/v1/{val}", dict(val="%20"), "/v1/%2520"), # escapes escape sequences in input + # Query: slash and ? are safe, # is not + ("/items?q={v}", dict(v="a/b"), "/items?q=a/b"), + ("/items?q={v}", dict(v="a?b"), "/items?q=a?b"), + ("/items?q={v}", dict(v="a#b"), "/items?q=a%23b"), + ("/items?q={v}", dict(v="a b"), "/items?q=a%20b"), + # Fragment: slash and ? are safe + ("/docs#{v}", dict(v="a/b"), "/docs#a/b"), + ("/docs#{v}", dict(v="a?b"), "/docs#a?b"), + # Path: slash, ? and # are all encoded + ("/v1/{v}", dict(v="a/b"), "/v1/a%2Fb"), + ("/v1/{v}", dict(v="a?b"), "/v1/a%3Fb"), + ("/v1/{v}", dict(v="a#b"), "/v1/a%23b"), + # same var encoded differently by component + ( + "/v1/{v}?q={v}#{v}", + dict(v="a/b?c#d"), + "/v1/a%2Fb%3Fc%23d?q=a/b?c%23d#a/b?c%23d", + ), + ("/v1/{val}", dict(val="x?admin=true"), "/v1/x%3Fadmin=true"), # query injection + ("/v1/{val}", dict(val="x#admin"), "/v1/x%23admin"), # fragment injection + ], +) +def test_interpolation(template: str, kwargs: dict[str, Any], expected: str) -> None: + assert path_template(template, **kwargs) == expected + + +def test_missing_kwarg_raises_key_error() -> None: + with pytest.raises(KeyError, match="org_id"): + path_template("/v1/{org_id}") + + +@pytest.mark.parametrize( + "template, kwargs", + [ + ("{a}/path", dict(a=".")), + ("{a}/path", dict(a="..")), + ("/v1/{a}", dict(a=".")), + ("/v1/{a}", dict(a="..")), + ("/v1/{a}/path", dict(a=".")), + ("/v1/{a}/path", dict(a="..")), + ("/v1/{a}{b}", dict(a=".", b=".")), # adjacent vars → ".." + ("/v1/{a}.", dict(a=".")), # var + static → ".." + ("/v1/{a}{b}", dict(a="", b=".")), # empty + dot → "." + ("/v1/%2e/{x}", dict(x="ok")), # encoded dot in static text + ("/v1/%2e./{x}", dict(x="ok")), # mixed encoded ".." in static + ("/v1/.%2E/{x}", dict(x="ok")), # mixed encoded ".." in static + ("/v1/{v}?q=1", dict(v="..")), + ("/v1/{v}#frag", dict(v="..")), + ], +) +def test_dot_segment_rejected(template: str, kwargs: dict[str, Any]) -> None: + with pytest.raises(ValueError, match="dot-segment"): + path_template(template, **kwargs) diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py index aedd3731ee..2b5ff19dab 100644 --- a/tests/test_utils/test_proxy.py +++ b/tests/test_utils/test_proxy.py @@ -3,6 +3,7 @@ from typing_extensions import override from openai._utils import LazyProxy +from openai._extras._common import MissingDependencyError class RecursiveLazyProxy(LazyProxy[Any]): @@ -21,3 +22,14 @@ def test_recursive_proxy() -> None: assert dir(proxy) == [] assert type(proxy).__name__ == "RecursiveLazyProxy" assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy" + + +def test_isinstance_does_not_error() -> None: + class MissingDepsProxy(LazyProxy[Any]): + @override + def __load__(self) -> Any: + raise MissingDependencyError("Mocking missing dependency") + + proxy = MissingDepsProxy() + assert not isinstance(proxy, dict) + assert isinstance(proxy, LazyProxy) diff --git a/tests/test_websocket_connection_options.py b/tests/test_websocket_connection_options.py new file mode 100644 index 0000000000..122ee10078 --- /dev/null +++ b/tests/test_websocket_connection_options.py @@ -0,0 +1,20 @@ +from openai import types +from openai.types import websocket_connection_options + + +def test_submodule_alias_is_preserved() -> None: + assert ( + websocket_connection_options.WebsocketConnectionOptions + is websocket_connection_options.WebSocketConnectionOptions + ) + + +def test_public_types_alias_is_preserved() -> None: + assert types.WebsocketConnectionOptions is types.WebSocketConnectionOptions + + +def test_beta_realtime_import_still_works_with_old_alias() -> None: + from openai.resources.beta.realtime.realtime import Realtime, AsyncRealtime + + assert Realtime.__name__ == "Realtime" + assert AsyncRealtime.__name__ == "AsyncRealtime" diff --git a/tests/utils.py b/tests/utils.py index 165f4e5bfd..e03ed1a039 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,13 +1,16 @@ from __future__ import annotations +import io import os import inspect import traceback import contextlib -from typing import Any, TypeVar, Iterator, cast +from typing import Any, TypeVar, Iterator, Sequence, ForwardRef, cast from datetime import date, datetime from typing_extensions import Literal, get_args, get_origin, assert_type +import rich + from openai._types import Omit, NoneType from openai._utils import ( is_dict, @@ -15,23 +18,29 @@ is_list_type, is_union_type, extract_type_arg, + is_sequence_type, is_annotated_type, + is_type_alias_type, ) -from openai._compat import PYDANTIC_V2, field_outer_type, get_model_fields +from openai._compat import PYDANTIC_V1, field_outer_type, get_model_fields from openai._models import BaseModel BaseModelT = TypeVar("BaseModelT", bound=BaseModel) +def evaluate_forwardref(forwardref: ForwardRef, globalns: dict[str, Any]) -> type: + return eval(str(forwardref), globalns) # type: ignore[no-any-return] + + def assert_matches_model(model: type[BaseModelT], value: BaseModelT, *, path: list[str]) -> bool: for name, field in get_model_fields(model).items(): field_value = getattr(value, name) - if PYDANTIC_V2: - allow_none = False - else: + if PYDANTIC_V1: # in v1 nullability was structured differently # https://docs.pydantic.dev/2.0/migration/#required-optional-and-nullable-fields allow_none = getattr(field, "allow_none", False) + else: + allow_none = False assert_matches_type( field_outer_type(field), @@ -51,6 +60,9 @@ def assert_matches_type( path: list[str], allow_none: bool = False, ) -> None: + if is_type_alias_type(type_): + type_ = type_.__value__ + # unwrap `Annotated[T, ...]` -> `T` if is_annotated_type(type_): type_ = extract_type_arg(type_, 0) @@ -67,6 +79,13 @@ def assert_matches_type( if is_list_type(type_): return _assert_list_type(type_, value) + if is_sequence_type(type_): + assert isinstance(value, Sequence) + inner_type = get_args(type_)[0] + for entry in value: # type: ignore + assert_type(inner_type, entry) # type: ignore + return + if origin == str: assert isinstance(value, str) elif origin == int: @@ -138,6 +157,16 @@ def _assert_list_type(type_: type[object], value: object) -> None: assert_type(inner_type, entry) # type: ignore +def rich_print_str(obj: object) -> str: + """Like `rich.print()` but returns the string instead""" + buf = io.StringIO() + + console = rich.console.Console(file=buf, width=120) + console.print(obj) + + return buf.getvalue() + + @contextlib.contextmanager def update_env(**new_env: str | Omit) -> Iterator[None]: old = os.environ.copy()