├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── actions │ └── poetry_setup │ │ └── action.yml ├── dependabot.yml └── workflows │ ├── _integration_tests.yml │ ├── _tests.yml │ ├── check_code_quality.yml │ └── upload-to-pypi.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── examples ├── code_interpreter_demo.py └── tokenize_data.py ├── mypy.ini ├── poetry.lock ├── pyproject.toml ├── src └── together │ ├── __init__.py │ ├── abstract │ ├── __init__.py │ └── api_requestor.py │ ├── cli │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── chat.py │ │ ├── completions.py │ │ ├── endpoints.py │ │ ├── files.py │ │ ├── finetune.py │ │ ├── images.py │ │ ├── models.py │ │ └── utils.py │ └── cli.py │ ├── client.py │ ├── constants.py │ ├── error.py │ ├── filemanager.py │ ├── legacy │ ├── __init__.py │ ├── base.py │ ├── complete.py │ ├── embeddings.py │ ├── files.py │ ├── finetune.py │ ├── images.py │ └── models.py │ ├── resources │ ├── __init__.py │ ├── audio │ │ ├── __init__.py │ │ └── speech.py │ ├── chat │ │ ├── __init__.py │ │ └── completions.py │ ├── code_interpreter.py │ ├── completions.py │ ├── embeddings.py │ ├── endpoints.py │ ├── files.py │ ├── finetune.py │ ├── images.py │ ├── models.py │ └── rerank.py │ ├── together_response.py │ ├── types │ ├── __init__.py │ ├── abstract.py │ ├── audio_speech.py │ ├── chat_completions.py │ ├── code_interpreter.py │ ├── common.py │ ├── completions.py │ ├── embeddings.py │ ├── endpoints.py │ ├── error.py │ ├── files.py │ ├── finetune.py │ ├── images.py │ ├── models.py │ └── rerank.py │ ├── utils │ ├── __init__.py │ ├── _log.py │ ├── api_helpers.py │ ├── files.py │ └── tools.py │ └── version.py ├── tests ├── integration │ ├── __init__.py │ ├── constants.py │ └── resources │ │ ├── __init__.py │ │ ├── generate_hyperparameters.py │ │ ├── test_completion.py │ │ └── test_completion_stream.py └── unit │ ├── test_async_client.py │ ├── test_client.py │ ├── test_code_interpreter.py │ ├── test_files_checks.py │ ├── test_finetune_resources.py │ ├── test_imports.py │ ├── test_preference_openai.py │ └── test_video_url.py └── tox.ini /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/python:3.10 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | RUN pipx install poetry==1.8.3 6 | 7 | # Install pre-commit 8 | RUN pip install pre-commit 9 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Together Python Development", 3 | "build": { 4 | "dockerfile": "Dockerfile" 5 | }, 6 | "features": { 7 | "ghcr.io/devcontainers/features/git:1": {}, 8 | "ghcr.io/devcontainers/features/node:1": {}, 9 | "ghcr.io/devcontainers/features/java:1": { 10 | "version": "17", 11 | "installMaven": false, 12 | "installGradle": false 13 | } 14 | }, 15 | "customizations": { 16 | "vscode": { 17 | "extensions": [ 18 | "ms-python.python", 19 | "ms-python.vscode-pylance", 20 | "ms-python.isort", 21 | "charliermarsh.ruff", 22 | "ms-python.mypy-type-checker", 23 | "eamodio.gitlens" 24 | ], 25 | "settings": { 26 | "[python]": { 27 | "editor.defaultFormatter": "charliermarsh.ruff" 28 | }, 29 | "editor.formatOnSave": true, 30 | "editor.codeActionsOnSave": { 31 | "source.fixAll": "explicit", 32 | "source.organizeImports": "explicit" 33 | }, 34 | "ruff.lineLength": 100 35 | } 36 | } 37 | }, 38 | "postCreateCommand": "poetry install", 39 | "remoteUser": "vscode" 40 | } 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | version: 2.1 3 | contact_links: 4 | - name: Together Support 5 | url: https://www.together.ai/contact 6 | about: Issues not directly pertaining to the Python Library 7 | - name: API Documentation 8 | url: https://docs.together.ai/ 9 | about: Documentation for the Together API 10 | - name: Discord 11 | url: https://discord.gg/9Rk6sSeWEG 12 | about: Chat with the Together community 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Have you read the [Contributing Guidelines](https://github.com/togethercomputer/together/blob/main/CONTRIBUTING.md)?* 2 | 3 | Issue # 4 | 5 | ## Describe your changes 6 | 7 | *Clearly and concisely describe what's in this pull request. Include screenshots, if necessary.* 8 | -------------------------------------------------------------------------------- /.github/actions/poetry_setup/action.yml: -------------------------------------------------------------------------------- 1 | # An action for setting up poetry install with caching. 2 | # Using a custom action since the default action does not 3 | # take poetry install groups into account. 4 | # Action code from: 5 | # https://github.com/actions/setup-python/issues/505#issuecomment-1273013236 6 | name: poetry-install-with-caching 7 | description: Poetry install with support for caching of dependency groups. 8 | 9 | inputs: 10 | python-version: 11 | description: Python version, supporting MAJOR.MINOR only 12 | required: true 13 | 14 | poetry-version: 15 | description: Poetry version 16 | required: true 17 | 18 | cache-key: 19 | description: Cache key to use for manual handling of caching 20 | required: true 21 | 22 | working-directory: 23 | description: Directory whose poetry.lock file should be cached 24 | required: true 25 | 26 | runs: 27 | using: composite 28 | steps: 29 | - uses: actions/setup-python@v5 30 | name: Setup python ${{ inputs.python-version }} 31 | id: setup-python 32 | with: 33 | python-version: ${{ inputs.python-version }} 34 | 35 | - uses: actions/cache@v4 36 | id: cache-bin-poetry 37 | name: Cache Poetry binary - Python ${{ inputs.python-version }} 38 | env: 39 | SEGMENT_DOWNLOAD_TIMEOUT_MIN: "1" 40 | with: 41 | path: | 42 | /opt/pipx/venvs/poetry 43 | # This step caches the poetry installation, so make sure it's keyed on the poetry version as well. 44 | key: bin-poetry-${{ runner.os }}-${{ runner.arch }}-py-${{ inputs.python-version }}-${{ inputs.poetry-version }} 45 | 46 | - name: Refresh shell hashtable and fixup softlinks 47 | if: steps.cache-bin-poetry.outputs.cache-hit == 'true' 48 | shell: bash 49 | env: 50 | POETRY_VERSION: ${{ inputs.poetry-version }} 51 | PYTHON_VERSION: ${{ inputs.python-version }} 52 | run: | 53 | set -eux 54 | 55 | # Refresh the shell hashtable, to ensure correct `which` output. 56 | hash -r 57 | 58 | # `actions/cache@v3` doesn't always seem able to correctly unpack softlinks. 59 | # Delete and recreate the softlinks pipx expects to have. 60 | rm /opt/pipx/venvs/poetry/bin/python 61 | cd /opt/pipx/venvs/poetry/bin 62 | ln -s "$(which "python$PYTHON_VERSION")" python 63 | chmod +x python 64 | cd /opt/pipx_bin/ 65 | ln -s /opt/pipx/venvs/poetry/bin/poetry poetry 66 | chmod +x poetry 67 | 68 | # Ensure everything got set up correctly. 69 | /opt/pipx/venvs/poetry/bin/python --version 70 | /opt/pipx_bin/poetry --version 71 | 72 | - name: Install poetry 73 | if: steps.cache-bin-poetry.outputs.cache-hit != 'true' 74 | shell: bash 75 | env: 76 | POETRY_VERSION: ${{ inputs.poetry-version }} 77 | PYTHON_VERSION: ${{ inputs.python-version }} 78 | # Install poetry using the python version installed by setup-python step. 79 | run: pipx install "poetry==$POETRY_VERSION" --python '${{ steps.setup-python.outputs.python-path }}' --verbose 80 | 81 | - name: Restore pip and poetry cached dependencies 82 | uses: actions/cache@v4 83 | env: 84 | SEGMENT_DOWNLOAD_TIMEOUT_MIN: "4" 85 | WORKDIR: ${{ inputs.working-directory == '' && '.' || inputs.working-directory }} 86 | with: 87 | path: | 88 | ~/.cache/pip 89 | ~/.cache/pypoetry/virtualenvs 90 | ~/.cache/pypoetry/cache 91 | ~/.cache/pypoetry/artifacts 92 | ${{ env.WORKDIR }}/.venv 93 | key: py-deps-${{ runner.os }}-${{ runner.arch }}-py-${{ inputs.python-version }}-poetry-${{ inputs.poetry-version }}-${{ inputs.cache-key }}-${{ hashFiles(format('{0}/**/poetry.lock', env.WORKDIR)) }} 94 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | groups: 13 | dependencies: 14 | patterns: 15 | - "*" 16 | -------------------------------------------------------------------------------- /.github/workflows/_integration_tests.yml: -------------------------------------------------------------------------------- 1 | name: integration-tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - "**.md" 8 | 9 | pull_request: 10 | branches: [ main ] 11 | paths-ignore: 12 | - "**.md" 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 16 | cancel-in-progress: true 17 | 18 | env: 19 | POETRY_VERSION: "2.1.1" 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | python-version: 27 | - "3.10" 28 | - "3.11" 29 | - "3.12" 30 | name: "integration_test python v${{ matrix.python-version }}" 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} 35 | uses: "./.github/actions/poetry_setup" 36 | with: 37 | python-version: ${{ matrix.python-version }} 38 | poetry-version: ${{ env.POETRY_VERSION }} 39 | cache-key: core 40 | 41 | - name: Install together with dependencies 42 | shell: bash 43 | run: poetry install --with quality,tests 44 | 45 | - name: Run integration tests 46 | shell: bash 47 | env: 48 | TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} 49 | run: | 50 | make integration_tests 51 | 52 | - name: Check no new files were created 53 | shell: bash 54 | run: | 55 | set -eu 56 | 57 | STATUS="$(git status)" 58 | echo "$STATUS" 59 | 60 | # grep will exit non-zero if the target message isn't found, 61 | # and `set -e` above will cause the step to fail. 62 | echo "$STATUS" | grep 'nothing to commit, working tree clean' 63 | -------------------------------------------------------------------------------- /.github/workflows/_tests.yml: -------------------------------------------------------------------------------- 1 | name: unit-tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - "**.md" 8 | 9 | pull_request: 10 | branches: [ main ] 11 | paths-ignore: 12 | - "**.md" 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 16 | cancel-in-progress: true 17 | 18 | env: 19 | POETRY_VERSION: "1.7.1" 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | python-version: 27 | - "3.10" 28 | - "3.11" 29 | - "3.12" 30 | name: "unittest python v${{ matrix.python-version }}" 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} 35 | uses: "./.github/actions/poetry_setup" 36 | with: 37 | python-version: ${{ matrix.python-version }} 38 | poetry-version: ${{ env.POETRY_VERSION }} 39 | cache-key: core 40 | 41 | - name: Install dependencies 42 | shell: bash 43 | run: poetry install --with quality,tests 44 | 45 | - name: Install together 46 | run: | 47 | poetry run pip install . 48 | 49 | - name: Run core tests 50 | shell: bash 51 | run: | 52 | make test 53 | 54 | - name: Check no new files were created 55 | shell: bash 56 | run: | 57 | set -eu 58 | 59 | STATUS="$(git status)" 60 | echo "$STATUS" 61 | 62 | # grep will exit non-zero if the target message isn't found, 63 | # and `set -e` above will cause the step to fail. 64 | echo "$STATUS" | grep 'nothing to commit, working tree clean' 65 | -------------------------------------------------------------------------------- /.github/workflows/check_code_quality.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | pull_request: 8 | branches: [ main ] 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | pre-commit: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - uses: pre-commit/action@v3.0.1 22 | -------------------------------------------------------------------------------- /.github/workflows/upload-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | env: 8 | POETRY_VERSION: "2.1.1" 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ["3.10"] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} 20 | uses: "./.github/actions/poetry_setup" 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | poetry-version: ${{ env.POETRY_VERSION }} 24 | cache-key: core 25 | 26 | - name: Set up cache 27 | uses: actions/cache@v4 28 | with: 29 | path: .venv 30 | key: venv-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}-${{ hashFiles('poetry.lock') }} 31 | 32 | - name: Install dependencies 33 | run: | 34 | poetry config virtualenvs.in-project true 35 | poetry install 36 | 37 | - name: Build 38 | run: | 39 | poetry build 40 | 41 | - name: Publish distribution to PyPI 42 | if: startsWith(github.ref, 'refs/tags') 43 | uses: pypa/gh-action-pypi-publish@release/v1 44 | with: 45 | password: ${{ secrets.ORANGETIN_PYPI }} 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.5.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - id: check-docstring-first 9 | - id: check-merge-conflict 10 | - id: check-toml 11 | - repo: https://github.com/psf/black 12 | rev: 24.3.0 13 | hooks: 14 | - id: black 15 | - repo: https://github.com/pre-commit/mirrors-mypy 16 | rev: 'v1.8.0' 17 | hooks: 18 | - id: mypy 19 | args: [--strict] 20 | additional_dependencies: [types-requests, types-tqdm, types-tabulate, types-click, types-filelock, types-Pillow, rich, pyarrow-stubs, pydantic, aiohttp] 21 | exclude: ^tests/ 22 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | support@together.ai. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | *Pull requests, bug reports, and all other forms of contribution are welcomed and highly encouraged!* :octocat: 4 | 5 | ### Contents 6 | 7 | - [Code of Conduct](#book-code-of-conduct) 8 | - [Asking Questions](#bulb-asking-questions) 9 | - [Opening an Issue](#inbox_tray-opening-an-issue) 10 | - [Feature Requests](#love_letter-feature-requests) 11 | - [Triaging Issues](#mag-triaging-issues) 12 | - [Submitting Pull Requests](#repeat-submitting-pull-requests) 13 | - [Writing Commit Messages](#memo-writing-commit-messages) 14 | - [Code Review](#white_check_mark-code-review) 15 | - [Coding Style](#nail_care-coding-style) 16 | - [Certificate of Origin](#medal_sports-certificate-of-origin) 17 | 18 | > **This guide serves to set clear expectations for everyone involved with the project so that we can improve it together while also creating a welcoming space for everyone to participate. Following these guidelines will help ensure a positive experience for contributors and maintainers.** 19 | 20 | ## Contributing Code 21 | 22 | To contribute to this project, please follow the ["fork and pull request"](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow. 23 | Please do not try to push directly to this repo unless you are a maintainer. 24 | 25 | Please follow the checked-in pull request template when opening pull requests. Note related issues and tag relevant 26 | maintainers. 27 | 28 | Pull requests cannot land without passing the formatting, linting, and testing checks first. See [Testing](#running-tests) and 29 | [Formatting and Linting](#formatting-and-linting) for how to run these checks locally. 30 | 31 | ### Dependency Management: Poetry and other env/dependency managers 32 | 33 | This project utilizes [Poetry](https://python-poetry.org/) v1.6.1+ as a dependency manager. 34 | 35 | ❗Note: *Before installing Poetry*, if you use `Conda`, create and activate a new Conda env (e.g. `conda create -n together python=3.10`) 36 | 37 | Install Poetry: **[documentation on how to install it](https://python-poetry.org/docs/#installation)**. 38 | 39 | ❗Note: If you use `Conda` or `Pyenv` as your environment/package manager, after installing Poetry, 40 | tell Poetry to use the virtualenv python environment (`poetry config virtualenvs.prefer-active-python true`) 41 | 42 | ### Local Development Dependencies 43 | 44 | Install Together development requirements (for running Together, running examples, linting, formatting, and tests): 45 | 46 | ```bash 47 | poetry install --with quality,tests 48 | ``` 49 | 50 | And set up pre-commit for auto-formatting and linting 51 | ```bash 52 | pre-commit install 53 | ``` 54 | 55 | ### Formatting and Linting 56 | 57 | Run these locally before submitting a PR; the CI system will check also. 58 | 59 | ``` 60 | $ make format 61 | ``` 62 | 63 | ### Running tests 64 | 65 | #### Unit Tests 66 | 67 | ```bash 68 | make tests 69 | ``` 70 | 71 | #### Integration Tests 72 | > 🚧 Warning: Integration Tests requires an API key to be exported and you will be charged for usage. It is recommended to let the CI system handle integration tests. 73 | 74 | ```bash 75 | make integration_tests 76 | ``` 77 | 78 | ### Working with Optional Dependencies 79 | 80 | If you're adding a new dependency to Together, assume that it will be an optional dependency, and 81 | that most users won't have it installed. 82 | 83 | Users who do not have the dependency installed should be able to **import** your code without 84 | any side effects (no warnings, no errors, no exceptions). 85 | 86 | To introduce the dependency to the pyproject.toml file correctly, please do the following: 87 | 88 | 1. Add the dependency to the main group as an optional dependency 89 | ```bash 90 | poetry add --optional [package_name] 91 | ``` 92 | 2. Open pyproject.toml and add the dependency to the `extended_testing` extra 93 | 3. Relock the poetry file to update the extra. 94 | ```bash 95 | poetry lock --no-update 96 | ``` 97 | 4. Add a unit test that the very least attempts to import the new code. Ideally, the unit 98 | test makes use of lightweight fixtures to test the logic of the code. 99 | 5. Please use the `@pytest.mark.requires(package_name)` decorator for any tests that require the dependency. 100 | 101 | 102 | ## :repeat: Submitting Pull Requests 103 | 104 | We **love** pull requests! Before [forking the repo](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) and [creating a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests) for non-trivial changes, it is usually best to first open an issue to discuss the changes, or discuss your intended approach for solving the problem in the comments for an existing issue. 105 | 106 | For most contributions, after your first pull request is accepted and merged, you will be [invited to the project](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/inviting-collaborators-to-a-personal-repository) and given **push access**. :tada: 107 | 108 | *Note: All contributions will be licensed under the project's license.* 109 | 110 | - **Smaller is better.** Submit **one** pull request per bug fix or feature. A pull request should contain isolated changes pertaining to a single bug fix or feature implementation. **Do not** refactor or reformat code that is unrelated to your change. It is better to **submit many small pull requests** rather than a single large one. Enormous pull requests will take enormous amounts of time to review, or may be rejected altogether. 111 | 112 | - **Coordinate bigger changes.** For large and non-trivial changes, open an issue to discuss a strategy with the maintainers. Otherwise, you risk doing a lot of work for nothing! 113 | 114 | - **Prioritize understanding over cleverness.** Write code clearly and concisely. Remember that source code usually gets written once and read often. Ensure the code is clear to the reader. The purpose and logic should be obvious to a reasonably skilled developer, otherwise you should add a comment that explains it. 115 | 116 | - **Follow existing coding style and conventions.** Keep your code consistent with the style, formatting, and conventions in the rest of the code base. When possible, these will be enforced with a linter. Consistency makes it easier to review and modify in the future. 117 | 118 | - **Include test coverage.** Add unit tests or integration tests when possible. Follow existing patterns for implementing tests. 119 | 120 | - **Update the examples** if one exists to exercise any new functionality you have added. 121 | 122 | - **Add documentation.** Document your changes with code doc comments or in existing guides. 123 | 124 | - **[Resolve any merge conflicts](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github)** that occur. 125 | 126 | - **Promptly address any CI failures**. If your pull request fails to build or pass tests, please push another commit to fix it. 127 | 128 | - When writing comments, use properly constructed sentences, including punctuation. 129 | 130 | - Use spaces, not tabs. 131 | 132 | 133 | ## :medal_sports: Certificate of Origin 134 | 135 | *Developer's Certificate of Origin 1.1* 136 | 137 | By making a contribution to this project, I certify that: 138 | 139 | > 1. The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or 140 | > 1. The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or 141 | > 1. The contribution was provided directly to me by some other person who certified (1), (2) or (3) and I have not modified it. 142 | > 1. I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. 143 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all format lint test tests test_watch integration_tests docker_tests help extended_tests 2 | 3 | # Default target executed when no arguments are given to make. 4 | all: help 5 | 6 | # Define a variable for the test file path. 7 | TEST_FILE ?= tests/unit/ 8 | 9 | test: 10 | poetry run pytest $(TEST_FILE) 11 | 12 | tests: 13 | poetry run pytest $(TEST_FILE) 14 | 15 | test_watch: 16 | poetry run ptw --ignore ./tests/integration . -- ./tests/unit 17 | 18 | extended_tests: 19 | poetry run pytest --only-extended ./tests/unit 20 | 21 | integration_tests: 22 | poetry run pytest tests/integration 23 | 24 | 25 | # Linting & Formatting 26 | 27 | install: 28 | poetry install --with quality,tests 29 | poetry run pre-commit install 30 | 31 | format: 32 | poetry run pre-commit run --all-files 33 | 34 | 35 | # Documentation 36 | 37 | html: 38 | make.bat html 39 | 40 | help: 41 | @echo '====================' 42 | @echo '-- DOCUMENTATION ---' 43 | @echo '--------------------' 44 | @echo 'install - install dependencies' 45 | @echo 'format - run code formatters' 46 | @echo 'test - run unit tests' 47 | @echo 'tests - run unit tests' 48 | @echo 'test TEST_FILE= - run all tests in file' 49 | @echo 'test_watch - run unit tests in watch mode' 50 | @echo 'extended_tests - run extended tests' 51 | @echo 'integration_tests - run integration tests' 52 | -------------------------------------------------------------------------------- /examples/code_interpreter_demo.py: -------------------------------------------------------------------------------- 1 | from together import Together 2 | 3 | client = Together() 4 | 5 | # Create a code interpreter instance 6 | code_interpreter = client.code_interpreter 7 | 8 | # Example 1: Simple print statement 9 | print("Example 1: Simple print") 10 | response = code_interpreter.run(code='print("Hello from Together!")', language="python") 11 | print(f"Status: {response.data.status}") 12 | for output in response.data.outputs: 13 | print(f"{output.type}: {output.data}") 14 | if response.data.errors: 15 | print(f"Errors: {response.data.errors}") 16 | print("\n") 17 | 18 | # Example 2: Using session for maintaining state 19 | print("Example 2: Using session for state") 20 | response1 = code_interpreter.run(code="x = 42", language="python") 21 | session_id = response1.data.session_id 22 | 23 | response2 = code_interpreter.run( 24 | code='print(f"The value of x is {x}")', language="python", session_id=session_id 25 | ) 26 | for output in response2.data.outputs: 27 | print(f"{output.type}: {output.data}") 28 | if response2.data.errors: 29 | print(f"Errors: {response2.data.errors}") 30 | print("\n") 31 | 32 | # Example 3: More complex computation 33 | print("Example 3: Complex computation") 34 | code = """ 35 | !pip install numpy 36 | import numpy as np 37 | 38 | # Create a random matrix 39 | matrix = np.random.rand(3, 3) 40 | print("Random matrix:") 41 | print(matrix) 42 | 43 | # Calculate eigenvalues 44 | eigenvalues = np.linalg.eigvals(matrix) 45 | print("\\nEigenvalues:") 46 | print(eigenvalues) 47 | """ 48 | 49 | response = code_interpreter.run(code=code, language="python") 50 | for output in response.data.outputs: 51 | print(f"{output.type}: {output.data}") 52 | if response.data.errors: 53 | print(f"Errors: {response.data.errors}") 54 | 55 | # Example 4: Uploading and using a file 56 | print("Example 4: Uploading and using a file") 57 | 58 | # Define the file content and structure as a dictionary 59 | file_to_upload = { 60 | "name": "data.txt", 61 | "encoding": "string", 62 | "content": "This is the content of the uploaded file.", 63 | } 64 | 65 | # Code to read the uploaded file 66 | code_to_read_file = """ 67 | try: 68 | with open('data.txt', 'r') as f: 69 | content = f.read() 70 | print(f"Content read from data.txt: {content}") 71 | except FileNotFoundError: 72 | print("Error: data.txt not found.") 73 | """ 74 | 75 | response = code_interpreter.run( 76 | code=code_to_read_file, 77 | language="python", 78 | files=[file_to_upload], # Pass the file dictionary in a list 79 | ) 80 | 81 | # Print results 82 | print(f"Status: {response.data.status}") 83 | for output in response.data.outputs: 84 | print(f"{output.type}: {output.data}") 85 | if response.data.errors: 86 | print(f"Errors: {response.data.errors}") 87 | print("\n") 88 | 89 | # Example 5: Uploading a script and running it 90 | print("Example 5: Uploading a python script and running it") 91 | 92 | script_content = "import sys\nprint(f'Hello from {sys.argv[0]}!')" 93 | 94 | # Define the script file as a dictionary 95 | script_file = { 96 | "name": "myscript.py", 97 | "encoding": "string", 98 | "content": script_content, 99 | } 100 | 101 | code_to_run_script = "!python myscript.py" 102 | 103 | response = code_interpreter.run( 104 | code=code_to_run_script, 105 | language="python", 106 | files=[script_file], # Pass the script dictionary in a list 107 | ) 108 | 109 | # Print results 110 | print(f"Status: {response.data.status}") 111 | for output in response.data.outputs: 112 | print(f"{output.type}: {output.data}") 113 | if response.data.errors: 114 | print(f"Errors: {response.data.errors}") 115 | print("\n") 116 | 117 | # Example 6: Uploading a base64 encoded image (simulated) 118 | 119 | print("Example 6: Uploading a base64 encoded binary file (e.g., image)") 120 | 121 | # Example: A tiny 1x1 black PNG image, base64 encoded 122 | # In a real scenario, you would read your binary file and base64 encode its content 123 | tiny_png_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" 124 | 125 | image_file = { 126 | "name": "tiny.png", 127 | "encoding": "base64", # Use base64 encoding for binary files 128 | "content": tiny_png_base64, 129 | } 130 | 131 | # Code to check if the file exists and its size (Python doesn't inherently know image dimensions from bytes alone) 132 | code_to_check_file = ( 133 | """ 134 | import os 135 | import base64 136 | 137 | file_path = 'tiny.png' 138 | if os.path.exists(file_path): 139 | # Read the raw bytes back 140 | with open(file_path, 'rb') as f: 141 | raw_bytes = f.read() 142 | original_bytes = base64.b64decode('""" 143 | + tiny_png_base64 144 | + """') 145 | print(f"File '{file_path}' exists.") 146 | print(f"Size on disk: {os.path.getsize(file_path)} bytes.") 147 | print(f"Size of original decoded base64 data: {len(original_bytes)} bytes.") 148 | 149 | else: 150 | print(f"File '{file_path}' does not exist.") 151 | """ 152 | ) 153 | 154 | response = code_interpreter.run( 155 | code=code_to_check_file, 156 | language="python", 157 | files=[image_file], 158 | ) 159 | 160 | # Print results 161 | print(f"Status: {response.data.status}") 162 | for output in response.data.outputs: 163 | print(f"{output.type}: {output.data}") 164 | if response.data.errors: 165 | print(f"Errors: {response.data.errors}") 166 | print("\n") 167 | -------------------------------------------------------------------------------- /examples/tokenize_data.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | from functools import partial 4 | from multiprocessing import cpu_count 5 | from typing import Dict, List 6 | 7 | from datasets import Dataset, load_dataset # type: ignore 8 | from transformers import ( # type: ignore 9 | AutoTokenizer, 10 | BatchEncoding, 11 | PreTrainedTokenizerBase, 12 | ) 13 | 14 | 15 | # see default of ignore_index 16 | # for https://pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html#torch.nn.CrossEntropyLoss 17 | LOSS_IGNORE_INDEX = -100 18 | 19 | logger = logging.getLogger(__name__) 20 | logging.basicConfig(level=logging.INFO) 21 | 22 | 23 | def tokenize_variable_length( 24 | data: Dict[str, str], 25 | tokenizer: PreTrainedTokenizerBase, 26 | add_special_tokens: bool = True, 27 | ) -> BatchEncoding: 28 | tokenized = tokenizer( 29 | data["text"], add_special_tokens=add_special_tokens, truncation=False 30 | ) 31 | return tokenized 32 | 33 | 34 | def tokenize_constant_length( 35 | data: Dict[str, str], 36 | tokenizer: PreTrainedTokenizerBase, 37 | max_length: int = 2048, 38 | add_special_tokens: bool = True, 39 | add_labels: bool = True, 40 | ) -> BatchEncoding: 41 | # tokenized contains `input_ids` and `attention_mask` 42 | tokenized: BatchEncoding = tokenizer( 43 | data["text"], 44 | max_length=max_length, 45 | truncation=True, 46 | padding="max_length", 47 | add_special_tokens=add_special_tokens, 48 | ) 49 | # add labels to mask out any padding tokens 50 | if add_labels: 51 | tokenized["labels"] = [ 52 | LOSS_IGNORE_INDEX if token_id == tokenizer.pad_token_id else token_id 53 | for token_id in tokenized["input_ids"] 54 | ] 55 | 56 | return tokenized 57 | 58 | 59 | def pack_sequences( 60 | batch: BatchEncoding, 61 | max_seq_len: int, 62 | pad_token_id: int, 63 | eos_token_id: int, 64 | add_labels: bool, 65 | cutoff_size: int = 0, 66 | ) -> Dict[str, List[List[int]]]: 67 | """ 68 | cutoff_size = max_seq_len means that we will drop any non-full sequences 69 | (full packing without padding) 70 | Example: 71 | Sequence 1: 72 | ['', '▁usually', '▁,', '▁he', '▁would', '▁be', '▁t', 'earing'] 73 | Sequence 2: 74 | ['▁around', '▁the', '▁living', '▁room', '▁,', '▁playing', '▁with', '▁his'] 75 | Sequence 3: 76 | ['▁toys', '▁.', '', '', '▁but', '▁just', '▁one', '▁look'] 77 | """ 78 | packed_sequences = [] 79 | buffer = [] 80 | 81 | for input_ids in batch["input_ids"]: 82 | # Add the current sequence to the buffer 83 | buffer.extend(input_ids) 84 | buffer.append(eos_token_id) # Add EOS at the end of each sequence 85 | 86 | # Check if buffer needs to be split into chunks 87 | while len(buffer) > max_seq_len: 88 | # Take a full chunk from the buffer and append it to packed_sequences 89 | packed_sequences.append(buffer[:max_seq_len]) 90 | # Remove the processed chunk from the buffer 91 | buffer = buffer[max_seq_len:] 92 | 93 | # Add the last buffer if it's exactly chunk_size 94 | if len(buffer) == max_seq_len: 95 | packed_sequences.append(buffer) 96 | elif len(buffer) > cutoff_size: 97 | # if the buffer is larger than the cutoff size, pad it to the chunk_size 98 | # if not, we do not include in the packed_sequences 99 | buffer.extend([pad_token_id] * (max_seq_len - len(buffer))) 100 | packed_sequences.append(buffer) 101 | 102 | output = {"input_ids": packed_sequences} 103 | if add_labels: 104 | output["labels"] = [ 105 | [ 106 | LOSS_IGNORE_INDEX if token_id == pad_token_id else token_id 107 | for token_id in example 108 | ] 109 | for example in output["input_ids"] 110 | ] 111 | 112 | # mask attention for padding tokens, a better version would also mask cross-sequence dependencies 113 | output["attention_mask"] = [ 114 | [0 if token_id == pad_token_id else 1 for token_id in example] 115 | for example in output["input_ids"] 116 | ] 117 | return output 118 | 119 | 120 | def process_fast_packing( 121 | dataset: Dataset, 122 | tokenizer: PreTrainedTokenizerBase, 123 | max_sequence_length: int, 124 | add_labels: bool, 125 | add_special_tokens: bool, 126 | ) -> Dataset: 127 | tokenized_dataset = dataset.map( 128 | lambda examples: tokenize_variable_length( 129 | examples, tokenizer, add_special_tokens=add_special_tokens 130 | ), 131 | batched=True, 132 | num_proc=cpu_count(), 133 | load_from_cache_file=True, 134 | remove_columns=dataset.column_names, 135 | ) 136 | logger.info(f"tokenized dataset: {tokenized_dataset}") 137 | 138 | packed_dataset = tokenized_dataset.map( 139 | lambda batch: pack_sequences( 140 | batch, 141 | max_sequence_length, 142 | tokenizer.pad_token_id, 143 | tokenizer.eos_token_id, 144 | add_labels=add_labels, 145 | cutoff_size=max_sequence_length, 146 | ), 147 | batched=True, 148 | num_proc=cpu_count() if len(tokenized_dataset) > 10000 else 1, 149 | remove_columns=["attention_mask"], 150 | ) 151 | logger.info(f"Packed dataset: {packed_dataset}") 152 | return packed_dataset 153 | 154 | 155 | def process_data(args: argparse.Namespace) -> None: 156 | if not args.out_filename.endswith(".parquet"): 157 | raise ValueError("`--out_filename` should have the `.parquet` extension") 158 | 159 | dataset = load_dataset(args.dataset, split="train") 160 | tokenizer = AutoTokenizer.from_pretrained(args.tokenizer) 161 | tokenizer.pad_token = tokenizer.eos_token 162 | 163 | dataset.to_json("dataset.jsonl", orient="records", lines=True) 164 | 165 | if not args.packing: 166 | tokenized_data = dataset.map( 167 | partial( 168 | tokenize_constant_length, 169 | tokenizer=tokenizer, 170 | max_length=args.max_seq_length, 171 | add_special_tokens=True, 172 | add_labels=args.add_labels, 173 | ), 174 | batched=False, 175 | num_proc=cpu_count(), 176 | remove_columns=dataset.column_names, 177 | ) 178 | else: 179 | tokenized_data = process_fast_packing( 180 | dataset, 181 | tokenizer, 182 | max_sequence_length=args.max_seq_length, 183 | add_labels=args.add_labels, 184 | add_special_tokens=True, 185 | ) 186 | 187 | assert ( 188 | "input_ids" in tokenized_data.column_names 189 | and "attention_mask" in tokenized_data.column_names 190 | ) 191 | 192 | if args.add_labels: 193 | assert "labels" in tokenized_data.column_names 194 | 195 | logger.info("Tokenized data:") 196 | print(tokenized_data) 197 | 198 | logger.info(f"Saving data to {args.out_filename}") 199 | print(len(tokenized_data[0]["input_ids"])) 200 | tokenized_data.to_parquet(args.out_filename) 201 | 202 | 203 | if __name__ == "__main__": 204 | parser = argparse.ArgumentParser( 205 | description="Pretokenize examples for finetuning via Together" 206 | ) 207 | parser.add_argument( 208 | "--dataset", 209 | type=str, 210 | default="clam004/antihallucination_dataset", 211 | help="Dataset name on the Hugging Face Hub", 212 | ) 213 | parser.add_argument( 214 | "--max-seq-length", type=int, default=8192, help="Maximum sequence length" 215 | ) 216 | parser.add_argument( 217 | "--add-labels", 218 | action="store_true", 219 | help="Whether to add loss labels from padding tokens", 220 | ) 221 | parser.add_argument( 222 | "--tokenizer", 223 | type=str, 224 | required=True, 225 | help="Tokenizer name (for example, togethercomputer/Llama-3-8b-hf)", 226 | ) 227 | parser.add_argument( 228 | "--out-filename", 229 | default="processed_dataset.parquet", 230 | help="Name of the Parquet file to save (should have .parquet extension)", 231 | ) 232 | parser.add_argument( 233 | "--packing", 234 | action="store_true", 235 | help="Whether to pack shorter sequences up to `--max-seq-length`", 236 | ) 237 | args = parser.parse_args() 238 | 239 | process_data(args) 240 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | plugins = pydantic.mypy 3 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "poetry", 4 | # Starting with NumPy 1.25, NumPy is (by default) as far back compatible 5 | # as oldest-support-numpy was (customizable with a NPY_TARGET_VERSION 6 | # define). For older Python versions (where NumPy 1.25 is not yet avaiable) 7 | # continue using oldest-support-numpy. 8 | "oldest-supported-numpy>=0.14; python_version<'3.9'", 9 | "numpy>=1.25; python_version>='3.9'", 10 | ] 11 | build-backend = "poetry.masonry.api" 12 | 13 | [tool.poetry] 14 | name = "together" 15 | version = "1.5.10" 16 | authors = ["Together AI "] 17 | description = "Python client for Together's Cloud Platform!" 18 | readme = "README.md" 19 | license = "Apache-2.0" 20 | classifiers = [ 21 | "Programming Language :: Python :: 3", 22 | "License :: OSI Approved :: Apache Software License", 23 | "Operating System :: POSIX :: Linux", 24 | ] 25 | repository = "https://github.com/togethercomputer/together-python" 26 | homepage = "https://github.com/togethercomputer/together-python" 27 | 28 | [tool.poetry.dependencies] 29 | python = "^3.10" 30 | typer = ">=0.9,<0.16" 31 | requests = "^2.31.0" 32 | rich = ">=13.8.1,<15.0.0" 33 | tqdm = "^4.66.2" 34 | tabulate = "^0.9.0" 35 | pydantic = "^2.6.3" 36 | aiohttp = "^3.9.3" 37 | filelock = "^3.13.1" 38 | eval-type-backport = ">=0.1.3,<0.3.0" 39 | click = "^8.1.7" 40 | pyarrow = { version = ">=10.0.1", optional = true } 41 | numpy = [ 42 | { version = ">=1.23.5", python = "<3.12" }, 43 | { version = ">=1.26.0", python = ">=3.12" }, 44 | ] 45 | pillow = "^11.1.0" 46 | 47 | [tool.poetry.extras] 48 | pyarrow = ["pyarrow"] 49 | 50 | [tool.poetry.group.quality] 51 | optional = true 52 | 53 | [tool.poetry.group.quality.dependencies] 54 | black = ">=23.1,<26.0" 55 | ruff = ">=0.3.2,<0.12.0" 56 | types-tqdm = "^4.65.0.0" 57 | types-tabulate = "^0.9.0.3" 58 | pre-commit = "4.2.0" 59 | types-requests = "^2.31.0.20240218" 60 | pyarrow-stubs = ">=10.0.1.7,<20240831.0.0.0" 61 | mypy = "^1.9.0" 62 | 63 | [tool.poetry.group.tests] 64 | optional = true 65 | 66 | [tool.poetry.group.tests.dependencies] 67 | pytest = ">=7.4.2,<9.0.0" 68 | pytest-watch = "^4.2.0" 69 | pytest-mock = "^3.14.0" 70 | tox = "^4.14.1" 71 | 72 | [tool.poetry.group.examples] 73 | optional = true 74 | 75 | [tool.poetry.group.examples.dependencies] 76 | datasets = ">=2.18,<4.0" 77 | transformers = "^4.39.3" 78 | 79 | 80 | [tool.poetry.urls] 81 | "Homepage" = "https://github.com/togethercomputer/together-python" 82 | "Bug Tracker" = "https://github.com/togethercomputer/together-python/issues" 83 | 84 | [tool.poetry.scripts] 85 | together = "together.cli.cli:main" 86 | 87 | [tool.black] 88 | target-version = ['py310'] 89 | 90 | [tool.ruff.lint] 91 | # Never enforce `E501` (line length violations). 92 | ignore = ["C901", "E501", "E741", "W605"] 93 | select = ["C", "E", "F", "I", "W"] 94 | 95 | # Ignore import violations in all `__init__.py` files. 96 | [tool.ruff.lint.per-file-ignores] 97 | "__init__.py" = ["E402", "F401", "F403", "F811"] 98 | 99 | [tool.ruff.lint.isort] 100 | lines-after-imports = 2 101 | known-first-party = ["together"] 102 | 103 | [tool.mypy] 104 | strict = true 105 | mypy_path = "." 106 | -------------------------------------------------------------------------------- /src/together/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from contextvars import ContextVar 4 | from typing import TYPE_CHECKING, Callable 5 | 6 | from together import ( 7 | abstract, 8 | client, 9 | constants, 10 | error, 11 | filemanager, 12 | resources, 13 | together_response, 14 | types, 15 | utils, 16 | ) 17 | from together.version import VERSION 18 | 19 | from together.legacy.complete import AsyncComplete, Complete, Completion 20 | from together.legacy.embeddings import Embeddings 21 | from together.legacy.files import Files 22 | from together.legacy.finetune import Finetune 23 | from together.legacy.images import Image 24 | from together.legacy.models import Models 25 | 26 | version = VERSION 27 | 28 | log: str | None = None # Set to either 'debug' or 'info', controls console logging 29 | 30 | if TYPE_CHECKING: 31 | import requests 32 | from aiohttp import ClientSession 33 | 34 | requestssession: "requests.Session" | Callable[[], "requests.Session"] | None = None 35 | 36 | aiosession: ContextVar["ClientSession" | None] = ContextVar( 37 | "aiohttp-session", default=None 38 | ) 39 | 40 | from together.client import AsyncClient, AsyncTogether, Client, Together 41 | 42 | 43 | api_key: str | None = None # To be deprecated in the next major release 44 | 45 | # Legacy functions 46 | 47 | 48 | __all__ = [ 49 | "aiosession", 50 | "constants", 51 | "version", 52 | "Together", 53 | "AsyncTogether", 54 | "Client", 55 | "AsyncClient", 56 | "resources", 57 | "types", 58 | "abstract", 59 | "filemanager", 60 | "error", 61 | "together_response", 62 | "client", 63 | "utils", 64 | "Complete", 65 | "AsyncComplete", 66 | "Completion", 67 | "Embeddings", 68 | "Files", 69 | "Finetune", 70 | "Image", 71 | "Models", 72 | ] 73 | -------------------------------------------------------------------------------- /src/together/abstract/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/togethercomputer/together-python/554d3e820ffe07aadff258410aa7cedadeb2fc51/src/together/abstract/__init__.py -------------------------------------------------------------------------------- /src/together/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/togethercomputer/together-python/554d3e820ffe07aadff258410aa7cedadeb2fc51/src/together/cli/__init__.py -------------------------------------------------------------------------------- /src/together/cli/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/togethercomputer/together-python/554d3e820ffe07aadff258410aa7cedadeb2fc51/src/together/cli/api/__init__.py -------------------------------------------------------------------------------- /src/together/cli/api/chat.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import cmd 4 | import json 5 | from typing import List, Tuple 6 | 7 | import click 8 | 9 | from together import Together 10 | from together.types.chat_completions import ( 11 | ChatCompletionChoicesChunk, 12 | ChatCompletionChunk, 13 | ChatCompletionResponse, 14 | ) 15 | 16 | 17 | class ChatShell(cmd.Cmd): 18 | intro = "Type /exit to exit, /help, or /? to list commands.\n" 19 | prompt = ">>> " 20 | 21 | def __init__( 22 | self, 23 | client: Together, 24 | model: str, 25 | max_tokens: int | None = None, 26 | stop: List[str] | None = None, 27 | temperature: float | None = None, 28 | top_p: float | None = None, 29 | top_k: int | None = None, 30 | repetition_penalty: float | None = None, 31 | presence_penalty: float | None = None, 32 | frequency_penalty: float | None = None, 33 | min_p: float | None = None, 34 | safety_model: str | None = None, 35 | system_message: str | None = None, 36 | ) -> None: 37 | super().__init__() 38 | self.client = client 39 | self.model = model 40 | self.max_tokens = max_tokens 41 | self.stop = stop 42 | self.temperature = temperature 43 | self.top_p = top_p 44 | self.top_k = top_k 45 | self.repetition_penalty = repetition_penalty 46 | self.presence_penalty = presence_penalty 47 | self.frequency_penalty = frequency_penalty 48 | self.min_p = min_p 49 | self.safety_model = safety_model 50 | self.system_message = system_message 51 | 52 | self.messages = ( 53 | [{"role": "system", "content": self.system_message}] 54 | if self.system_message 55 | else [] 56 | ) 57 | 58 | def precmd(self, line: str) -> str: 59 | if line.startswith("/"): 60 | return line[1:] 61 | else: 62 | return "say " + line 63 | 64 | def do_say(self, arg: str) -> None: 65 | self.messages.append({"role": "user", "content": arg}) 66 | 67 | output = "" 68 | 69 | for chunk in self.client.chat.completions.create( 70 | messages=self.messages, 71 | model=self.model, 72 | max_tokens=self.max_tokens, 73 | stop=self.stop, 74 | temperature=self.temperature, 75 | top_p=self.top_p, 76 | top_k=self.top_k, 77 | repetition_penalty=self.repetition_penalty, 78 | presence_penalty=self.presence_penalty, 79 | frequency_penalty=self.frequency_penalty, 80 | min_p=self.min_p, 81 | safety_model=self.safety_model, 82 | stream=True, 83 | ): 84 | # assertions for type checking 85 | assert isinstance(chunk, ChatCompletionChunk) 86 | assert chunk.choices 87 | assert chunk.choices[0].delta 88 | 89 | token = chunk.choices[0].delta.content 90 | 91 | click.echo(token, nl=False) 92 | 93 | output += token or "" 94 | 95 | click.echo("\n") 96 | 97 | self.messages.append({"role": "assistant", "content": output}) 98 | 99 | def do_reset(self, arg: str) -> None: 100 | self.messages = ( 101 | [{"role": "system", "content": self.system_message}] 102 | if self.system_message 103 | else [] 104 | ) 105 | 106 | def do_exit(self, arg: str) -> bool: 107 | return True 108 | 109 | 110 | @click.command(name="chat.interactive") 111 | @click.pass_context 112 | @click.option("--model", type=str, required=True, help="Model name") 113 | @click.option("--max-tokens", type=int, help="Max tokens to generate") 114 | @click.option( 115 | "--stop", type=str, multiple=True, help="List of strings to stop generation" 116 | ) 117 | @click.option("--temperature", type=float, help="Sampling temperature") 118 | @click.option("--top-p", type=int, help="Top p sampling") 119 | @click.option("--top-k", type=float, help="Top k sampling") 120 | @click.option("--repetition-penalty", type=float, help="Repetition penalty") 121 | @click.option("--presence-penalty", type=float, help="Presence penalty") 122 | @click.option("--frequency-penalty", type=float, help="Frequency penalty") 123 | @click.option("--min-p", type=float, help="Minimum p") 124 | @click.option("--safety-model", type=str, help="Moderation model") 125 | @click.option("--system-message", type=str, help="System message to use for the chat") 126 | def interactive( 127 | ctx: click.Context, 128 | model: str, 129 | max_tokens: int | None = None, 130 | stop: List[str] | None = None, 131 | temperature: float | None = None, 132 | top_p: float | None = None, 133 | top_k: int | None = None, 134 | repetition_penalty: float | None = None, 135 | presence_penalty: float | None = None, 136 | frequency_penalty: float | None = None, 137 | min_p: float | None = None, 138 | safety_model: str | None = None, 139 | system_message: str | None = None, 140 | ) -> None: 141 | """Interactive chat shell""" 142 | client: Together = ctx.obj 143 | 144 | ChatShell( 145 | client=client, 146 | model=model, 147 | max_tokens=max_tokens, 148 | stop=stop, 149 | temperature=temperature, 150 | top_p=top_p, 151 | top_k=top_k, 152 | repetition_penalty=repetition_penalty, 153 | presence_penalty=presence_penalty, 154 | frequency_penalty=frequency_penalty, 155 | min_p=min_p, 156 | safety_model=safety_model, 157 | system_message=system_message, 158 | ).cmdloop() 159 | 160 | 161 | @click.command(name="chat.completions") 162 | @click.pass_context 163 | @click.option( 164 | "--message", 165 | type=(str, str), 166 | multiple=True, 167 | required=True, 168 | help="Message to generate chat completions from", 169 | ) 170 | @click.option("--model", type=str, required=True, help="Model name") 171 | @click.option("--max-tokens", type=int, help="Max tokens to generate") 172 | @click.option( 173 | "--stop", type=str, multiple=True, help="List of strings to stop generation" 174 | ) 175 | @click.option("--temperature", type=float, help="Sampling temperature") 176 | @click.option("--top-p", type=int, help="Top p sampling") 177 | @click.option("--top-k", type=float, help="Top k sampling") 178 | @click.option("--repetition-penalty", type=float, help="Repetition penalty") 179 | @click.option("--presence-penalty", type=float, help="Presence penalty sampling method") 180 | @click.option( 181 | "--frequency-penalty", type=float, help="Frequency penalty sampling method" 182 | ) 183 | @click.option("--min-p", type=float, help="Min p sampling") 184 | @click.option("--no-stream", is_flag=True, help="Disable streaming") 185 | @click.option("--logprobs", type=int, help="Return logprobs. Only works with --raw.") 186 | @click.option("--echo", is_flag=True, help="Echo prompt. Only works with --raw.") 187 | @click.option("--n", type=int, help="Number of output generations") 188 | @click.option("--safety-model", type=str, help="Moderation model") 189 | @click.option("--raw", is_flag=True, help="Output raw JSON") 190 | def chat( 191 | ctx: click.Context, 192 | message: List[Tuple[str, str]], 193 | model: str, 194 | max_tokens: int | None = None, 195 | stop: List[str] | None = None, 196 | temperature: float | None = None, 197 | top_p: float | None = None, 198 | top_k: int | None = None, 199 | repetition_penalty: float | None = None, 200 | presence_penalty: float | None = None, 201 | frequency_penalty: float | None = None, 202 | min_p: float | None = None, 203 | no_stream: bool = False, 204 | logprobs: int | None = None, 205 | echo: bool | None = None, 206 | n: int | None = None, 207 | safety_model: str | None = None, 208 | raw: bool = False, 209 | ) -> None: 210 | """Generate chat completions from messages""" 211 | client: Together = ctx.obj 212 | 213 | messages = [{"role": msg[0], "content": msg[1]} for msg in message] 214 | 215 | response = client.chat.completions.create( 216 | model=model, 217 | messages=messages, 218 | top_p=top_p, 219 | top_k=top_k, 220 | temperature=temperature, 221 | max_tokens=max_tokens, 222 | stop=stop, 223 | repetition_penalty=repetition_penalty, 224 | presence_penalty=presence_penalty, 225 | frequency_penalty=frequency_penalty, 226 | min_p=min_p, 227 | stream=not no_stream, 228 | logprobs=logprobs, 229 | echo=echo, 230 | n=n, 231 | safety_model=safety_model, 232 | ) 233 | 234 | if not no_stream: 235 | for chunk in response: 236 | # assertions for type checking 237 | assert isinstance(chunk, ChatCompletionChunk) 238 | assert chunk.choices 239 | 240 | if raw: 241 | click.echo(f"{json.dumps(chunk.model_dump(exclude_none=True))}") 242 | continue 243 | 244 | should_print_header = len(chunk.choices) > 1 245 | for stream_choice in sorted(chunk.choices, key=lambda c: c.index): # type: ignore 246 | assert isinstance(stream_choice, ChatCompletionChoicesChunk) 247 | assert stream_choice.delta 248 | 249 | if should_print_header: 250 | click.echo(f"\n===== Completion {stream_choice.index} =====\n") 251 | click.echo(f"{stream_choice.delta.content}", nl=False) 252 | 253 | if should_print_header: 254 | click.echo("\n") 255 | 256 | # new line after stream ends 257 | click.echo("\n") 258 | else: 259 | # assertions for type checking 260 | assert isinstance(response, ChatCompletionResponse) 261 | assert isinstance(response.choices, list) 262 | 263 | if raw: 264 | click.echo( 265 | f"{json.dumps(response.model_dump(exclude_none=True), indent=4)}" 266 | ) 267 | return 268 | 269 | should_print_header = len(response.choices) > 1 270 | for i, choice in enumerate(response.choices): 271 | if should_print_header: 272 | click.echo(f"===== Completion {i} =====") 273 | click.echo(choice.message.content) # type: ignore 274 | 275 | if should_print_header: 276 | click.echo("\n") 277 | -------------------------------------------------------------------------------- /src/together/cli/api/completions.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | from typing import List 5 | 6 | import click 7 | 8 | from together import Together 9 | from together.types import CompletionChunk 10 | from together.types.completions import CompletionChoicesChunk, CompletionResponse 11 | 12 | 13 | @click.command() 14 | @click.pass_context 15 | @click.argument("prompt", type=str, required=True) 16 | @click.option("--model", type=str, required=True, help="Model name") 17 | @click.option("--max-tokens", type=int, help="Max tokens to generate") 18 | @click.option( 19 | "--stop", type=str, multiple=True, help="List of strings to stop generation" 20 | ) 21 | @click.option("--temperature", type=float, help="Sampling temperature") 22 | @click.option("--top-p", type=int, help="Top p sampling") 23 | @click.option("--top-k", type=float, help="Top k sampling") 24 | @click.option("--repetition-penalty", type=float, help="Repetition penalty") 25 | @click.option("--presence-penalty", type=float, help="Presence penalty") 26 | @click.option("--frequency-penalty", type=float, help="Frequency penalty") 27 | @click.option("--min-p", type=float, help="Minimum p") 28 | @click.option("--no-stream", is_flag=True, help="Disable streaming") 29 | @click.option("--logprobs", type=int, help="Return logprobs. Only works with --raw.") 30 | @click.option("--echo", is_flag=True, help="Echo prompt. Only works with --raw.") 31 | @click.option("--n", type=int, help="Number of output generations") 32 | @click.option("--safety-model", type=str, help="Moderation model") 33 | @click.option("--raw", is_flag=True, help="Return raw JSON response") 34 | def completions( 35 | ctx: click.Context, 36 | prompt: str, 37 | model: str, 38 | max_tokens: int | None = 512, 39 | stop: List[str] | None = None, 40 | temperature: float | None = None, 41 | top_p: float | None = None, 42 | top_k: int | None = None, 43 | repetition_penalty: float | None = None, 44 | presence_penalty: float | None = None, 45 | frequency_penalty: float | None = None, 46 | min_p: float | None = None, 47 | no_stream: bool = False, 48 | logprobs: int | None = None, 49 | echo: bool | None = None, 50 | n: int | None = None, 51 | safety_model: str | None = None, 52 | raw: bool = False, 53 | ) -> None: 54 | """Generate text completions""" 55 | client: Together = ctx.obj 56 | 57 | response = client.completions.create( 58 | model=model, 59 | prompt=prompt, 60 | top_p=top_p, 61 | top_k=top_k, 62 | temperature=temperature, 63 | max_tokens=max_tokens, 64 | stop=stop, 65 | repetition_penalty=repetition_penalty, 66 | presence_penalty=presence_penalty, 67 | frequency_penalty=frequency_penalty, 68 | min_p=min_p, 69 | stream=not no_stream, 70 | logprobs=logprobs, 71 | echo=echo, 72 | n=n, 73 | safety_model=safety_model, 74 | ) 75 | 76 | if not no_stream: 77 | for chunk in response: 78 | # assertions for type checking 79 | assert isinstance(chunk, CompletionChunk) 80 | assert chunk.choices 81 | 82 | if raw: 83 | click.echo(f"{json.dumps(chunk.model_dump(exclude_none=True))}") 84 | continue 85 | 86 | should_print_header = len(chunk.choices) > 1 87 | for stream_choice in sorted(chunk.choices, key=lambda c: c.index): # type: ignore 88 | # assertions for type checking 89 | assert isinstance(stream_choice, CompletionChoicesChunk) 90 | assert stream_choice.delta 91 | 92 | if should_print_header: 93 | click.echo(f"\n===== Completion {stream_choice.index} =====\n") 94 | click.echo(f"{stream_choice.delta.content}", nl=False) 95 | 96 | if should_print_header: 97 | click.echo("\n") 98 | 99 | # new line after stream ends 100 | click.echo("\n") 101 | else: 102 | # assertions for type checking 103 | assert isinstance(response, CompletionResponse) 104 | assert isinstance(response.choices, list) 105 | 106 | if raw: 107 | click.echo( 108 | f"{json.dumps(response.model_dump(exclude_none=True), indent=4)}" 109 | ) 110 | return 111 | 112 | should_print_header = len(response.choices) > 1 113 | for i, choice in enumerate(response.choices): 114 | if should_print_header: 115 | click.echo(f"===== Completion {i} =====") 116 | click.echo(choice.text) 117 | 118 | if should_print_header or not choice.text.endswith("\n"): 119 | click.echo("\n") 120 | -------------------------------------------------------------------------------- /src/together/cli/api/files.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pathlib 3 | from textwrap import wrap 4 | 5 | import click 6 | from tabulate import tabulate 7 | 8 | from together import Together 9 | from together.types import FilePurpose 10 | from together.utils import check_file, convert_bytes, convert_unix_timestamp 11 | 12 | 13 | @click.group() 14 | @click.pass_context 15 | def files(ctx: click.Context) -> None: 16 | """File API commands""" 17 | pass 18 | 19 | 20 | @files.command() 21 | @click.pass_context 22 | @click.argument( 23 | "file", 24 | type=click.Path( 25 | exists=True, file_okay=True, resolve_path=True, readable=True, dir_okay=False 26 | ), 27 | required=True, 28 | ) 29 | @click.option( 30 | "--purpose", 31 | type=str, 32 | default=FilePurpose.FineTune.value, 33 | help="Purpose of file upload. Acceptable values in enum `together.types.FilePurpose`. Defaults to `fine-tunes`.", 34 | ) 35 | @click.option( 36 | "--check/--no-check", 37 | default=True, 38 | help="Whether to check the file before uploading.", 39 | ) 40 | def upload(ctx: click.Context, file: pathlib.Path, purpose: str, check: bool) -> None: 41 | """Upload file""" 42 | 43 | client: Together = ctx.obj 44 | 45 | response = client.files.upload(file=file, purpose=purpose, check=check) 46 | 47 | click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4)) 48 | 49 | 50 | @files.command() 51 | @click.pass_context 52 | def list(ctx: click.Context) -> None: 53 | """List files""" 54 | client: Together = ctx.obj 55 | 56 | response = client.files.list() 57 | 58 | display_list = [] 59 | for i in response.data or []: 60 | display_list.append( 61 | { 62 | "File name": "\n".join(wrap(i.filename or "", width=30)), 63 | "File ID": i.id, 64 | "Size": convert_bytes( 65 | float(str(i.bytes)) 66 | ), # convert to string for mypy typing 67 | "Created At": convert_unix_timestamp(i.created_at or 0), 68 | "Line Count": i.line_count, 69 | } 70 | ) 71 | table = tabulate(display_list, headers="keys", tablefmt="grid", showindex=True) 72 | 73 | click.echo(table) 74 | 75 | 76 | @files.command() 77 | @click.pass_context 78 | @click.argument("id", type=str, required=True) 79 | def retrieve(ctx: click.Context, id: str) -> None: 80 | """Upload file""" 81 | 82 | client: Together = ctx.obj 83 | 84 | response = client.files.retrieve(id=id) 85 | 86 | click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4)) 87 | 88 | 89 | @files.command() 90 | @click.pass_context 91 | @click.argument("id", type=str, required=True) 92 | @click.option("--output", type=str, default=None, help="Output filename") 93 | def retrieve_content(ctx: click.Context, id: str, output: str) -> None: 94 | """Retrieve file content and output to file""" 95 | 96 | client: Together = ctx.obj 97 | 98 | response = client.files.retrieve_content(id=id, output=output) 99 | 100 | click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4)) 101 | 102 | 103 | @files.command() 104 | @click.pass_context 105 | @click.argument("id", type=str, required=True) 106 | def delete(ctx: click.Context, id: str) -> None: 107 | """Delete remote file""" 108 | 109 | client: Together = ctx.obj 110 | 111 | response = client.files.delete(id=id) 112 | 113 | click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4)) 114 | 115 | 116 | @files.command() 117 | @click.pass_context 118 | @click.argument( 119 | "file", 120 | type=click.Path( 121 | exists=True, file_okay=True, resolve_path=True, readable=True, dir_okay=False 122 | ), 123 | required=True, 124 | ) 125 | def check(ctx: click.Context, file: pathlib.Path) -> None: 126 | """Check file for issues""" 127 | 128 | report = check_file(file) 129 | 130 | click.echo(json.dumps(report, indent=4)) 131 | -------------------------------------------------------------------------------- /src/together/cli/api/images.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import pathlib 3 | import requests 4 | 5 | import click 6 | from PIL import Image 7 | 8 | from together import Together 9 | from together.types import ImageResponse 10 | from together.types.images import ImageChoicesData 11 | 12 | 13 | @click.group() 14 | @click.pass_context 15 | def images(ctx: click.Context) -> None: 16 | """Images generations API commands""" 17 | pass 18 | 19 | 20 | @images.command() 21 | @click.pass_context 22 | @click.argument("prompt", type=str, required=True) 23 | @click.option("--model", type=str, required=True, help="Model name") 24 | @click.option("--steps", type=int, default=20, help="Number of steps to run generation") 25 | @click.option("--seed", type=int, default=None, help="Random seed") 26 | @click.option("--n", type=int, default=1, help="Number of images to generate") 27 | @click.option("--height", type=int, default=1024, help="Image height") 28 | @click.option("--width", type=int, default=1024, help="Image width") 29 | @click.option("--negative-prompt", type=str, default=None, help="Negative prompt") 30 | @click.option( 31 | "--output", 32 | type=click.Path(exists=True, file_okay=False, resolve_path=True), 33 | required=False, 34 | default=pathlib.Path("."), 35 | help="Output directory", 36 | ) 37 | @click.option("--prefix", type=str, required=False, default="image-") 38 | @click.option("--no-show", is_flag=True, help="Do not open images in viewer") 39 | def generate( 40 | ctx: click.Context, 41 | prompt: str, 42 | model: str, 43 | steps: int, 44 | seed: int, 45 | n: int, 46 | height: int, 47 | width: int, 48 | negative_prompt: str, 49 | output: pathlib.Path, 50 | prefix: str, 51 | no_show: bool, 52 | ) -> None: 53 | """Generate image""" 54 | 55 | client: Together = ctx.obj 56 | 57 | response = client.images.generate( 58 | prompt=prompt, 59 | model=model, 60 | steps=steps, 61 | seed=seed, 62 | n=n, 63 | height=height, 64 | width=width, 65 | negative_prompt=negative_prompt, 66 | ) 67 | 68 | assert isinstance(response, ImageResponse) 69 | assert isinstance(response.data, list) 70 | 71 | for i, choice in enumerate(response.data): 72 | assert isinstance(choice, ImageChoicesData) 73 | 74 | data = None 75 | if choice.b64_json: 76 | data = base64.b64decode(choice.b64_json) 77 | elif choice.url: 78 | data = requests.get(choice.url).content 79 | 80 | if not data: 81 | click.echo(f"Image [{i + 1}/{len(response.data)}] is empty") 82 | continue 83 | 84 | with open(f"{output}/{prefix}{choice.index}.png", "wb") as f: 85 | f.write(data) 86 | 87 | click.echo( 88 | f"Image [{i + 1}/{len(response.data)}] saved to {output}/{prefix}{choice.index}.png" 89 | ) 90 | 91 | if not no_show: 92 | image = Image.open(f"{output}/{prefix}{choice.index}.png") 93 | image.show() 94 | -------------------------------------------------------------------------------- /src/together/cli/api/models.py: -------------------------------------------------------------------------------- 1 | import json as json_lib 2 | 3 | import click 4 | from tabulate import tabulate 5 | 6 | from together import Together 7 | from together.types.models import ModelObject 8 | 9 | 10 | @click.group() 11 | @click.pass_context 12 | def models(ctx: click.Context) -> None: 13 | """Models API commands""" 14 | pass 15 | 16 | 17 | @models.command() 18 | @click.option( 19 | "--type", 20 | type=click.Choice(["dedicated"]), 21 | help="Filter models by type (dedicated: models that can be deployed as dedicated endpoints)", 22 | ) 23 | @click.option( 24 | "--json", 25 | is_flag=True, 26 | help="Output in JSON format", 27 | ) 28 | @click.pass_context 29 | def list(ctx: click.Context, type: str | None, json: bool) -> None: 30 | """List models""" 31 | client: Together = ctx.obj 32 | 33 | response = client.models.list(dedicated=(type == "dedicated")) 34 | 35 | display_list = [] 36 | 37 | model: ModelObject 38 | for model in response: 39 | display_list.append( 40 | { 41 | "ID": model.id, 42 | "Name": model.display_name, 43 | "Organization": model.organization, 44 | "Type": model.type, 45 | "Context Length": model.context_length, 46 | "License": model.license, 47 | "Input per 1M token": model.pricing.input, 48 | "Output per 1M token": model.pricing.output, 49 | } 50 | ) 51 | 52 | if json: 53 | click.echo(json_lib.dumps(display_list, indent=2)) 54 | else: 55 | click.echo(tabulate(display_list, headers="keys", tablefmt="plain")) 56 | -------------------------------------------------------------------------------- /src/together/cli/api/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from gettext import gettext as _ 4 | from typing import Literal 5 | 6 | import click 7 | 8 | 9 | class AutoIntParamType(click.ParamType): 10 | name = "integer_or_max" 11 | _number_class = int 12 | 13 | def convert( 14 | self, value: str, param: click.Parameter | None, ctx: click.Context | None 15 | ) -> int | Literal["max"] | None: 16 | if value == "max": 17 | return "max" 18 | try: 19 | return int(value) 20 | except ValueError: 21 | self.fail( 22 | _("{value!r} is not a valid {number_type}.").format( 23 | value=value, number_type=self.name 24 | ), 25 | param, 26 | ctx, 27 | ) 28 | 29 | 30 | class BooleanWithAutoParamType(click.ParamType): 31 | name = "boolean_or_auto" 32 | 33 | def convert( 34 | self, value: str, param: click.Parameter | None, ctx: click.Context | None 35 | ) -> bool | Literal["auto"] | None: 36 | if value == "auto": 37 | return "auto" 38 | try: 39 | return bool(value) 40 | except ValueError: 41 | self.fail( 42 | _("{value!r} is not a valid {type}.").format( 43 | value=value, type=self.name 44 | ), 45 | param, 46 | ctx, 47 | ) 48 | 49 | 50 | INT_WITH_MAX = AutoIntParamType() 51 | BOOL_WITH_AUTO = BooleanWithAutoParamType() 52 | -------------------------------------------------------------------------------- /src/together/cli/cli.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import Any 5 | 6 | import click 7 | 8 | import together 9 | from together.cli.api.chat import chat, interactive 10 | from together.cli.api.completions import completions 11 | from together.cli.api.endpoints import endpoints 12 | from together.cli.api.files import files 13 | from together.cli.api.finetune import fine_tuning 14 | from together.cli.api.images import images 15 | from together.cli.api.models import models 16 | from together.constants import MAX_RETRIES, TIMEOUT_SECS 17 | 18 | 19 | def print_version(ctx: click.Context, params: Any, value: Any) -> None: 20 | if not value or ctx.resilient_parsing: 21 | return 22 | click.echo(f"Version {together.version}") 23 | ctx.exit() 24 | 25 | 26 | @click.group() 27 | @click.pass_context 28 | @click.option( 29 | "--api-key", 30 | type=str, 31 | help="API Key. Defaults to environment variable `TOGETHER_API_KEY`", 32 | default=os.getenv("TOGETHER_API_KEY"), 33 | ) 34 | @click.option( 35 | "--base-url", type=str, help="API Base URL. Defaults to Together AI endpoint." 36 | ) 37 | @click.option( 38 | "--timeout", type=int, help=f"Request timeout. Defaults to {TIMEOUT_SECS} seconds" 39 | ) 40 | @click.option( 41 | "--max-retries", 42 | type=int, 43 | help=f"Maximum number of HTTP retries. Defaults to {MAX_RETRIES}.", 44 | ) 45 | @click.option( 46 | "--version", 47 | is_flag=True, 48 | callback=print_version, 49 | expose_value=False, 50 | is_eager=True, 51 | help="Print version", 52 | ) 53 | @click.option("--debug", help="Debug mode", is_flag=True) 54 | def main( 55 | ctx: click.Context, 56 | api_key: str | None, 57 | base_url: str | None, 58 | timeout: int | None, 59 | max_retries: int | None, 60 | debug: bool | None, 61 | ) -> None: 62 | """This is a sample CLI tool.""" 63 | together.log = "debug" if debug else None 64 | ctx.obj = together.Together( 65 | api_key=api_key, base_url=base_url, timeout=timeout, max_retries=max_retries 66 | ) 67 | 68 | 69 | main.add_command(chat) 70 | main.add_command(interactive) 71 | main.add_command(completions) 72 | main.add_command(images) 73 | main.add_command(files) 74 | main.add_command(fine_tuning) 75 | main.add_command(models) 76 | main.add_command(endpoints) 77 | 78 | if __name__ == "__main__": 79 | main() 80 | -------------------------------------------------------------------------------- /src/together/client.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import sys 5 | from typing import Dict, TYPE_CHECKING 6 | 7 | from together import resources 8 | from together.constants import BASE_URL, MAX_RETRIES, TIMEOUT_SECS 9 | from together.error import AuthenticationError 10 | from together.resources.code_interpreter import CodeInterpreter 11 | from together.types import TogetherClient 12 | from together.utils import enforce_trailing_slash 13 | from together.utils.api_helpers import get_google_colab_secret 14 | 15 | 16 | class Together: 17 | completions: resources.Completions 18 | chat: resources.Chat 19 | embeddings: resources.Embeddings 20 | files: resources.Files 21 | images: resources.Images 22 | models: resources.Models 23 | fine_tuning: resources.FineTuning 24 | rerank: resources.Rerank 25 | audio: resources.Audio 26 | code_interpreter: CodeInterpreter 27 | 28 | # client options 29 | client: TogetherClient 30 | 31 | def __init__( 32 | self, 33 | *, 34 | api_key: str | None = None, 35 | base_url: str | None = None, 36 | timeout: float | None = None, 37 | max_retries: int | None = None, 38 | supplied_headers: Dict[str, str] | None = None, 39 | ) -> None: 40 | """Construct a new synchronous together client instance. 41 | 42 | This automatically infers the following arguments from their corresponding environment variables if they are not provided: 43 | - `api_key` from `TOGETHER_API_KEY` 44 | - `base_url` from `TOGETHER_BASE_URL` 45 | """ 46 | 47 | # get api key 48 | if not api_key: 49 | api_key = os.environ.get("TOGETHER_API_KEY") 50 | 51 | if not api_key and "google.colab" in sys.modules: 52 | api_key = get_google_colab_secret("TOGETHER_API_KEY") 53 | 54 | if not api_key: 55 | raise AuthenticationError( 56 | "The api_key client option must be set either by passing api_key to the client or by setting the " 57 | "TOGETHER_API_KEY environment variable" 58 | ) 59 | 60 | # get base url 61 | if not base_url: 62 | base_url = os.environ.get("TOGETHER_BASE_URL") 63 | 64 | if not base_url: 65 | base_url = BASE_URL 66 | 67 | if timeout is None: 68 | timeout = TIMEOUT_SECS 69 | 70 | if max_retries is None: 71 | max_retries = MAX_RETRIES 72 | 73 | # TogetherClient object 74 | self.client = TogetherClient( 75 | api_key=api_key, 76 | base_url=enforce_trailing_slash(base_url), 77 | timeout=timeout, 78 | max_retries=max_retries, 79 | supplied_headers=supplied_headers, 80 | ) 81 | 82 | self.completions = resources.Completions(self.client) 83 | self.chat = resources.Chat(self.client) 84 | self.embeddings = resources.Embeddings(self.client) 85 | self.files = resources.Files(self.client) 86 | self.images = resources.Images(self.client) 87 | self.models = resources.Models(self.client) 88 | self.fine_tuning = resources.FineTuning(self.client) 89 | self.rerank = resources.Rerank(self.client) 90 | self.audio = resources.Audio(self.client) 91 | self.endpoints = resources.Endpoints(self.client) 92 | self.code_interpreter = CodeInterpreter(self.client) 93 | 94 | 95 | class AsyncTogether: 96 | completions: resources.AsyncCompletions 97 | chat: resources.AsyncChat 98 | embeddings: resources.AsyncEmbeddings 99 | files: resources.AsyncFiles 100 | images: resources.AsyncImages 101 | models: resources.AsyncModels 102 | fine_tuning: resources.AsyncFineTuning 103 | rerank: resources.AsyncRerank 104 | code_interpreter: CodeInterpreter 105 | 106 | # client options 107 | client: TogetherClient 108 | 109 | def __init__( 110 | self, 111 | *, 112 | api_key: str | None = None, 113 | base_url: str | None = None, 114 | timeout: float | None = None, 115 | max_retries: int | None = None, 116 | supplied_headers: Dict[str, str] | None = None, 117 | ) -> None: 118 | """Construct a new async together client instance. 119 | 120 | This automatically infers the following arguments from their corresponding environment variables if they are not provided: 121 | - `api_key` from `TOGETHER_API_KEY` 122 | - `base_url` from `TOGETHER_BASE_URL` 123 | """ 124 | 125 | # get api key 126 | if not api_key: 127 | api_key = os.environ.get("TOGETHER_API_KEY") 128 | 129 | if not api_key and "google.colab" in sys.modules: 130 | api_key = get_google_colab_secret("TOGETHER_API_KEY") 131 | 132 | if not api_key: 133 | raise AuthenticationError( 134 | "The api_key client option must be set either by passing api_key to the client or by setting the " 135 | "TOGETHER_API_KEY environment variable" 136 | ) 137 | 138 | # get base url 139 | if not base_url: 140 | base_url = os.environ.get("TOGETHER_BASE_URL") 141 | 142 | if not base_url: 143 | base_url = BASE_URL 144 | 145 | if timeout is None: 146 | timeout = TIMEOUT_SECS 147 | 148 | if max_retries is None: 149 | max_retries = MAX_RETRIES 150 | 151 | # TogetherClient object 152 | self.client = TogetherClient( 153 | api_key=api_key, 154 | base_url=enforce_trailing_slash(base_url), 155 | timeout=timeout, 156 | max_retries=max_retries, 157 | supplied_headers=supplied_headers, 158 | ) 159 | 160 | self.completions = resources.AsyncCompletions(self.client) 161 | self.chat = resources.AsyncChat(self.client) 162 | self.embeddings = resources.AsyncEmbeddings(self.client) 163 | self.files = resources.AsyncFiles(self.client) 164 | self.images = resources.AsyncImages(self.client) 165 | self.models = resources.AsyncModels(self.client) 166 | self.fine_tuning = resources.AsyncFineTuning(self.client) 167 | self.rerank = resources.AsyncRerank(self.client) 168 | self.code_interpreter = CodeInterpreter(self.client) 169 | 170 | 171 | Client = Together 172 | 173 | AsyncClient = AsyncTogether 174 | -------------------------------------------------------------------------------- /src/together/constants.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | # Session constants 4 | TIMEOUT_SECS = 600 5 | MAX_SESSION_LIFETIME_SECS = 180 6 | MAX_CONNECTION_RETRIES = 2 7 | MAX_RETRIES = 5 8 | INITIAL_RETRY_DELAY = 0.5 9 | MAX_RETRY_DELAY = 8.0 10 | 11 | # API defaults 12 | BASE_URL = "https://api.together.xyz/v1" 13 | 14 | # Download defaults 15 | DOWNLOAD_BLOCK_SIZE = 10 * 1024 * 1024 # 10 MB 16 | DISABLE_TQDM = False 17 | 18 | # Messages 19 | MISSING_API_KEY_MESSAGE = """TOGETHER_API_KEY not found. 20 | Please set it as an environment variable or set it as together.api_key 21 | Find your TOGETHER_API_KEY at https://api.together.xyz/settings/api-keys""" 22 | 23 | # Minimum number of samples required for fine-tuning file 24 | MIN_SAMPLES = 1 25 | 26 | # the number of bytes in a gigabyte, used to convert bytes to GB for readable comparison 27 | NUM_BYTES_IN_GB = 2**30 28 | 29 | # maximum number of GB sized files we support finetuning for 30 | MAX_FILE_SIZE_GB = 4.9 31 | 32 | # expected columns for Parquet files 33 | PARQUET_EXPECTED_COLUMNS = ["input_ids", "attention_mask", "labels"] 34 | 35 | 36 | class DatasetFormat(enum.Enum): 37 | """Dataset format enum.""" 38 | 39 | GENERAL = "general" 40 | CONVERSATION = "conversation" 41 | INSTRUCTION = "instruction" 42 | PREFERENCE_OPENAI = "preference_openai" 43 | 44 | 45 | JSONL_REQUIRED_COLUMNS_MAP = { 46 | DatasetFormat.GENERAL: ["text"], 47 | DatasetFormat.CONVERSATION: ["messages"], 48 | DatasetFormat.INSTRUCTION: ["prompt", "completion"], 49 | DatasetFormat.PREFERENCE_OPENAI: [ 50 | "input", 51 | "preferred_output", 52 | "non_preferred_output", 53 | ], 54 | } 55 | REQUIRED_COLUMNS_MESSAGE = ["role", "content"] 56 | POSSIBLE_ROLES_CONVERSATION = ["system", "user", "assistant"] 57 | -------------------------------------------------------------------------------- /src/together/error.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | from typing import Any, Dict 5 | 6 | from requests import RequestException 7 | 8 | from together.types.error import TogetherErrorResponse 9 | 10 | 11 | class TogetherException(Exception): 12 | def __init__( 13 | self, 14 | message: ( 15 | TogetherErrorResponse | Exception | str | RequestException | None 16 | ) = None, 17 | headers: str | Dict[Any, Any] | None = None, 18 | request_id: str | None = None, 19 | http_status: int | None = None, 20 | ) -> None: 21 | if isinstance(message, TogetherErrorResponse): 22 | self.api_response = message 23 | 24 | _message = ( 25 | json.dumps(message.model_dump(exclude_none=True)) 26 | if isinstance(message, TogetherErrorResponse) 27 | else message 28 | ) 29 | if http_status is not None: 30 | self._message = f"Error code: {http_status} - {_message}" 31 | else: 32 | self._message = str(_message) 33 | 34 | super().__init__(self._message) 35 | 36 | self.http_status = http_status 37 | self.headers = headers or {} 38 | self.request_id = request_id 39 | 40 | def __repr__(self) -> str: 41 | repr_message = json.dumps( 42 | { 43 | "response": self._message, 44 | "status": self.http_status, 45 | "request_id": self.request_id, 46 | "headers": self.headers, 47 | } 48 | ) 49 | return "%s(%r)" % (self.__class__.__name__, repr_message) 50 | 51 | 52 | class AuthenticationError(TogetherException): 53 | def __init__( 54 | self, 55 | message: ( 56 | TogetherErrorResponse | Exception | str | RequestException | None 57 | ) = None, 58 | **kwargs: Any, 59 | ) -> None: 60 | super().__init__(message=message, **kwargs) 61 | 62 | 63 | class ResponseError(TogetherException): 64 | def __init__( 65 | self, 66 | message: ( 67 | TogetherErrorResponse | Exception | str | RequestException | None 68 | ) = None, 69 | **kwargs: Any, 70 | ) -> None: 71 | super().__init__(message=message, **kwargs) 72 | 73 | 74 | class JSONError(TogetherException): 75 | def __init__( 76 | self, 77 | message: ( 78 | TogetherErrorResponse | Exception | str | RequestException | None 79 | ) = None, 80 | **kwargs: Any, 81 | ) -> None: 82 | super().__init__(message=message, **kwargs) 83 | 84 | 85 | class InstanceError(TogetherException): 86 | def __init__(self, model: str | None = "model", **kwargs: Any) -> None: 87 | super().__init__(**kwargs) 88 | self.message = f"""No running instances for {model}. 89 | You can start an instance with one of the following methods: 90 | 1. navigating to the Together Playground at api.together.ai 91 | 2. starting one in python using together.Models.start(model_name) 92 | 3. `$ together models start ` at the command line. 93 | See `together.Models.list()` in python or `$ together models list` in command line 94 | to get an updated list of valid model names. 95 | """ 96 | 97 | 98 | class RateLimitError(TogetherException): 99 | def __init__( 100 | self, 101 | message: ( 102 | TogetherErrorResponse | Exception | str | RequestException | None 103 | ) = None, 104 | **kwargs: Any, 105 | ) -> None: 106 | super().__init__(message=message, **kwargs) 107 | 108 | 109 | class FileTypeError(TogetherException): 110 | def __init__( 111 | self, 112 | message: ( 113 | TogetherErrorResponse | Exception | str | RequestException | None 114 | ) = None, 115 | **kwargs: Any, 116 | ) -> None: 117 | super().__init__(message=message, **kwargs) 118 | 119 | 120 | class AttributeError(TogetherException): 121 | def __init__( 122 | self, 123 | message: ( 124 | TogetherErrorResponse | Exception | str | RequestException | None 125 | ) = None, 126 | **kwargs: Any, 127 | ) -> None: 128 | super().__init__(message=message, **kwargs) 129 | 130 | 131 | class Timeout(TogetherException): 132 | def __init__( 133 | self, 134 | message: ( 135 | TogetherErrorResponse | Exception | str | RequestException | None 136 | ) = None, 137 | **kwargs: Any, 138 | ) -> None: 139 | super().__init__(message=message, **kwargs) 140 | 141 | 142 | class APIConnectionError(TogetherException): 143 | def __init__( 144 | self, 145 | message: ( 146 | TogetherErrorResponse | Exception | str | RequestException | None 147 | ) = None, 148 | **kwargs: Any, 149 | ) -> None: 150 | super().__init__(message=message, **kwargs) 151 | 152 | 153 | class InvalidRequestError(TogetherException): 154 | def __init__( 155 | self, 156 | message: ( 157 | TogetherErrorResponse | Exception | str | RequestException | None 158 | ) = None, 159 | **kwargs: Any, 160 | ) -> None: 161 | super().__init__(message=message, **kwargs) 162 | 163 | 164 | class APIError(TogetherException): 165 | def __init__( 166 | self, 167 | message: ( 168 | TogetherErrorResponse | Exception | str | RequestException | None 169 | ) = None, 170 | **kwargs: Any, 171 | ) -> None: 172 | super().__init__(message=message, **kwargs) 173 | 174 | 175 | class ServiceUnavailableError(TogetherException): 176 | def __init__( 177 | self, 178 | message: ( 179 | TogetherErrorResponse | Exception | str | RequestException | None 180 | ) = None, 181 | **kwargs: Any, 182 | ) -> None: 183 | super().__init__(message=message, **kwargs) 184 | 185 | 186 | class DownloadError(TogetherException): 187 | def __init__( 188 | self, 189 | message: ( 190 | TogetherErrorResponse | Exception | str | RequestException | None 191 | ) = None, 192 | **kwargs: Any, 193 | ) -> None: 194 | super().__init__(message=message, **kwargs) 195 | -------------------------------------------------------------------------------- /src/together/legacy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/togethercomputer/together-python/554d3e820ffe07aadff258410aa7cedadeb2fc51/src/together/legacy/__init__.py -------------------------------------------------------------------------------- /src/together/legacy/base.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import warnings 3 | 4 | 5 | API_KEY_WARNING = ( 6 | "The use of together.api_key is deprecated and will be removed in the next major release. " 7 | "Please set the TOGETHER_API_KEY environment variable instead." 8 | ) 9 | 10 | 11 | def deprecated(func): # type: ignore 12 | """ 13 | This is a decorator which can be used to mark functions 14 | as deprecated. It will result in a warning being emitted 15 | when the function is used. 16 | """ 17 | 18 | @functools.wraps(func) 19 | def new_func(*args, **kwargs): # type: ignore 20 | warnings.warn( 21 | f"Call to deprecated function {func.__name__}.", 22 | category=DeprecationWarning, 23 | stacklevel=2, 24 | ) 25 | return func(*args, **kwargs) 26 | 27 | return new_func 28 | -------------------------------------------------------------------------------- /src/together/legacy/complete.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import warnings 4 | from typing import Any, AsyncGenerator, Dict, Iterator 5 | 6 | import together 7 | from together.legacy.base import API_KEY_WARNING, deprecated 8 | from together.types import CompletionChunk, CompletionResponse 9 | 10 | 11 | class Complete: 12 | @classmethod 13 | @deprecated # type: ignore 14 | def create( 15 | cls, 16 | prompt: str, 17 | **kwargs: Any, 18 | ) -> Dict[str, Any]: 19 | """Legacy completion function.""" 20 | 21 | api_key = None 22 | if together.api_key: 23 | warnings.warn(API_KEY_WARNING) 24 | api_key = together.api_key 25 | 26 | client = together.Together(api_key=api_key) 27 | 28 | result = client.completions.create(prompt=prompt, stream=False, **kwargs) 29 | 30 | assert isinstance(result, CompletionResponse) 31 | 32 | return result.model_dump(exclude_none=True) 33 | 34 | @classmethod 35 | @deprecated # type: ignore 36 | def create_streaming( 37 | cls, 38 | prompt: str, 39 | **kwargs: Any, 40 | ) -> Iterator[Dict[str, Any]]: 41 | """Legacy streaming completion function.""" 42 | 43 | api_key = None 44 | if together.api_key: 45 | warnings.warn(API_KEY_WARNING) 46 | api_key = together.api_key 47 | 48 | client = together.Together(api_key=api_key) 49 | 50 | return ( 51 | token.model_dump(exclude_none=True) # type: ignore 52 | for token in client.completions.create(prompt=prompt, stream=True, **kwargs) 53 | ) 54 | 55 | 56 | class Completion: 57 | @classmethod 58 | @deprecated # type: ignore 59 | def create( 60 | cls, 61 | prompt: str, 62 | **kwargs: Any, 63 | ) -> CompletionResponse | Iterator[CompletionChunk]: 64 | """Completion function.""" 65 | 66 | api_key = None 67 | if together.api_key: 68 | warnings.warn(API_KEY_WARNING) 69 | api_key = together.api_key 70 | 71 | client = together.Together(api_key=api_key) 72 | 73 | return client.completions.create(prompt=prompt, **kwargs) 74 | 75 | 76 | class AsyncComplete: 77 | @classmethod 78 | @deprecated # type: ignore 79 | async def create( 80 | cls, 81 | prompt: str, 82 | **kwargs: Any, 83 | ) -> CompletionResponse | AsyncGenerator[CompletionChunk, None]: 84 | """Async completion function.""" 85 | 86 | api_key = None 87 | if together.api_key: 88 | warnings.warn(API_KEY_WARNING) 89 | api_key = together.api_key 90 | 91 | client = together.AsyncTogether(api_key=api_key) 92 | 93 | return await client.completions.create(prompt=prompt, **kwargs) 94 | -------------------------------------------------------------------------------- /src/together/legacy/embeddings.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from typing import Any, Dict 3 | 4 | import together 5 | from together.legacy.base import API_KEY_WARNING, deprecated 6 | 7 | 8 | class Embeddings: 9 | @classmethod 10 | @deprecated # type: ignore 11 | def create( 12 | cls, 13 | input: str, 14 | **kwargs: Any, 15 | ) -> Dict[str, Any]: 16 | """Legacy embeddings function.""" 17 | 18 | api_key = None 19 | if together.api_key: 20 | warnings.warn(API_KEY_WARNING) 21 | api_key = together.api_key 22 | 23 | client = together.Together(api_key=api_key) 24 | 25 | return client.embeddings.create(input=input, **kwargs).model_dump( 26 | exclude_none=True 27 | ) 28 | -------------------------------------------------------------------------------- /src/together/legacy/files.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import warnings 5 | from typing import Any, Dict, List 6 | 7 | import together 8 | from together.legacy.base import API_KEY_WARNING, deprecated 9 | from together.utils.files import check_file as check_json 10 | 11 | 12 | class Files: 13 | @classmethod 14 | @deprecated # type: ignore 15 | def list( 16 | cls, 17 | ) -> Dict[str, Any]: 18 | """Legacy file list function.""" 19 | 20 | api_key = None 21 | if together.api_key: 22 | warnings.warn(API_KEY_WARNING) 23 | api_key = together.api_key 24 | 25 | client = together.Together(api_key=api_key) 26 | 27 | return client.files.list().model_dump(exclude_none=True) 28 | 29 | @classmethod 30 | def check(self, file: str) -> Dict[str, object]: 31 | return check_json(file) 32 | 33 | @classmethod 34 | @deprecated # type: ignore 35 | def upload( 36 | cls, 37 | file: str, 38 | check: bool = True, 39 | ) -> Dict[str, Any]: 40 | """Legacy file upload function.""" 41 | 42 | api_key = None 43 | if together.api_key: 44 | warnings.warn(API_KEY_WARNING) 45 | api_key = together.api_key 46 | 47 | if check: 48 | report_dict = check_json(file) 49 | if not report_dict["is_check_passed"]: 50 | raise together.error.FileTypeError( 51 | f"Invalid file supplied. Failed to upload.\nReport:\n {report_dict}" 52 | ) 53 | 54 | client = together.Together(api_key=api_key) 55 | 56 | # disabling the check, because it was run previously 57 | response = client.files.upload(file=file, check=False).model_dump( 58 | exclude_none=True 59 | ) 60 | 61 | if check: 62 | response["report_dict"] = report_dict 63 | 64 | return response 65 | 66 | @classmethod 67 | @deprecated # type: ignore 68 | def delete( 69 | cls, 70 | file_id: str, 71 | ) -> Dict[str, Any]: 72 | """Legacy file delete function.""" 73 | 74 | api_key = None 75 | if together.api_key: 76 | warnings.warn(API_KEY_WARNING) 77 | api_key = together.api_key 78 | 79 | client = together.Together(api_key=api_key) 80 | 81 | return client.files.delete(id=file_id).model_dump(exclude_none=True) 82 | 83 | @classmethod 84 | @deprecated # type: ignore 85 | def retrieve( 86 | cls, 87 | file_id: str, 88 | ) -> Dict[str, Any]: 89 | """Legacy file retrieve function.""" 90 | 91 | api_key = None 92 | if together.api_key: 93 | warnings.warn(API_KEY_WARNING) 94 | api_key = together.api_key 95 | 96 | client = together.Together(api_key=api_key) 97 | 98 | return client.files.retrieve(id=file_id).model_dump(exclude_none=True) 99 | 100 | @classmethod 101 | @deprecated # type: ignore 102 | def retrieve_content( 103 | cls, 104 | file_id: str, 105 | output: str | None = None, 106 | ) -> Dict[str, Any]: 107 | """Legacy file retrieve content function.""" 108 | 109 | api_key = None 110 | if together.api_key: 111 | warnings.warn(API_KEY_WARNING) 112 | api_key = together.api_key 113 | 114 | client = together.Together(api_key=api_key) 115 | 116 | return client.files.retrieve_content(id=file_id, output=output).dict( 117 | exclude_none=True 118 | ) 119 | 120 | @classmethod 121 | @deprecated # type: ignore 122 | def save_jsonl( 123 | self, data: Dict[str, str], output_path: str, append: bool = False 124 | ) -> None: 125 | """ 126 | Write list of objects to a JSON lines file. 127 | """ 128 | mode = "a+" if append else "w" 129 | with open(output_path, mode, encoding="utf-8") as f: 130 | for line in data: 131 | json_record = json.dumps(line, ensure_ascii=False) 132 | f.write(json_record + "\n") 133 | print("Wrote {} records to {}".format(len(data), output_path)) 134 | 135 | @classmethod 136 | @deprecated # type: ignore 137 | def load_jsonl(self, input_path: str) -> List[Dict[str, str]]: 138 | """ 139 | Read list of objects from a JSON lines file. 140 | """ 141 | data = [] 142 | with open(input_path, "r", encoding="utf-8") as f: 143 | for line in f: 144 | data.append(json.loads(line.rstrip("\n|\r"))) 145 | print("Loaded {} records from {}".format(len(data), input_path)) 146 | return data 147 | -------------------------------------------------------------------------------- /src/together/legacy/finetune.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import warnings 4 | from typing import Any, Dict, List, Literal 5 | 6 | import together 7 | from together.legacy.base import API_KEY_WARNING, deprecated 8 | 9 | 10 | class Finetune: 11 | @classmethod 12 | @deprecated # type: ignore 13 | def create( 14 | cls, 15 | training_file: str, # training file_id 16 | model: str, 17 | n_epochs: int = 1, 18 | n_checkpoints: int | None = 1, 19 | batch_size: int | None = 32, 20 | learning_rate: float = 0.00001, 21 | suffix: ( 22 | str | None 23 | ) = None, # resulting finetuned model name will include the suffix 24 | estimate_price: bool = False, 25 | wandb_api_key: str | None = None, 26 | confirm_inputs: bool = False, 27 | ): 28 | api_key = None 29 | if together.api_key: 30 | warnings.warn(API_KEY_WARNING) 31 | api_key = together.api_key 32 | 33 | if estimate_price: 34 | raise ValueError("Price estimation is not supported in version >= 1.0.1") 35 | 36 | if confirm_inputs: 37 | raise ValueError("Input confirmation is not supported in version >= 1.0.1") 38 | 39 | client = together.Together(api_key=api_key) 40 | 41 | return client.fine_tuning.create( 42 | training_file=training_file, 43 | model=model, 44 | n_epochs=n_epochs, 45 | n_checkpoints=n_checkpoints, 46 | batch_size=batch_size if isinstance(batch_size, int) else "max", 47 | learning_rate=learning_rate, 48 | suffix=suffix, 49 | wandb_api_key=wandb_api_key, 50 | ).model_dump(exclude_none=True) 51 | 52 | @classmethod 53 | @deprecated # type: ignore 54 | def list( 55 | cls, 56 | ) -> Dict[str, Any]: 57 | """Legacy finetuning list function.""" 58 | 59 | api_key = None 60 | if together.api_key: 61 | warnings.warn(API_KEY_WARNING) 62 | api_key = together.api_key 63 | 64 | client = together.Together(api_key=api_key) 65 | 66 | return client.fine_tuning.list().model_dump(exclude_none=True) 67 | 68 | @classmethod 69 | @deprecated # type: ignore 70 | def retrieve( 71 | cls, 72 | fine_tune_id: str, 73 | ) -> Dict[str, Any]: 74 | """Legacy finetuning retrieve function.""" 75 | 76 | api_key = None 77 | if together.api_key: 78 | warnings.warn(API_KEY_WARNING) 79 | api_key = together.api_key 80 | 81 | client = together.Together(api_key=api_key) 82 | 83 | return client.fine_tuning.retrieve(id=fine_tune_id).model_dump( 84 | exclude_none=True 85 | ) 86 | 87 | @classmethod 88 | @deprecated # type: ignore 89 | def cancel( 90 | cls, 91 | fine_tune_id: str, 92 | ) -> Dict[str, Any]: 93 | """Legacy finetuning cancel function.""" 94 | 95 | api_key = None 96 | if together.api_key: 97 | warnings.warn(API_KEY_WARNING) 98 | api_key = together.api_key 99 | 100 | client = together.Together(api_key=api_key) 101 | 102 | return client.fine_tuning.cancel(id=fine_tune_id).model_dump(exclude_none=True) 103 | 104 | @classmethod 105 | @deprecated # type: ignore 106 | def list_events( 107 | cls, 108 | fine_tune_id: str, 109 | ) -> Dict[str, Any]: 110 | """Legacy finetuning list events function.""" 111 | 112 | api_key = None 113 | if together.api_key: 114 | warnings.warn(API_KEY_WARNING) 115 | api_key = together.api_key 116 | 117 | client = together.Together(api_key=api_key) 118 | 119 | return client.fine_tuning.list_events(id=fine_tune_id).model_dump( 120 | exclude_none=True 121 | ) 122 | 123 | @classmethod 124 | @deprecated # type: ignore 125 | def get_checkpoints( 126 | cls, 127 | fine_tune_id: str, 128 | ) -> List[Any]: 129 | """Legacy finetuning get checkpoints function.""" 130 | 131 | finetune_events = list(cls.retrieve(fine_tune_id=fine_tune_id)["events"]) 132 | 133 | saved_events = [i for i in finetune_events if i["type"] in ["CHECKPOINT_SAVE"]] 134 | 135 | return saved_events 136 | 137 | @classmethod 138 | @deprecated # type: ignore 139 | def get_job_status(cls, fine_tune_id: str) -> str: 140 | """Legacy finetuning get job status function.""" 141 | return str(cls.retrieve(fine_tune_id=fine_tune_id)["status"]) 142 | 143 | @classmethod 144 | @deprecated # type: ignore 145 | def is_final_model_available(cls, fine_tune_id: str) -> bool: 146 | """Legacy finetuning is final model available function.""" 147 | 148 | finetune_events = list(cls.retrieve(fine_tune_id=fine_tune_id)["events"]) 149 | 150 | for i in finetune_events: 151 | if i["type"] in ["JOB_COMPLETE", "JOB_ERROR"]: 152 | if i["checkpoint_path"] != "": 153 | return False 154 | else: 155 | return True 156 | return False 157 | 158 | @classmethod 159 | @deprecated # type: ignore 160 | def download( 161 | cls, 162 | fine_tune_id: str, 163 | output: str | None = None, 164 | step: int | None = None, 165 | ) -> Dict[str, Any]: 166 | """Legacy finetuning download function.""" 167 | 168 | api_key = None 169 | if together.api_key: 170 | warnings.warn(API_KEY_WARNING) 171 | api_key = together.api_key 172 | 173 | client = together.Together(api_key=api_key) 174 | 175 | return client.fine_tuning.download( 176 | id=fine_tune_id, output=output, checkpoint_step=step 177 | ).model_dump(exclude_none=True) 178 | -------------------------------------------------------------------------------- /src/together/legacy/images.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from typing import Any, Dict 3 | 4 | import together 5 | from together.legacy.base import API_KEY_WARNING, deprecated 6 | 7 | 8 | class Image: 9 | @classmethod 10 | @deprecated # type: ignore 11 | def create( 12 | cls, 13 | prompt: str, 14 | **kwargs: Any, 15 | ) -> Dict[str, Any]: 16 | """Legacy image function.""" 17 | 18 | api_key = None 19 | if together.api_key: 20 | warnings.warn(API_KEY_WARNING) 21 | api_key = together.api_key 22 | 23 | client = together.Together(api_key=api_key) 24 | 25 | return client.images.generate(prompt=prompt, **kwargs).model_dump( 26 | exclude_none=True 27 | ) 28 | -------------------------------------------------------------------------------- /src/together/legacy/models.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from typing import Any, Dict, List 3 | 4 | import together 5 | from together.legacy.base import API_KEY_WARNING, deprecated 6 | 7 | 8 | class Models: 9 | @classmethod 10 | @deprecated # type: ignore 11 | def list( 12 | cls, 13 | ) -> List[Dict[str, Any]]: 14 | """Legacy model list function.""" 15 | 16 | api_key = None 17 | if together.api_key: 18 | warnings.warn(API_KEY_WARNING) 19 | api_key = together.api_key 20 | 21 | client = together.Together(api_key=api_key) 22 | 23 | return [item.model_dump(exclude_none=True) for item in client.models.list()] 24 | 25 | @classmethod 26 | @deprecated # type: ignore 27 | def info( 28 | cls, 29 | model: str, 30 | ) -> Dict[str, Any]: 31 | """Legacy model info function.""" 32 | 33 | api_key = None 34 | if together.api_key: 35 | warnings.warn(API_KEY_WARNING) 36 | api_key = together.api_key 37 | 38 | client = together.Together(api_key=api_key) 39 | 40 | model_list = client.models.list() 41 | 42 | for item in model_list: 43 | if item.id == model: 44 | return item.model_dump(exclude_none=True) 45 | -------------------------------------------------------------------------------- /src/together/resources/__init__.py: -------------------------------------------------------------------------------- 1 | from together.resources.audio import AsyncAudio, Audio 2 | from together.resources.chat import AsyncChat, Chat 3 | from together.resources.completions import AsyncCompletions, Completions 4 | from together.resources.embeddings import AsyncEmbeddings, Embeddings 5 | from together.resources.endpoints import AsyncEndpoints, Endpoints 6 | from together.resources.files import AsyncFiles, Files 7 | from together.resources.finetune import AsyncFineTuning, FineTuning 8 | from together.resources.images import AsyncImages, Images 9 | from together.resources.models import AsyncModels, Models 10 | from together.resources.rerank import AsyncRerank, Rerank 11 | 12 | 13 | __all__ = [ 14 | "AsyncCompletions", 15 | "Completions", 16 | "AsyncChat", 17 | "Chat", 18 | "AsyncEmbeddings", 19 | "Embeddings", 20 | "AsyncFineTuning", 21 | "FineTuning", 22 | "AsyncFiles", 23 | "Files", 24 | "AsyncImages", 25 | "Images", 26 | "AsyncModels", 27 | "Models", 28 | "AsyncRerank", 29 | "Rerank", 30 | "AsyncAudio", 31 | "Audio", 32 | "AsyncEndpoints", 33 | "Endpoints", 34 | ] 35 | -------------------------------------------------------------------------------- /src/together/resources/audio/__init__.py: -------------------------------------------------------------------------------- 1 | from functools import cached_property 2 | 3 | from together.resources.audio.speech import AsyncSpeech, Speech 4 | from together.types import ( 5 | TogetherClient, 6 | ) 7 | 8 | 9 | class Audio: 10 | def __init__(self, client: TogetherClient) -> None: 11 | self._client = client 12 | 13 | @cached_property 14 | def speech(self) -> Speech: 15 | return Speech(self._client) 16 | 17 | 18 | class AsyncAudio: 19 | def __init__(self, client: TogetherClient) -> None: 20 | self._client = client 21 | 22 | @cached_property 23 | def speech(self) -> AsyncSpeech: 24 | return AsyncSpeech(self._client) 25 | -------------------------------------------------------------------------------- /src/together/resources/audio/speech.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, AsyncGenerator, Dict, Iterator, List, Union 4 | 5 | from together.abstract import api_requestor 6 | from together.together_response import TogetherResponse 7 | from together.types import ( 8 | AudioSpeechRequest, 9 | AudioResponseFormat, 10 | AudioLanguage, 11 | AudioResponseEncoding, 12 | AudioSpeechStreamChunk, 13 | AudioSpeechStreamEvent, 14 | AudioSpeechStreamResponse, 15 | TogetherClient, 16 | TogetherRequest, 17 | ) 18 | 19 | 20 | class Speech: 21 | def __init__(self, client: TogetherClient) -> None: 22 | self._client = client 23 | 24 | def create( 25 | self, 26 | *, 27 | model: str, 28 | input: str, 29 | voice: str | None = None, 30 | response_format: str = "wav", 31 | language: str = "en", 32 | response_encoding: str = "pcm_f32le", 33 | sample_rate: int = 44100, 34 | stream: bool = False, 35 | **kwargs: Any, 36 | ) -> AudioSpeechStreamResponse: 37 | """ 38 | Method to generate audio from input text using a specified model. 39 | 40 | Args: 41 | model (str): The name of the model to query. 42 | input (str): Input text to generate the audio for. 43 | voice (str, optional): The voice to use for generating the audio. 44 | Defaults to None. 45 | response_format (str, optional): The format of audio output. 46 | Defaults to "wav". 47 | language (str, optional): Language of input text. 48 | Defaults to "en". 49 | response_encoding (str, optional): Audio encoding of response. 50 | Defaults to "pcm_f32le". 51 | sample_rate (int, optional): Sampling rate to use for the output audio. 52 | Defaults to 44100. 53 | stream (bool, optional): If true, output is streamed for several characters at a time. 54 | Defaults to False. 55 | 56 | Returns: 57 | Union[bytes, Iterator[AudioSpeechStreamChunk]]: The generated audio as bytes or an iterator over audio stream chunks. 58 | """ 59 | 60 | requestor = api_requestor.APIRequestor( 61 | client=self._client, 62 | ) 63 | 64 | parameter_payload = AudioSpeechRequest( 65 | model=model, 66 | input=input, 67 | voice=voice, 68 | response_format=AudioResponseFormat(response_format), 69 | language=AudioLanguage(language), 70 | response_encoding=AudioResponseEncoding(response_encoding), 71 | sample_rate=sample_rate, 72 | stream=stream, 73 | **kwargs, 74 | ).model_dump(exclude_none=True) 75 | 76 | response, streamed, _ = requestor.request( 77 | options=TogetherRequest( 78 | method="POST", 79 | url="audio/speech", 80 | params=parameter_payload, 81 | ), 82 | stream=stream, 83 | ) 84 | 85 | return AudioSpeechStreamResponse(response=response) 86 | 87 | 88 | class AsyncSpeech: 89 | def __init__(self, client: TogetherClient) -> None: 90 | self._client = client 91 | 92 | async def create( 93 | self, 94 | *, 95 | model: str, 96 | input: str, 97 | voice: str | None = None, 98 | response_format: str = "wav", 99 | language: str = "en", 100 | response_encoding: str = "pcm_f32le", 101 | sample_rate: int = 44100, 102 | stream: bool = False, 103 | **kwargs: Any, 104 | ) -> AudioSpeechStreamResponse: 105 | """ 106 | Async method to generate audio from input text using a specified model. 107 | 108 | Args: 109 | model (str): The name of the model to query. 110 | input (str): Input text to generate the audio for. 111 | voice (str, optional): The voice to use for generating the audio. 112 | Defaults to None. 113 | response_format (str, optional): The format of audio output. 114 | Defaults to "wav". 115 | language (str, optional): Language of input text. 116 | Defaults to "en". 117 | response_encoding (str, optional): Audio encoding of response. 118 | Defaults to "pcm_f32le". 119 | sample_rate (int, optional): Sampling rate to use for the output audio. 120 | Defaults to 44100. 121 | stream (bool, optional): If true, output is streamed for several characters at a time. 122 | Defaults to False. 123 | 124 | Returns: 125 | Union[bytes, AsyncGenerator[AudioSpeechStreamChunk, None]]: The generated audio as bytes or an async generator over audio stream chunks. 126 | """ 127 | 128 | requestor = api_requestor.APIRequestor( 129 | client=self._client, 130 | ) 131 | 132 | parameter_payload = AudioSpeechRequest( 133 | model=model, 134 | input=input, 135 | voice=voice, 136 | response_format=AudioResponseFormat(response_format), 137 | language=AudioLanguage(language), 138 | response_encoding=AudioResponseEncoding(response_encoding), 139 | sample_rate=sample_rate, 140 | stream=stream, 141 | **kwargs, 142 | ).model_dump(exclude_none=True) 143 | 144 | response, _, _ = await requestor.arequest( 145 | options=TogetherRequest( 146 | method="POST", 147 | url="audio/speech", 148 | params=parameter_payload, 149 | ), 150 | stream=stream, 151 | ) 152 | 153 | return AudioSpeechStreamResponse(response=response) 154 | -------------------------------------------------------------------------------- /src/together/resources/chat/__init__.py: -------------------------------------------------------------------------------- 1 | from functools import cached_property 2 | 3 | from together.resources.chat.completions import AsyncChatCompletions, ChatCompletions 4 | from together.types import ( 5 | TogetherClient, 6 | ) 7 | 8 | 9 | class Chat: 10 | def __init__(self, client: TogetherClient) -> None: 11 | self._client = client 12 | 13 | @cached_property 14 | def completions(self) -> ChatCompletions: 15 | return ChatCompletions(self._client) 16 | 17 | 18 | class AsyncChat: 19 | def __init__(self, client: TogetherClient) -> None: 20 | self._client = client 21 | 22 | @cached_property 23 | def completions(self) -> AsyncChatCompletions: 24 | return AsyncChatCompletions(self._client) 25 | -------------------------------------------------------------------------------- /src/together/resources/code_interpreter.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, Dict, List, Literal, Optional 4 | from pydantic import ValidationError 5 | 6 | from together.abstract import api_requestor 7 | from together.together_response import TogetherResponse 8 | from together.types import TogetherClient, TogetherRequest 9 | from together.types.code_interpreter import ExecuteResponse, FileInput 10 | 11 | 12 | class CodeInterpreter: 13 | """Code Interpreter resource for executing code snippets.""" 14 | 15 | def __init__(self, client: TogetherClient) -> None: 16 | self._client = client 17 | 18 | def run( 19 | self, 20 | code: str, 21 | language: Literal["python"], 22 | session_id: Optional[str] = None, 23 | files: Optional[List[Dict[str, Any]]] = None, 24 | ) -> ExecuteResponse: 25 | """Execute a code snippet, optionally with files. 26 | 27 | Args: 28 | code (str): Code snippet to execute 29 | language (str): Programming language for the code to execute. Currently only supports Python. 30 | session_id (str, optional): Identifier of the current session. Used to make follow-up calls. 31 | files (List[Dict], optional): Files to upload to the session before executing the code. 32 | 33 | Returns: 34 | ExecuteResponse: Object containing execution results and outputs 35 | 36 | Raises: 37 | ValidationError: If any dictionary in the `files` list does not conform to the 38 | required structure or types. 39 | """ 40 | requestor = api_requestor.APIRequestor( 41 | client=self._client, 42 | ) 43 | 44 | data: Dict[str, Any] = { 45 | "code": code, 46 | "language": language, 47 | } 48 | 49 | if session_id is not None: 50 | data["session_id"] = session_id 51 | 52 | if files is not None: 53 | serialized_files = [] 54 | try: 55 | for file_dict in files: 56 | # Validate the dictionary by creating a FileInput instance 57 | validated_file = FileInput(**file_dict) 58 | # Serialize the validated model back to a dict for the API call 59 | serialized_files.append(validated_file.model_dump()) 60 | except ValidationError as e: 61 | raise ValueError(f"Invalid file input format: {e}") from e 62 | except TypeError as e: 63 | raise ValueError( 64 | f"Invalid file input: Each item in 'files' must be a dictionary. Error: {e}" 65 | ) from e 66 | 67 | data["files"] = serialized_files 68 | 69 | # Use absolute URL to bypass the /v1 prefix 70 | response, _, _ = requestor.request( 71 | options=TogetherRequest( 72 | method="POST", 73 | url="/tci/execute", 74 | params=data, 75 | ), 76 | stream=False, 77 | ) 78 | 79 | assert isinstance(response, TogetherResponse) 80 | 81 | # Return the response data directly since our types match the API structure 82 | return ExecuteResponse(**response.data) 83 | -------------------------------------------------------------------------------- /src/together/resources/embeddings.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List, Any 4 | 5 | from together.abstract import api_requestor 6 | from together.together_response import TogetherResponse 7 | from together.types import ( 8 | EmbeddingRequest, 9 | EmbeddingResponse, 10 | TogetherClient, 11 | TogetherRequest, 12 | ) 13 | 14 | 15 | class Embeddings: 16 | def __init__(self, client: TogetherClient) -> None: 17 | self._client = client 18 | 19 | def create( 20 | self, 21 | *, 22 | input: str | List[str], 23 | model: str, 24 | **kwargs: Any, 25 | ) -> EmbeddingResponse: 26 | """ 27 | Method to generate completions based on a given prompt using a specified model. 28 | 29 | Args: 30 | input (str | List[str]): A string or list of strings to embed 31 | model (str): The name of the model to query. 32 | 33 | Returns: 34 | EmbeddingResponse: Object containing embeddings 35 | """ 36 | 37 | requestor = api_requestor.APIRequestor( 38 | client=self._client, 39 | ) 40 | 41 | parameter_payload = EmbeddingRequest( 42 | input=input, 43 | model=model, 44 | **kwargs, 45 | ).model_dump(exclude_none=True) 46 | 47 | response, _, _ = requestor.request( 48 | options=TogetherRequest( 49 | method="POST", 50 | url="embeddings", 51 | params=parameter_payload, 52 | ), 53 | stream=False, 54 | ) 55 | 56 | assert isinstance(response, TogetherResponse) 57 | 58 | return EmbeddingResponse(**response.data) 59 | 60 | 61 | class AsyncEmbeddings: 62 | def __init__(self, client: TogetherClient) -> None: 63 | self._client = client 64 | 65 | async def create( 66 | self, 67 | *, 68 | input: str | List[str], 69 | model: str, 70 | **kwargs: Any, 71 | ) -> EmbeddingResponse: 72 | """ 73 | Async method to generate completions based on a given prompt using a specified model. 74 | 75 | Args: 76 | input (str | List[str]): A string or list of strings to embed 77 | model (str): The name of the model to query. 78 | 79 | Returns: 80 | EmbeddingResponse: Object containing embeddings 81 | """ 82 | 83 | requestor = api_requestor.APIRequestor( 84 | client=self._client, 85 | ) 86 | 87 | parameter_payload = EmbeddingRequest( 88 | input=input, 89 | model=model, 90 | **kwargs, 91 | ).model_dump(exclude_none=True) 92 | 93 | response, _, _ = await requestor.arequest( 94 | options=TogetherRequest( 95 | method="POST", 96 | url="embeddings", 97 | params=parameter_payload, 98 | ), 99 | stream=False, 100 | ) 101 | 102 | assert isinstance(response, TogetherResponse) 103 | 104 | return EmbeddingResponse(**response.data) 105 | -------------------------------------------------------------------------------- /src/together/resources/files.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | from pprint import pformat 5 | 6 | from together.abstract import api_requestor 7 | from together.error import FileTypeError 8 | from together.filemanager import DownloadManager, UploadManager 9 | from together.together_response import TogetherResponse 10 | from together.types import ( 11 | FileDeleteResponse, 12 | FileList, 13 | FileObject, 14 | FilePurpose, 15 | FileResponse, 16 | TogetherClient, 17 | TogetherRequest, 18 | ) 19 | from together.utils import check_file, normalize_key 20 | 21 | 22 | class Files: 23 | def __init__(self, client: TogetherClient) -> None: 24 | self._client = client 25 | 26 | def upload( 27 | self, 28 | file: Path | str, 29 | *, 30 | purpose: FilePurpose | str = FilePurpose.FineTune, 31 | check: bool = True, 32 | ) -> FileResponse: 33 | upload_manager = UploadManager(self._client) 34 | 35 | if check: 36 | report_dict = check_file(file) 37 | if not report_dict["is_check_passed"]: 38 | raise FileTypeError( 39 | f"Invalid file supplied, failed to upload. Report:\n{pformat(report_dict)}" 40 | ) 41 | 42 | if isinstance(file, str): 43 | file = Path(file) 44 | 45 | if isinstance(purpose, str): 46 | purpose = FilePurpose(purpose) 47 | 48 | assert isinstance(purpose, FilePurpose) 49 | 50 | return upload_manager.upload("files", file, purpose=purpose, redirect=True) 51 | 52 | def list(self) -> FileList: 53 | requestor = api_requestor.APIRequestor( 54 | client=self._client, 55 | ) 56 | 57 | response, _, _ = requestor.request( 58 | options=TogetherRequest( 59 | method="GET", 60 | url="files", 61 | ), 62 | stream=False, 63 | ) 64 | 65 | assert isinstance(response, TogetherResponse) 66 | 67 | return FileList(**response.data) 68 | 69 | def retrieve(self, id: str) -> FileResponse: 70 | requestor = api_requestor.APIRequestor( 71 | client=self._client, 72 | ) 73 | 74 | response, _, _ = requestor.request( 75 | options=TogetherRequest( 76 | method="GET", 77 | url=f"files/{id}", 78 | ), 79 | stream=False, 80 | ) 81 | 82 | assert isinstance(response, TogetherResponse) 83 | 84 | return FileResponse(**response.data) 85 | 86 | def retrieve_content( 87 | self, id: str, *, output: Path | str | None = None 88 | ) -> FileObject: 89 | download_manager = DownloadManager(self._client) 90 | 91 | if isinstance(output, str): 92 | output = Path(output) 93 | 94 | downloaded_filename, file_size = download_manager.download( 95 | f"files/{id}/content", output, normalize_key(f"{id}.jsonl") 96 | ) 97 | 98 | return FileObject( 99 | object="local", 100 | id=id, 101 | filename=downloaded_filename, 102 | size=file_size, 103 | ) 104 | 105 | def delete(self, id: str) -> FileDeleteResponse: 106 | requestor = api_requestor.APIRequestor( 107 | client=self._client, 108 | ) 109 | 110 | response, _, _ = requestor.request( 111 | options=TogetherRequest( 112 | method="DELETE", 113 | url=f"files/{id}", 114 | ), 115 | stream=False, 116 | ) 117 | 118 | assert isinstance(response, TogetherResponse) 119 | 120 | return FileDeleteResponse(**response.data) 121 | 122 | 123 | class AsyncFiles: 124 | def __init__(self, client: TogetherClient) -> None: 125 | self._client = client 126 | 127 | async def upload( 128 | self, file: Path | str, *, purpose: FilePurpose | str = FilePurpose.FineTune 129 | ) -> None: 130 | raise NotImplementedError() 131 | 132 | async def list(self) -> FileList: 133 | requestor = api_requestor.APIRequestor( 134 | client=self._client, 135 | ) 136 | 137 | response, _, _ = await requestor.arequest( 138 | options=TogetherRequest( 139 | method="GET", 140 | url="files", 141 | ), 142 | stream=False, 143 | ) 144 | 145 | assert isinstance(response, TogetherResponse) 146 | 147 | return FileList(**response.data) 148 | 149 | async def retrieve(self, id: str) -> FileResponse: 150 | requestor = api_requestor.APIRequestor( 151 | client=self._client, 152 | ) 153 | 154 | response, _, _ = await requestor.arequest( 155 | options=TogetherRequest( 156 | method="GET", 157 | url=f"files/{id}", 158 | ), 159 | stream=False, 160 | ) 161 | 162 | assert isinstance(response, TogetherResponse) 163 | 164 | return FileResponse(**response.data) 165 | 166 | async def retrieve_content( 167 | self, id: str, *, output: Path | str | None = None 168 | ) -> FileObject: 169 | raise NotImplementedError() 170 | 171 | async def delete(self, id: str) -> FileDeleteResponse: 172 | requestor = api_requestor.APIRequestor( 173 | client=self._client, 174 | ) 175 | 176 | response, _, _ = await requestor.arequest( 177 | options=TogetherRequest( 178 | method="DELETE", 179 | url=f"files/{id}", 180 | ), 181 | stream=False, 182 | ) 183 | 184 | assert isinstance(response, TogetherResponse) 185 | 186 | return FileDeleteResponse(**response.data) 187 | -------------------------------------------------------------------------------- /src/together/resources/images.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any 4 | 5 | from together.abstract import api_requestor 6 | from together.together_response import TogetherResponse 7 | from together.types import ( 8 | ImageRequest, 9 | ImageResponse, 10 | TogetherClient, 11 | TogetherRequest, 12 | ) 13 | 14 | 15 | class Images: 16 | def __init__(self, client: TogetherClient) -> None: 17 | self._client = client 18 | 19 | def generate( 20 | self, 21 | *, 22 | prompt: str, 23 | model: str, 24 | steps: int | None = 20, 25 | seed: int | None = None, 26 | n: int | None = 1, 27 | height: int | None = 1024, 28 | width: int | None = 1024, 29 | negative_prompt: str | None = None, 30 | **kwargs: Any, 31 | ) -> ImageResponse: 32 | """ 33 | Method to generate images based on a given prompt using a specified model. 34 | 35 | Args: 36 | prompt (str): A description of the desired images. Maximum length varies by model. 37 | 38 | model (str, optional): The model to use for image generation. 39 | 40 | steps (int, optional): Number of generation steps. Defaults to 20 41 | 42 | seed (int, optional): Seed used for generation. Can be used to reproduce image generations. 43 | Defaults to None. 44 | 45 | n (int, optional): Number of image results to generate. Defaults to 1. 46 | 47 | height (int, optional): Height of the image to generate in number of pixels. Defaults to 1024 48 | 49 | width (int, optional): Width of the image to generate in number of pixels. Defaults to 1024 50 | 51 | negative_prompt (str, optional): The prompt or prompts not to guide the image generation. 52 | Defaults to None 53 | 54 | image_base64: (str, optional): Reference image used for generation. Defaults to None. 55 | 56 | Returns: 57 | ImageResponse: Object containing image data 58 | """ 59 | 60 | requestor = api_requestor.APIRequestor( 61 | client=self._client, 62 | ) 63 | 64 | parameter_payload = ImageRequest( 65 | prompt=prompt, 66 | model=model, 67 | steps=steps, 68 | seed=seed, 69 | n=n, 70 | height=height, 71 | width=width, 72 | negative_prompt=negative_prompt, 73 | **kwargs, 74 | ).model_dump(exclude_none=True) 75 | 76 | response, _, _ = requestor.request( 77 | options=TogetherRequest( 78 | method="POST", 79 | url="images/generations", 80 | params=parameter_payload, 81 | ), 82 | stream=False, 83 | ) 84 | 85 | assert isinstance(response, TogetherResponse) 86 | 87 | return ImageResponse(**response.data) 88 | 89 | 90 | class AsyncImages: 91 | def __init__(self, client: TogetherClient) -> None: 92 | self._client = client 93 | 94 | async def generate( 95 | self, 96 | *, 97 | prompt: str, 98 | model: str, 99 | steps: int | None = 20, 100 | seed: int | None = None, 101 | n: int | None = 1, 102 | height: int | None = 1024, 103 | width: int | None = 1024, 104 | negative_prompt: str | None = None, 105 | **kwargs: Any, 106 | ) -> ImageResponse: 107 | """ 108 | Async method to generate images based on a given prompt using a specified model. 109 | 110 | Args: 111 | prompt (str): A description of the desired images. Maximum length varies by model. 112 | 113 | model (str, optional): The model to use for image generation. 114 | 115 | steps (int, optional): Number of generation steps. Defaults to 20 116 | 117 | seed (int, optional): Seed used for generation. Can be used to reproduce image generations. 118 | Defaults to None. 119 | 120 | n (int, optional): Number of image results to generate. Defaults to 1. 121 | 122 | height (int, optional): Height of the image to generate in number of pixels. Defaults to 1024 123 | 124 | width (int, optional): Width of the image to generate in number of pixels. Defaults to 1024 125 | 126 | negative_prompt (str, optional): The prompt or prompts not to guide the image generation. 127 | Defaults to None 128 | 129 | image_base64: (str, optional): Reference image used for generation. Defaults to None. 130 | 131 | Returns: 132 | ImageResponse: Object containing image data 133 | """ 134 | 135 | requestor = api_requestor.APIRequestor( 136 | client=self._client, 137 | ) 138 | 139 | parameter_payload = ImageRequest( 140 | prompt=prompt, 141 | model=model, 142 | steps=steps, 143 | seed=seed, 144 | n=n, 145 | height=height, 146 | width=width, 147 | negative_prompt=negative_prompt, 148 | **kwargs, 149 | ).model_dump(exclude_none=True) 150 | 151 | response, _, _ = await requestor.arequest( 152 | options=TogetherRequest( 153 | method="POST", 154 | url="images/generations", 155 | params=parameter_payload, 156 | ), 157 | stream=False, 158 | ) 159 | 160 | assert isinstance(response, TogetherResponse) 161 | 162 | return ImageResponse(**response.data) 163 | -------------------------------------------------------------------------------- /src/together/resources/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List 4 | 5 | from together.abstract import api_requestor 6 | from together.together_response import TogetherResponse 7 | from together.types import ( 8 | ModelObject, 9 | TogetherClient, 10 | TogetherRequest, 11 | ) 12 | 13 | 14 | class ModelsBase: 15 | def __init__(self, client: TogetherClient) -> None: 16 | self._client = client 17 | 18 | def _filter_dedicated_models( 19 | self, models: List[ModelObject], dedicated_response: TogetherResponse 20 | ) -> List[ModelObject]: 21 | """ 22 | Filter models based on dedicated model response. 23 | 24 | Args: 25 | models (List[ModelObject]): List of all models 26 | dedicated_response (TogetherResponse): Response from autoscale models endpoint 27 | 28 | Returns: 29 | List[ModelObject]: Filtered list of models 30 | """ 31 | assert isinstance(dedicated_response.data, list) 32 | 33 | # Create a set of dedicated model names for efficient lookup 34 | dedicated_model_names = {model["name"] for model in dedicated_response.data} 35 | 36 | # Filter models to only include those in dedicated_model_names 37 | # Note: The model.id from ModelObject matches the name field in the autoscale response 38 | return [model for model in models if model.id in dedicated_model_names] 39 | 40 | 41 | class Models(ModelsBase): 42 | def list( 43 | self, 44 | dedicated: bool = False, 45 | ) -> List[ModelObject]: 46 | """ 47 | Method to return list of models on the API 48 | 49 | Args: 50 | dedicated (bool, optional): If True, returns only dedicated models. Defaults to False. 51 | 52 | Returns: 53 | List[ModelObject]: List of model objects 54 | """ 55 | requestor = api_requestor.APIRequestor( 56 | client=self._client, 57 | ) 58 | 59 | response, _, _ = requestor.request( 60 | options=TogetherRequest( 61 | method="GET", 62 | url="models", 63 | ), 64 | stream=False, 65 | ) 66 | 67 | assert isinstance(response, TogetherResponse) 68 | assert isinstance(response.data, list) 69 | 70 | models = [ModelObject(**model) for model in response.data] 71 | 72 | if dedicated: 73 | # Get dedicated models 74 | dedicated_response, _, _ = requestor.request( 75 | options=TogetherRequest( 76 | method="GET", 77 | url="autoscale/models", 78 | ), 79 | stream=False, 80 | ) 81 | 82 | models = self._filter_dedicated_models(models, dedicated_response) 83 | 84 | models.sort(key=lambda x: x.id.lower()) 85 | 86 | return models 87 | 88 | 89 | class AsyncModels(ModelsBase): 90 | async def list( 91 | self, 92 | dedicated: bool = False, 93 | ) -> List[ModelObject]: 94 | """ 95 | Async method to return list of models on API 96 | 97 | Args: 98 | dedicated (bool, optional): If True, returns only dedicated models. Defaults to False. 99 | 100 | Returns: 101 | List[ModelObject]: List of model objects 102 | """ 103 | requestor = api_requestor.APIRequestor( 104 | client=self._client, 105 | ) 106 | 107 | response, _, _ = await requestor.arequest( 108 | options=TogetherRequest( 109 | method="GET", 110 | url="models", 111 | ), 112 | stream=False, 113 | ) 114 | 115 | assert isinstance(response, TogetherResponse) 116 | assert isinstance(response.data, list) 117 | 118 | models = [ModelObject(**model) for model in response.data] 119 | 120 | if dedicated: 121 | # Get dedicated models 122 | dedicated_response, _, _ = await requestor.arequest( 123 | options=TogetherRequest( 124 | method="GET", 125 | url="autoscale/models", 126 | ), 127 | stream=False, 128 | ) 129 | 130 | models = self._filter_dedicated_models(models, dedicated_response) 131 | 132 | models.sort(key=lambda x: x.id.lower()) 133 | 134 | return models 135 | -------------------------------------------------------------------------------- /src/together/resources/rerank.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List, Dict, Any 4 | 5 | from together.abstract import api_requestor 6 | from together.together_response import TogetherResponse 7 | from together.types import ( 8 | RerankRequest, 9 | RerankResponse, 10 | TogetherClient, 11 | TogetherRequest, 12 | ) 13 | 14 | 15 | class Rerank: 16 | def __init__(self, client: TogetherClient) -> None: 17 | self._client = client 18 | 19 | def create( 20 | self, 21 | *, 22 | model: str, 23 | query: str, 24 | documents: List[str] | List[Dict[str, Any]], 25 | top_n: int | None = None, 26 | return_documents: bool = False, 27 | rank_fields: List[str] | None = None, 28 | **kwargs: Any, 29 | ) -> RerankResponse: 30 | """ 31 | Method to generate completions based on a given prompt using a specified model. 32 | 33 | Args: 34 | model (str): The name of the model to query. 35 | query (str): The input query or list of queries to rerank. 36 | documents (List[str] | List[Dict[str, Any]]): List of documents to be reranked. 37 | top_n (int | None): Number of top results to return. 38 | return_documents (bool): Flag to indicate whether to return documents. 39 | rank_fields (List[str] | None): Fields to be used for ranking the documents. 40 | 41 | Returns: 42 | RerankResponse: Object containing reranked scores and documents 43 | """ 44 | 45 | requestor = api_requestor.APIRequestor( 46 | client=self._client, 47 | ) 48 | 49 | parameter_payload = RerankRequest( 50 | model=model, 51 | query=query, 52 | documents=documents, 53 | top_n=top_n, 54 | return_documents=return_documents, 55 | rank_fields=rank_fields, 56 | **kwargs, 57 | ).model_dump(exclude_none=True) 58 | 59 | response, _, _ = requestor.request( 60 | options=TogetherRequest( 61 | method="POST", 62 | url="rerank", 63 | params=parameter_payload, 64 | ), 65 | stream=False, 66 | ) 67 | 68 | assert isinstance(response, TogetherResponse) 69 | 70 | return RerankResponse(**response.data) 71 | 72 | 73 | class AsyncRerank: 74 | def __init__(self, client: TogetherClient) -> None: 75 | self._client = client 76 | 77 | async def create( 78 | self, 79 | *, 80 | model: str, 81 | query: str, 82 | documents: List[str] | List[Dict[str, Any]], 83 | top_n: int | None = None, 84 | return_documents: bool = False, 85 | rank_fields: List[str] | None = None, 86 | **kwargs: Any, 87 | ) -> RerankResponse: 88 | """ 89 | Async method to generate completions based on a given prompt using a specified model. 90 | 91 | Args: 92 | model (str): The name of the model to query. 93 | query (str): The input query or list of queries to rerank. 94 | documents (List[str] | List[Dict[str, Any]]): List of documents to be reranked. 95 | top_n (int | None): Number of top results to return. 96 | return_documents (bool): Flag to indicate whether to return documents. 97 | rank_fields (List[str] | None): Fields to be used for ranking the documents. 98 | 99 | Returns: 100 | RerankResponse: Object containing reranked scores and documents 101 | """ 102 | 103 | requestor = api_requestor.APIRequestor( 104 | client=self._client, 105 | ) 106 | 107 | parameter_payload = RerankRequest( 108 | model=model, 109 | query=query, 110 | documents=documents, 111 | top_n=top_n, 112 | return_documents=return_documents, 113 | rank_fields=rank_fields, 114 | **kwargs, 115 | ).model_dump(exclude_none=True) 116 | 117 | response, _, _ = await requestor.arequest( 118 | options=TogetherRequest( 119 | method="POST", 120 | url="rerank", 121 | params=parameter_payload, 122 | ), 123 | stream=False, 124 | ) 125 | 126 | assert isinstance(response, TogetherResponse) 127 | 128 | return RerankResponse(**response.data) 129 | -------------------------------------------------------------------------------- /src/together/together_response.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, Dict 4 | 5 | 6 | class TogetherResponse: 7 | """ 8 | API Response class. Stores headers and response data. 9 | """ 10 | 11 | def __init__(self, data: Any, headers: Dict[str, Any]): 12 | self._headers = headers 13 | self.data = data 14 | 15 | @property 16 | def request_id(self) -> str | None: 17 | """ 18 | Fetches request id from headers 19 | """ 20 | if "cf-ray" in self._headers: 21 | return str(self._headers["cf-ray"]) 22 | return None 23 | 24 | @property 25 | def requests_remaining(self) -> int | None: 26 | """ 27 | Number of requests remaining at current rate limit 28 | """ 29 | if "x-ratelimit-remaining" in self._headers: 30 | return int(self._headers["x-ratelimit-remaining"]) 31 | return None 32 | 33 | @property 34 | def processed_by(self) -> str | None: 35 | """ 36 | Processing host server name 37 | """ 38 | if "x-hostname" in self._headers: 39 | return str(self._headers["x-hostname"]) 40 | return None 41 | 42 | @property 43 | def response_ms(self) -> int | None: 44 | """ 45 | Server request completion time 46 | """ 47 | if "x-total-time" in self._headers: 48 | h = self._headers["x-total-time"] 49 | return None if h is None else round(float(h)) 50 | return None 51 | -------------------------------------------------------------------------------- /src/together/types/__init__.py: -------------------------------------------------------------------------------- 1 | from together.types.abstract import TogetherClient 2 | from together.types.audio_speech import ( 3 | AudioLanguage, 4 | AudioResponseEncoding, 5 | AudioResponseFormat, 6 | AudioSpeechRequest, 7 | AudioSpeechStreamChunk, 8 | AudioSpeechStreamEvent, 9 | AudioSpeechStreamResponse, 10 | ) 11 | from together.types.chat_completions import ( 12 | ChatCompletionChunk, 13 | ChatCompletionRequest, 14 | ChatCompletionResponse, 15 | ) 16 | from together.types.common import TogetherRequest 17 | from together.types.completions import ( 18 | CompletionChunk, 19 | CompletionRequest, 20 | CompletionResponse, 21 | ) 22 | from together.types.embeddings import EmbeddingRequest, EmbeddingResponse 23 | from together.types.endpoints import Autoscaling, DedicatedEndpoint, ListEndpoint 24 | from together.types.files import ( 25 | FileDeleteResponse, 26 | FileList, 27 | FileObject, 28 | FilePurpose, 29 | FileRequest, 30 | FileResponse, 31 | FileType, 32 | ) 33 | from together.types.finetune import ( 34 | TrainingMethodDPO, 35 | TrainingMethodSFT, 36 | FinetuneCheckpoint, 37 | CosineLRScheduler, 38 | CosineLRSchedulerArgs, 39 | FinetuneDownloadResult, 40 | LinearLRScheduler, 41 | LinearLRSchedulerArgs, 42 | FinetuneLRScheduler, 43 | FinetuneList, 44 | FinetuneListEvents, 45 | FinetuneRequest, 46 | FinetuneResponse, 47 | FinetuneTrainingLimits, 48 | FullTrainingType, 49 | LoRATrainingType, 50 | TrainingType, 51 | ) 52 | from together.types.images import ImageRequest, ImageResponse 53 | from together.types.models import ModelObject 54 | from together.types.rerank import RerankRequest, RerankResponse 55 | 56 | 57 | __all__ = [ 58 | "TogetherClient", 59 | "TogetherRequest", 60 | "CompletionChunk", 61 | "CompletionRequest", 62 | "CompletionResponse", 63 | "ChatCompletionChunk", 64 | "ChatCompletionRequest", 65 | "ChatCompletionResponse", 66 | "EmbeddingRequest", 67 | "EmbeddingResponse", 68 | "FinetuneCheckpoint", 69 | "FinetuneRequest", 70 | "FinetuneResponse", 71 | "FinetuneList", 72 | "FinetuneListEvents", 73 | "FinetuneDownloadResult", 74 | "FinetuneLRScheduler", 75 | "LinearLRScheduler", 76 | "LinearLRSchedulerArgs", 77 | "CosineLRScheduler", 78 | "CosineLRSchedulerArgs", 79 | "FileRequest", 80 | "FileResponse", 81 | "FileList", 82 | "FileDeleteResponse", 83 | "FileObject", 84 | "FilePurpose", 85 | "FileType", 86 | "ImageRequest", 87 | "ImageResponse", 88 | "ModelObject", 89 | "TrainingType", 90 | "FullTrainingType", 91 | "LoRATrainingType", 92 | "TrainingMethodDPO", 93 | "TrainingMethodSFT", 94 | "RerankRequest", 95 | "RerankResponse", 96 | "FinetuneTrainingLimits", 97 | "AudioSpeechRequest", 98 | "AudioResponseFormat", 99 | "AudioLanguage", 100 | "AudioResponseEncoding", 101 | "AudioSpeechStreamChunk", 102 | "AudioSpeechStreamEvent", 103 | "AudioSpeechStreamResponse", 104 | "DedicatedEndpoint", 105 | "ListEndpoint", 106 | "Autoscaling", 107 | ] 108 | -------------------------------------------------------------------------------- /src/together/types/abstract.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Dict 5 | 6 | import pydantic 7 | from pydantic import ConfigDict 8 | from typing_extensions import ClassVar 9 | 10 | from together.constants import BASE_URL, MAX_RETRIES, TIMEOUT_SECS 11 | 12 | 13 | PYDANTIC_V2 = pydantic.VERSION.startswith("2.") 14 | 15 | 16 | @dataclass 17 | class TogetherClient: 18 | api_key: str | None = None 19 | base_url: str | None = BASE_URL 20 | timeout: float | None = TIMEOUT_SECS 21 | max_retries: int | None = MAX_RETRIES 22 | supplied_headers: Dict[str, str] | None = None 23 | 24 | 25 | class BaseModel(pydantic.BaseModel): 26 | model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow") 27 | -------------------------------------------------------------------------------- /src/together/types/audio_speech.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | from typing import Iterator 5 | import threading 6 | 7 | from pydantic import BaseModel, ConfigDict 8 | 9 | from together.together_response import TogetherResponse 10 | import base64 11 | 12 | 13 | class AudioResponseFormat(str, Enum): 14 | MP3 = "mp3" 15 | WAV = "wav" 16 | RAW = "raw" 17 | 18 | 19 | class AudioLanguage(str, Enum): 20 | EN = "en" 21 | DE = "de" 22 | FR = "fr" 23 | ES = "es" 24 | HI = "hi" 25 | IT = "it" 26 | JA = "ja" 27 | KO = "ko" 28 | NL = "nl" 29 | PL = "pl" 30 | PT = "pt" 31 | RU = "ru" 32 | SV = "sv" 33 | TR = "tr" 34 | ZH = "zh" 35 | 36 | 37 | class AudioResponseEncoding(str, Enum): 38 | PCM_F32LE = "pcm_f32le" 39 | PCM_S16LE = "pcm_s16le" 40 | PCM_MULAW = "pcm_mulaw" 41 | PCM_ALAW = "pcm_alaw" 42 | 43 | 44 | class AudioObjectType(str, Enum): 45 | AUDIO_TTS_CHUNK = "audio.tts.chunk" 46 | 47 | 48 | class StreamSentinelType(str, Enum): 49 | DONE = "[DONE]" 50 | 51 | 52 | class AudioSpeechRequest(BaseModel): 53 | model: str 54 | input: str 55 | voice: str | None = None 56 | response_format: AudioResponseFormat = AudioResponseFormat.MP3 57 | language: AudioLanguage = AudioLanguage.EN 58 | response_encoding: AudioResponseEncoding = AudioResponseEncoding.PCM_F32LE 59 | sample_rate: int = 44100 60 | stream: bool = False 61 | 62 | 63 | class AudioSpeechStreamChunk(BaseModel): 64 | object: AudioObjectType = AudioObjectType.AUDIO_TTS_CHUNK 65 | model: str 66 | b64: str 67 | 68 | 69 | class AudioSpeechStreamEvent(BaseModel): 70 | data: AudioSpeechStreamChunk 71 | 72 | 73 | class StreamSentinel(BaseModel): 74 | data: StreamSentinelType = StreamSentinelType.DONE 75 | 76 | 77 | class AudioSpeechStreamEventResponse(BaseModel): 78 | response: AudioSpeechStreamEvent | StreamSentinel 79 | 80 | 81 | class AudioSpeechStreamResponse(BaseModel): 82 | 83 | response: TogetherResponse | Iterator[TogetherResponse] 84 | 85 | model_config = ConfigDict(arbitrary_types_allowed=True) 86 | 87 | def stream_to_file(self, file_path: str) -> None: 88 | 89 | if isinstance(self.response, TogetherResponse): 90 | # save response to file 91 | with open(file_path, "wb") as f: 92 | f.write(self.response.data) 93 | 94 | elif isinstance(self.response, Iterator): 95 | 96 | with open(file_path, "wb") as f: 97 | for chunk in self.response: 98 | 99 | # Try to parse as stream chunk 100 | stream_event_response = AudioSpeechStreamEventResponse( 101 | response={"data": chunk.data} 102 | ) 103 | 104 | if isinstance(stream_event_response.response, StreamSentinel): 105 | break 106 | 107 | # decode base64 108 | audio = base64.b64decode(stream_event_response.response.data.b64) 109 | 110 | f.write(audio) 111 | -------------------------------------------------------------------------------- /src/together/types/chat_completions.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import warnings 4 | from enum import Enum 5 | from typing import Any, Dict, List 6 | 7 | from pydantic import model_validator 8 | from typing_extensions import Self 9 | 10 | from together.types.abstract import BaseModel 11 | from together.types.common import ( 12 | DeltaContent, 13 | FinishReason, 14 | LogprobsPart, 15 | ObjectType, 16 | PromptPart, 17 | UsageData, 18 | ) 19 | 20 | 21 | class MessageRole(str, Enum): 22 | ASSISTANT = "assistant" 23 | SYSTEM = "system" 24 | USER = "user" 25 | TOOL = "tool" 26 | 27 | 28 | class ResponseFormatType(str, Enum): 29 | JSON_OBJECT = "json_object" 30 | JSON_SCHEMA = "json_schema" 31 | 32 | 33 | class FunctionCall(BaseModel): 34 | name: str | None = None 35 | arguments: str | None = None 36 | 37 | 38 | class ToolCalls(BaseModel): 39 | id: str | None = None 40 | type: str | None = None 41 | function: FunctionCall | None = None 42 | 43 | 44 | class ChatCompletionMessageContentType(str, Enum): 45 | TEXT = "text" 46 | IMAGE_URL = "image_url" 47 | VIDEO_URL = "video_url" 48 | 49 | 50 | class ChatCompletionMessageContentImageURL(BaseModel): 51 | url: str 52 | 53 | 54 | class ChatCompletionMessageContentVideoURL(BaseModel): 55 | url: str 56 | 57 | 58 | class ChatCompletionMessageContent(BaseModel): 59 | type: ChatCompletionMessageContentType 60 | text: str | None = None 61 | image_url: ChatCompletionMessageContentImageURL | None = None 62 | video_url: ChatCompletionMessageContentVideoURL | None = None 63 | 64 | 65 | class ChatCompletionMessage(BaseModel): 66 | role: MessageRole 67 | content: str | List[ChatCompletionMessageContent] | None = None 68 | tool_calls: List[ToolCalls] | None = None 69 | 70 | 71 | class ResponseFormat(BaseModel): 72 | type: ResponseFormatType 73 | schema_: Dict[str, Any] | None = None 74 | 75 | def to_dict(self) -> Dict[str, Any]: 76 | return {"schema": self.schema_, "type": self.type} 77 | 78 | 79 | class FunctionTool(BaseModel): 80 | description: str | None = None 81 | name: str 82 | parameters: Dict[str, Any] | None = None 83 | 84 | 85 | class FunctionToolChoice(BaseModel): 86 | name: str 87 | 88 | 89 | class Tools(BaseModel): 90 | type: str 91 | function: FunctionTool 92 | 93 | 94 | class ToolChoice(BaseModel): 95 | type: str 96 | function: FunctionToolChoice 97 | 98 | 99 | class ToolChoiceEnum(str, Enum): 100 | Auto = "auto" 101 | Required = "required" 102 | 103 | 104 | class ChatCompletionRequest(BaseModel): 105 | # list of messages 106 | messages: List[ChatCompletionMessage] 107 | # model name 108 | model: str 109 | # stopping criteria: max tokens to generate 110 | max_tokens: int | None = None 111 | # stopping criteria: list of strings to stop generation 112 | stop: List[str] | None = None 113 | # sampling hyperparameters 114 | temperature: float | None = None 115 | top_p: float | None = None 116 | top_k: int | None = None 117 | repetition_penalty: float | None = None 118 | presence_penalty: float | None = None 119 | frequency_penalty: float | None = None 120 | min_p: float | None = None 121 | logit_bias: Dict[str, float] | None = None 122 | seed: int | None = None 123 | # stream SSE token chunks 124 | stream: bool = False 125 | # return logprobs 126 | logprobs: int | None = None 127 | # echo prompt. 128 | # can be used with logprobs to return prompt logprobs 129 | echo: bool | None = None 130 | # number of output generations 131 | n: int | None = None 132 | # moderation model 133 | safety_model: str | None = None 134 | # constraints 135 | response_format: ResponseFormat | None = None 136 | tools: List[Tools] | None = None 137 | tool_choice: ToolChoice | ToolChoiceEnum | None = None 138 | 139 | # Raise warning if repetition_penalty is used with presence_penalty or frequency_penalty 140 | @model_validator(mode="after") 141 | def verify_parameters(self) -> Self: 142 | if self.repetition_penalty: 143 | if self.presence_penalty or self.frequency_penalty: 144 | warnings.warn( 145 | "repetition_penalty is not advisable to be used alongside presence_penalty or frequency_penalty" 146 | ) 147 | return self 148 | 149 | 150 | class ChatCompletionChoicesData(BaseModel): 151 | index: int | None = None 152 | logprobs: LogprobsPart | None = None 153 | seed: int | None = None 154 | finish_reason: FinishReason | None = None 155 | message: ChatCompletionMessage | None = None 156 | 157 | 158 | class ChatCompletionResponse(BaseModel): 159 | # request id 160 | id: str | None = None 161 | # object type 162 | object: ObjectType | None = None 163 | # created timestamp 164 | created: int | None = None 165 | # model name 166 | model: str | None = None 167 | # choices list 168 | choices: List[ChatCompletionChoicesData] | None = None 169 | # prompt list 170 | prompt: List[PromptPart] | List[None] | None = None 171 | # token usage data 172 | usage: UsageData | None = None 173 | 174 | 175 | class ChatCompletionChoicesChunk(BaseModel): 176 | index: int | None = None 177 | logprobs: float | None = None 178 | seed: int | None = None 179 | finish_reason: FinishReason | None = None 180 | delta: DeltaContent | None = None 181 | 182 | 183 | class ChatCompletionChunk(BaseModel): 184 | # request id 185 | id: str | None = None 186 | # object type 187 | object: ObjectType | None = None 188 | # created timestamp 189 | created: int | None = None 190 | # model name 191 | model: str | None = None 192 | # delta content 193 | choices: List[ChatCompletionChoicesChunk] | None = None 194 | # finish reason 195 | finish_reason: FinishReason | None = None 196 | # token usage data 197 | usage: UsageData | None = None 198 | -------------------------------------------------------------------------------- /src/together/types/code_interpreter.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, Dict, Literal, Union 4 | 5 | from pydantic import Field 6 | 7 | from together.types.endpoints import TogetherJSONModel 8 | 9 | 10 | class FileInput(TogetherJSONModel): 11 | """File input to be uploaded to the code interpreter session.""" 12 | 13 | name: str = Field(description="The name of the file.") 14 | encoding: Literal["string", "base64"] = Field( 15 | description="Encoding of the file content. Use 'string' for text files and 'base64' for binary files." 16 | ) 17 | content: str = Field(description="The content of the file, encoded as specified.") 18 | 19 | 20 | class InterpreterOutput(TogetherJSONModel): 21 | """Base class for interpreter output types.""" 22 | 23 | type: Literal["stdout", "stderr", "error", "display_data", "execute_result"] = ( 24 | Field(description="The type of output") 25 | ) 26 | data: Union[str, Dict[str, Any]] = Field(description="The output data") 27 | 28 | 29 | class ExecuteResponseData(TogetherJSONModel): 30 | """Data from code execution response.""" 31 | 32 | outputs: list[InterpreterOutput] = Field( 33 | description="List of outputs from execution", default_factory=list 34 | ) 35 | errors: Union[str, None] = Field( 36 | description="Any errors that occurred during execution", default=None 37 | ) 38 | session_id: str = Field( 39 | description="Identifier of the current session. Used to make follow-up calls." 40 | ) 41 | status: str = Field(description="Status of the execution", default="completed") 42 | 43 | 44 | class ExecuteResponse(TogetherJSONModel): 45 | """Response from code execution.""" 46 | 47 | data: ExecuteResponseData = Field( 48 | description="The response data containing outputs and session information" 49 | ) 50 | 51 | 52 | __all__ = [ 53 | "FileInput", 54 | "InterpreterOutput", 55 | "ExecuteResponseData", 56 | "ExecuteResponse", 57 | ] 58 | -------------------------------------------------------------------------------- /src/together/types/common.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | from typing import Any, Dict, List 5 | 6 | from pydantic import ConfigDict 7 | from tqdm.utils import CallbackIOWrapper 8 | 9 | from together.types.abstract import BaseModel 10 | 11 | 12 | # Generation finish reason 13 | class FinishReason(str, Enum): 14 | Length = "length" 15 | StopSequence = "stop" 16 | EOS = "eos" 17 | ToolCalls = "tool_calls" 18 | Error = "error" 19 | Null = "" 20 | 21 | 22 | class UsageData(BaseModel): 23 | prompt_tokens: int 24 | completion_tokens: int 25 | total_tokens: int 26 | 27 | 28 | class ObjectType(str, Enum): 29 | Completion = "text.completion" 30 | CompletionChunk = "completion.chunk" 31 | ChatCompletion = "chat.completion" 32 | ChatCompletionChunk = "chat.completion.chunk" 33 | Embedding = "embedding" 34 | FinetuneEvent = "fine-tune-event" 35 | File = "file" 36 | Model = "model" 37 | 38 | 39 | class LogprobsPart(BaseModel): 40 | # token list 41 | tokens: List[str | None] | None = None 42 | # token logprob list 43 | token_logprobs: List[float | None] | None = None 44 | 45 | 46 | class PromptPart(BaseModel): 47 | # prompt string 48 | text: str | None = None 49 | # list of prompt logprobs 50 | logprobs: LogprobsPart | None = None 51 | 52 | 53 | class DeltaContent(BaseModel): 54 | content: str | None = None 55 | 56 | 57 | class TogetherRequest(BaseModel): 58 | model_config = ConfigDict(arbitrary_types_allowed=True) 59 | 60 | method: str 61 | url: str 62 | headers: Dict[str, str] | None = None 63 | params: Dict[str, Any] | CallbackIOWrapper | None = None 64 | files: Dict[str, Any] | None = None 65 | allow_redirects: bool = True 66 | override_headers: bool = False 67 | -------------------------------------------------------------------------------- /src/together/types/completions.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import warnings 4 | from typing import Dict, List 5 | 6 | from pydantic import model_validator 7 | from typing_extensions import Self 8 | 9 | from together.types.abstract import BaseModel 10 | from together.types.common import ( 11 | DeltaContent, 12 | FinishReason, 13 | LogprobsPart, 14 | ObjectType, 15 | PromptPart, 16 | UsageData, 17 | ) 18 | 19 | 20 | class CompletionRequest(BaseModel): 21 | # prompt to complete 22 | prompt: str 23 | # query model 24 | model: str 25 | # stopping criteria: max tokens to generate 26 | max_tokens: int | None = None 27 | # stopping criteria: list of strings to stop generation 28 | stop: List[str] | None = None 29 | # sampling hyperparameters 30 | temperature: float | None = None 31 | top_p: float | None = None 32 | top_k: int | None = None 33 | repetition_penalty: float | None = None 34 | presence_penalty: float | None = None 35 | frequency_penalty: float | None = None 36 | min_p: float | None = None 37 | logit_bias: Dict[str, float] | None = None 38 | seed: int | None = None 39 | # stream SSE token chunks 40 | stream: bool = False 41 | # return logprobs 42 | logprobs: int | None = None 43 | # echo prompt. 44 | # can be used with logprobs to return prompt logprobs 45 | echo: bool | None = None 46 | # number of output generations 47 | n: int | None = None 48 | # moderation model 49 | safety_model: str | None = None 50 | 51 | # Raise warning if repetition_penalty is used with presence_penalty or frequency_penalty 52 | @model_validator(mode="after") 53 | def verify_parameters(self) -> Self: 54 | if self.repetition_penalty: 55 | if self.presence_penalty or self.frequency_penalty: 56 | warnings.warn( 57 | "repetition_penalty is not advisable to be used alongside presence_penalty or frequency_penalty" 58 | ) 59 | return self 60 | 61 | 62 | class CompletionChoicesData(BaseModel): 63 | index: int 64 | logprobs: LogprobsPart | None = None 65 | seed: int | None = None 66 | finish_reason: FinishReason 67 | text: str 68 | 69 | 70 | class CompletionChoicesChunk(BaseModel): 71 | index: int | None = None 72 | logprobs: float | None = None 73 | seed: int | None = None 74 | finish_reason: FinishReason | None = None 75 | delta: DeltaContent | None = None 76 | 77 | 78 | class CompletionResponse(BaseModel): 79 | # request id 80 | id: str | None = None 81 | # object type 82 | object: ObjectType | None = None 83 | # created timestamp 84 | created: int | None = None 85 | # model name 86 | model: str | None = None 87 | # choices list 88 | choices: List[CompletionChoicesData] | None = None 89 | # prompt list 90 | prompt: List[PromptPart] | None = None 91 | # token usage data 92 | usage: UsageData | None = None 93 | 94 | 95 | class CompletionChunk(BaseModel): 96 | # request id 97 | id: str | None = None 98 | # object type 99 | object: ObjectType | None = None 100 | # created timestamp 101 | created: int | None = None 102 | # model name 103 | model: str | None = None 104 | # choices list 105 | choices: List[CompletionChoicesChunk] | None = None 106 | # token usage data 107 | usage: UsageData | None = None 108 | -------------------------------------------------------------------------------- /src/together/types/embeddings.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List, Literal 4 | 5 | from together.types.abstract import BaseModel 6 | from together.types.common import ( 7 | ObjectType, 8 | ) 9 | 10 | 11 | class EmbeddingRequest(BaseModel): 12 | # input or list of inputs 13 | input: str | List[str] 14 | # model to query 15 | model: str 16 | 17 | 18 | class EmbeddingChoicesData(BaseModel): 19 | # response index 20 | index: int 21 | # object type 22 | object: ObjectType 23 | # embedding response 24 | embedding: List[float] | None = None 25 | 26 | 27 | class EmbeddingResponse(BaseModel): 28 | # job id 29 | id: str | None = None 30 | # query model 31 | model: str | None = None 32 | # object type 33 | object: Literal["list"] | None = None 34 | # list of embedding choices 35 | data: List[EmbeddingChoicesData] | None = None 36 | -------------------------------------------------------------------------------- /src/together/types/endpoints.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from datetime import datetime 4 | from typing import Any, Dict, Literal, Optional, Union 5 | 6 | from pydantic import BaseModel, Field 7 | 8 | 9 | class TogetherJSONModel(BaseModel): 10 | """Base model with JSON serialization support.""" 11 | 12 | def model_dump(self, **kwargs: Any) -> Dict[str, Any]: 13 | exclude_none = kwargs.pop("exclude_none", True) 14 | data = super().model_dump(exclude_none=exclude_none, **kwargs) 15 | 16 | # Convert datetime objects to ISO format strings 17 | for key, value in data.items(): 18 | if isinstance(value, datetime): 19 | data[key] = value.isoformat() 20 | 21 | return data 22 | 23 | 24 | class Autoscaling(TogetherJSONModel): 25 | """Configuration for automatic scaling of replicas based on demand.""" 26 | 27 | min_replicas: int = Field( 28 | description="The minimum number of replicas to maintain, even when there is no load" 29 | ) 30 | max_replicas: int = Field( 31 | description="The maximum number of replicas to scale up to under load" 32 | ) 33 | 34 | 35 | class EndpointPricing(TogetherJSONModel): 36 | """Pricing details for using an endpoint.""" 37 | 38 | cents_per_minute: float = Field( 39 | description="Cost per minute of endpoint uptime in cents" 40 | ) 41 | 42 | 43 | class HardwareSpec(TogetherJSONModel): 44 | """Detailed specifications of a hardware configuration.""" 45 | 46 | gpu_type: str = Field(description="The type/model of GPU") 47 | gpu_link: str = Field(description="The GPU interconnect technology") 48 | gpu_memory: Union[float, int] = Field(description="Amount of GPU memory in GB") 49 | gpu_count: int = Field(description="Number of GPUs in this configuration") 50 | 51 | 52 | class HardwareAvailability(TogetherJSONModel): 53 | """Indicates the current availability status of a hardware configuration.""" 54 | 55 | status: Literal["available", "unavailable", "insufficient"] = Field( 56 | description="The availability status of the hardware configuration" 57 | ) 58 | 59 | 60 | class HardwareWithStatus(TogetherJSONModel): 61 | """Hardware configuration details with optional availability status.""" 62 | 63 | object: Literal["hardware"] = Field(description="The type of object") 64 | id: str = Field(description="Unique identifier for the hardware configuration") 65 | pricing: EndpointPricing = Field( 66 | description="Pricing details for this hardware configuration" 67 | ) 68 | specs: HardwareSpec = Field(description="Detailed specifications of this hardware") 69 | availability: Optional[HardwareAvailability] = Field( 70 | default=None, 71 | description="Current availability status of this hardware configuration", 72 | ) 73 | updated_at: datetime = Field( 74 | description="Timestamp of when the hardware status was last updated" 75 | ) 76 | 77 | 78 | class BaseEndpoint(TogetherJSONModel): 79 | """Base class for endpoint models with common fields.""" 80 | 81 | object: Literal["endpoint"] = Field(description="The type of object") 82 | id: Optional[str] = Field( 83 | default=None, description="Unique identifier for the endpoint" 84 | ) 85 | name: str = Field(description="System name for the endpoint") 86 | model: str = Field(description="The model deployed on this endpoint") 87 | type: str = Field(description="The type of endpoint") 88 | owner: str = Field(description="The owner of this endpoint") 89 | state: Literal[ 90 | "PENDING", "STARTING", "STARTED", "STOPPING", "STOPPED", "FAILED", "ERROR" 91 | ] = Field(description="Current state of the endpoint") 92 | created_at: datetime = Field(description="Timestamp when the endpoint was created") 93 | 94 | 95 | class ListEndpoint(BaseEndpoint): 96 | """Details about an endpoint when listed via the list endpoint.""" 97 | 98 | type: Literal["dedicated", "serverless"] = Field(description="The type of endpoint") 99 | 100 | 101 | class DedicatedEndpoint(BaseEndpoint): 102 | """Details about a dedicated endpoint deployment.""" 103 | 104 | id: str = Field(description="Unique identifier for the endpoint") 105 | type: Literal["dedicated"] = Field(description="The type of endpoint") 106 | display_name: str = Field(description="Human-readable name for the endpoint") 107 | hardware: str = Field( 108 | description="The hardware configuration used for this endpoint" 109 | ) 110 | autoscaling: Autoscaling = Field( 111 | description="Configuration for automatic scaling of the endpoint" 112 | ) 113 | 114 | 115 | __all__ = [ 116 | "DedicatedEndpoint", 117 | "ListEndpoint", 118 | "Autoscaling", 119 | "EndpointPricing", 120 | "HardwareSpec", 121 | "HardwareAvailability", 122 | "HardwareWithStatus", 123 | ] 124 | -------------------------------------------------------------------------------- /src/together/types/error.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pydantic import Field 4 | 5 | from together.types.abstract import BaseModel 6 | 7 | 8 | class TogetherErrorResponse(BaseModel): 9 | # error message 10 | message: str | None = None 11 | # error type 12 | type_: str | None = Field(None, alias="type") 13 | # param causing error 14 | param: str | None = None 15 | # error code 16 | code: str | None = None 17 | -------------------------------------------------------------------------------- /src/together/types/files.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | from typing import List, Literal 5 | 6 | from pydantic import Field 7 | 8 | from together.types.abstract import BaseModel 9 | from together.types.common import ( 10 | ObjectType, 11 | ) 12 | 13 | 14 | class FilePurpose(str, Enum): 15 | FineTune = "fine-tune" 16 | 17 | 18 | class FileType(str, Enum): 19 | jsonl = "jsonl" 20 | parquet = "parquet" 21 | 22 | 23 | class FileRequest(BaseModel): 24 | """ 25 | Files request type 26 | """ 27 | 28 | # training file ID 29 | training_file: str 30 | # base model string 31 | model: str 32 | # number of epochs to train for 33 | n_epochs: int 34 | # training learning rate 35 | learning_rate: float 36 | # number of checkpoints to save 37 | n_checkpoints: int | None = None 38 | # training batch size 39 | batch_size: int | None = None 40 | # up to 40 character suffix for output model name 41 | suffix: str | None = None 42 | # weights & biases api key 43 | wandb_api_key: str | None = None 44 | 45 | 46 | class FileResponse(BaseModel): 47 | """ 48 | Files API response type 49 | """ 50 | 51 | id: str 52 | object: Literal[ObjectType.File] 53 | # created timestamp 54 | created_at: int | None = None 55 | type: FileType | None = None 56 | purpose: FilePurpose | None = None 57 | filename: str | None = None 58 | # file byte size 59 | bytes: int | None = None 60 | # JSONL line count 61 | line_count: int | None = Field(None, alias="LineCount") 62 | processed: bool | None = Field(None, alias="Processed") 63 | 64 | 65 | class FileList(BaseModel): 66 | # object type 67 | object: Literal["list"] | None = None 68 | # list of fine-tune job objects 69 | data: List[FileResponse] | None = None 70 | 71 | 72 | class FileDeleteResponse(BaseModel): 73 | # file id 74 | id: str 75 | # object type 76 | object: Literal[ObjectType.File] 77 | # is deleted 78 | deleted: bool 79 | 80 | 81 | class FileObject(BaseModel): 82 | # object type 83 | object: Literal["local"] | None = None 84 | # fine-tune job id 85 | id: str | None = None 86 | # local path filename 87 | filename: str | None = None 88 | # size in bytes 89 | size: int | None = None 90 | -------------------------------------------------------------------------------- /src/together/types/images.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List, Literal 4 | 5 | from together.types.abstract import BaseModel 6 | 7 | 8 | class ImageRequest(BaseModel): 9 | # input or list of inputs 10 | prompt: str 11 | # model to query 12 | model: str 13 | # num generation steps 14 | steps: int | None = 20 15 | # seed 16 | seed: int | None = None 17 | # number of results to return 18 | n: int | None = 1 19 | # pixel height 20 | height: int | None = 1024 21 | # pixel width 22 | width: int | None = 1024 23 | # negative prompt 24 | negative_prompt: str | None = None 25 | 26 | 27 | class ImageChoicesData(BaseModel): 28 | # response index 29 | index: int 30 | # base64 image response 31 | b64_json: str | None = None 32 | # URL hosting image 33 | url: str | None = None 34 | 35 | 36 | class ImageResponse(BaseModel): 37 | # job id 38 | id: str | None = None 39 | # query model 40 | model: str | None = None 41 | # object type 42 | object: Literal["list"] | None = None 43 | # list of embedding choices 44 | data: List[ImageChoicesData] | None = None 45 | -------------------------------------------------------------------------------- /src/together/types/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | from typing import Literal 5 | 6 | from together.types.abstract import BaseModel 7 | from together.types.common import ObjectType 8 | 9 | 10 | class ModelType(str, Enum): 11 | CHAT = "chat" 12 | LANGUAGE = "language" 13 | CODE = "code" 14 | IMAGE = "image" 15 | EMBEDDING = "embedding" 16 | MODERATION = "moderation" 17 | RERANK = "rerank" 18 | AUDIO = "audio" 19 | 20 | 21 | class PricingObject(BaseModel): 22 | input: float | None = None 23 | output: float | None = None 24 | hourly: float | None = None 25 | base: float | None = None 26 | finetune: float | None = None 27 | 28 | 29 | class ModelObject(BaseModel): 30 | # model id 31 | id: str 32 | # object type 33 | object: Literal[ObjectType.Model] 34 | created: int | None = None 35 | # model type 36 | type: ModelType | None = None 37 | # pretty name 38 | display_name: str | None = None 39 | # model creator organization 40 | organization: str | None = None 41 | # link to model resource 42 | link: str | None = None 43 | license: str | None = None 44 | context_length: int | None = None 45 | pricing: PricingObject 46 | -------------------------------------------------------------------------------- /src/together/types/rerank.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List, Literal, Dict, Any 4 | 5 | from together.types.abstract import BaseModel 6 | from together.types.common import UsageData 7 | 8 | 9 | class RerankRequest(BaseModel): 10 | # model to query 11 | model: str 12 | # input or list of inputs 13 | query: str 14 | # list of documents 15 | documents: List[str] | List[Dict[str, Any]] 16 | # return top_n results 17 | top_n: int | None = None 18 | # boolean to return documents 19 | return_documents: bool = False 20 | # field selector for documents 21 | rank_fields: List[str] | None = None 22 | 23 | 24 | class RerankChoicesData(BaseModel): 25 | # response index 26 | index: int 27 | # object type 28 | relevance_score: float 29 | # rerank response 30 | document: Dict[str, Any] | None = None 31 | 32 | 33 | class RerankResponse(BaseModel): 34 | # job id 35 | id: str | None = None 36 | # object type 37 | object: Literal["rerank"] | None = None 38 | # query model 39 | model: str | None = None 40 | # list of reranked results 41 | results: List[RerankChoicesData] | None = None 42 | # usage stats 43 | usage: UsageData | None = None 44 | -------------------------------------------------------------------------------- /src/together/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from together.utils._log import log_debug, log_info, log_warn, log_warn_once, logfmt 2 | from together.utils.api_helpers import default_api_key, get_headers 3 | from together.utils.files import check_file 4 | from together.utils.tools import ( 5 | convert_bytes, 6 | convert_unix_timestamp, 7 | enforce_trailing_slash, 8 | finetune_price_to_dollars, 9 | normalize_key, 10 | parse_timestamp, 11 | format_timestamp, 12 | get_event_step, 13 | ) 14 | 15 | 16 | __all__ = [ 17 | "check_file", 18 | "get_headers", 19 | "default_api_key", 20 | "log_debug", 21 | "log_info", 22 | "log_warn", 23 | "log_warn_once", 24 | "logfmt", 25 | "enforce_trailing_slash", 26 | "normalize_key", 27 | "parse_timestamp", 28 | "format_timestamp", 29 | "get_event_step", 30 | "finetune_price_to_dollars", 31 | "convert_bytes", 32 | "convert_unix_timestamp", 33 | ] 34 | -------------------------------------------------------------------------------- /src/together/utils/_log.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import os 5 | import re 6 | import sys 7 | from typing import Any, Dict 8 | 9 | import together 10 | 11 | 12 | logger = logging.getLogger("together") 13 | 14 | TOGETHER_LOG = os.environ.get("TOGETHER_LOG") 15 | 16 | WARNING_MESSAGES_ONCE = set() 17 | 18 | 19 | def _console_log_level() -> str | None: 20 | if together.log in ["debug", "info"]: 21 | return together.log 22 | elif TOGETHER_LOG in ["debug", "info"]: 23 | return TOGETHER_LOG 24 | else: 25 | return None 26 | 27 | 28 | def logfmt(props: Dict[str, Any]) -> str: 29 | def fmt(key: str, val: Any) -> str: 30 | # Handle case where val is a bytes or bytesarray 31 | if hasattr(val, "decode"): 32 | val = val.decode("utf-8") 33 | # Check if val is already a string to avoid re-encoding into ascii. 34 | if not isinstance(val, str): 35 | val = str(val) 36 | if re.search(r"\s", val): 37 | val = repr(val) 38 | # key should already be a string 39 | if re.search(r"\s", key): 40 | key = repr(key) 41 | return "{key}={val}".format(key=key, val=val) 42 | 43 | return " ".join([fmt(key, val) for key, val in sorted(props.items())]) 44 | 45 | 46 | def log_debug(message: str | Any, **params: Any) -> None: 47 | msg = logfmt(dict(message=message, **params)) 48 | if _console_log_level() == "debug": 49 | print(msg, file=sys.stderr) 50 | logger.debug(msg) 51 | 52 | 53 | def log_info(message: str | Any, **params: Any) -> None: 54 | msg = logfmt(dict(message=message, **params)) 55 | if _console_log_level() in ["debug", "info"]: 56 | print(msg, file=sys.stderr) 57 | logger.info(msg) 58 | 59 | 60 | def log_warn(message: str | Any, **params: Any) -> None: 61 | msg = logfmt(dict(message=message, **params)) 62 | print(msg, file=sys.stderr) 63 | logger.warn(msg) 64 | 65 | 66 | def log_warn_once(message: str | Any, **params: Any) -> None: 67 | msg = logfmt(dict(message=message, **params)) 68 | if msg not in WARNING_MESSAGES_ONCE: 69 | print(msg, file=sys.stderr) 70 | logger.warn(msg) 71 | WARNING_MESSAGES_ONCE.add(msg) 72 | -------------------------------------------------------------------------------- /src/together/utils/api_helpers.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import os 5 | import sys 6 | import platform 7 | from typing import TYPE_CHECKING, Any, Dict 8 | 9 | 10 | if TYPE_CHECKING: 11 | from _typeshed import SupportsKeysAndGetItem 12 | 13 | import together 14 | from together import error 15 | from together.utils._log import _console_log_level 16 | from together.utils import log_info 17 | 18 | 19 | def get_headers( 20 | method: str | None = None, 21 | api_key: str | None = None, 22 | extra: "SupportsKeysAndGetItem[str, Any] | None" = None, 23 | ) -> Dict[str, str]: 24 | """ 25 | Generates request headers with API key, metadata, and supplied headers 26 | 27 | Args: 28 | method (str, optional): HTTP request type (POST, GET, etc.) 29 | Defaults to None. 30 | api_key (str, optional): API key to add as an Authorization header. 31 | Defaults to None. 32 | extra (SupportsKeysAndGetItem[str, Any], optional): Additional headers to add to request. 33 | Defaults to None. 34 | 35 | Returns: 36 | headers (Dict[str, str]): Compiled headers from data 37 | """ 38 | 39 | user_agent = "Together/v1 PythonBindings/%s" % (together.version,) 40 | 41 | uname_without_node = " ".join( 42 | v for k, v in platform.uname()._asdict().items() if k != "node" 43 | ) 44 | ua = { 45 | "bindings_version": together.version, 46 | "httplib": "requests", 47 | "lang": "python", 48 | "lang_version": platform.python_version(), 49 | "platform": platform.platform(), 50 | "publisher": "together", 51 | "uname": uname_without_node, 52 | } 53 | 54 | headers: Dict[str, Any] = { 55 | "X-Together-Client-User-Agent": json.dumps(ua), 56 | "Authorization": f"Bearer {default_api_key(api_key)}", 57 | "User-Agent": user_agent, 58 | } 59 | 60 | if _console_log_level(): 61 | headers["Together-Debug"] = _console_log_level() 62 | if extra: 63 | headers.update(extra) 64 | 65 | return headers 66 | 67 | 68 | def default_api_key(api_key: str | None = None) -> str | None: 69 | """ 70 | API key fallback logic from input argument and environment variable 71 | 72 | Args: 73 | api_key (str, optional): Supplied API key. This argument takes priority over env var 74 | 75 | Returns: 76 | together_api_key (str): Returns API key from supplied input or env var 77 | 78 | Raises: 79 | together.error.AuthenticationError: if API key not found 80 | """ 81 | if api_key: 82 | return api_key 83 | if os.environ.get("TOGETHER_API_KEY"): 84 | return os.environ.get("TOGETHER_API_KEY") 85 | 86 | raise error.AuthenticationError(together.constants.MISSING_API_KEY_MESSAGE) 87 | 88 | 89 | def get_google_colab_secret(secret_name: str = "TOGETHER_API_KEY") -> str | None: 90 | """ 91 | Checks to see if the user is running in Google Colab, and looks for the Together API Key secret. 92 | 93 | Args: 94 | secret_name (str, optional). Defaults to TOGETHER_API_KEY 95 | 96 | Returns: 97 | str: if the API key is found; None if an error occurred or the secret was not found. 98 | """ 99 | # If running in Google Colab, check for Together in notebook secrets 100 | if "google.colab" in sys.modules: 101 | if TYPE_CHECKING: 102 | from google.colab import userdata # type: ignore 103 | else: 104 | from google.colab import userdata 105 | 106 | try: 107 | api_key = userdata.get(secret_name) 108 | if not isinstance(api_key, str): 109 | return None 110 | else: 111 | return str(api_key) 112 | except userdata.NotebookAccessError: 113 | log_info( 114 | "The TOGETHER_API_KEY Colab secret was found, but notebook access is disabled. Please enable notebook " 115 | "access for the secret." 116 | ) 117 | except userdata.SecretNotFoundError: 118 | # warn and carry on 119 | log_info("Colab: No Google Colab secret named TOGETHER_API_KEY was found.") 120 | 121 | return None 122 | 123 | else: 124 | return None 125 | -------------------------------------------------------------------------------- /src/together/utils/tools.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import os 5 | from datetime import datetime 6 | import re 7 | from typing import Any 8 | 9 | 10 | logger = logging.getLogger("together") 11 | 12 | TOGETHER_LOG = os.environ.get("TOGETHER_LOG") 13 | 14 | NANODOLLAR = 1_000_000_000 15 | 16 | 17 | def enforce_trailing_slash(url: str) -> str: 18 | if not url.endswith("/"): 19 | return url + "/" 20 | else: 21 | return url 22 | 23 | 24 | def normalize_key(key: str) -> str: 25 | return key.replace("/", "--").replace("_", "-").replace(" ", "-").lower() 26 | 27 | 28 | def parse_timestamp(timestamp: str) -> datetime | None: 29 | """Parse a timestamp string into a datetime object or None if the string is empty. 30 | 31 | Args: 32 | timestamp (str): Timestamp 33 | 34 | Returns: 35 | datetime | None: Parsed datetime, or None if the string is empty 36 | """ 37 | if timestamp == "": 38 | return None 39 | 40 | formats = ["%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ"] 41 | for fmt in formats: 42 | try: 43 | return datetime.strptime(timestamp, fmt) 44 | except ValueError: 45 | continue 46 | 47 | raise ValueError("Timestamp does not match any expected format") 48 | 49 | 50 | def format_timestamp(timestamp_str: str) -> str: 51 | """Format timestamp to a readable date string. 52 | 53 | Args: 54 | timestamp: A timestamp string 55 | 56 | Returns: 57 | str: Formatted timestamp string (MM/DD/YYYY, HH:MM AM/PM) 58 | """ 59 | timestamp = parse_timestamp(timestamp_str) 60 | if timestamp is None: 61 | return "" 62 | return timestamp.strftime("%m/%d/%Y, %I:%M %p") 63 | 64 | 65 | def get_event_step(event: Any) -> str | None: 66 | """Extract the step number from a checkpoint event. 67 | 68 | Args: 69 | event: A checkpoint event object 70 | 71 | Returns: 72 | str | None: The step number as a string, or None if not found 73 | """ 74 | step = getattr(event, "step", None) 75 | if step is not None: 76 | return str(step) 77 | return None 78 | 79 | 80 | def finetune_price_to_dollars(price: float) -> float: 81 | """Convert fine-tuning job price to dollars 82 | 83 | Args: 84 | price (float): Fine-tuning job price in billing units 85 | 86 | Returns: 87 | float: Price in dollars 88 | """ 89 | return price / NANODOLLAR 90 | 91 | 92 | def convert_bytes(num: float) -> str | None: 93 | """ 94 | Convert bytes to a human-readable format. 95 | 96 | Args: 97 | num (int): Number of bytes. 98 | 99 | Returns: 100 | str: Human-readable representation of the size. 101 | """ 102 | for unit in ["B", "KB", "MB", "GB", "TB", "PB"]: 103 | if num < 1024.0: 104 | return "{:.1f} {}".format(num, unit) 105 | num /= 1024.0 106 | 107 | return None 108 | 109 | 110 | def convert_unix_timestamp(timestamp: int) -> str: 111 | """ 112 | Convert a Unix timestamp to a human-readable date and time format. 113 | 114 | Args: 115 | timestamp (int): Unix timestamp. 116 | 117 | Returns: 118 | str: Human-readable date and time string. 119 | """ 120 | # Convert Unix timestamp to datetime object 121 | dt_object = datetime.fromtimestamp(timestamp) 122 | 123 | # Format datetime object as ISO 8601 string 124 | iso_format = dt_object.strftime("%Y-%m-%dT%H:%M:%S.%fZ") 125 | 126 | return iso_format 127 | -------------------------------------------------------------------------------- /src/together/version.py: -------------------------------------------------------------------------------- 1 | import importlib.metadata 2 | 3 | 4 | VERSION = importlib.metadata.version( 5 | "together" 6 | ) # gets version number from pyproject.toml 7 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/togethercomputer/together-python/554d3e820ffe07aadff258410aa7cedadeb2fc51/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/integration/constants.py: -------------------------------------------------------------------------------- 1 | completion_test_model_list = [ 2 | "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", 3 | ] 4 | chat_test_model_list = [] 5 | embedding_test_model_list = [] 6 | image_test_model_list = [] 7 | moderation_test_model_list = [] 8 | 9 | LLAMA_PROMPT = """Llamas that are well-socialized and trained to halter and lead after weaning are very friendly and pleasant to be around. They are extremely curious and most will approach people easily. However, llamas that are bottle-fed or over-socialized and over-handled as youth will become extremely difficult to handle when mature, when they will begin to treat humans as they treat each other, which is characterized by bouts of spitting, kicking and neck wrestling. 10 | Llamas are now utilized as certified therapy animals in nursing homes and hospitals. Rojo the Llama, located in the Pacific Northwest was certified in 2008. The Mayo Clinic says animal-assisted therapy can reduce pain, depression, anxiety, and fatigue. This type of therapy is growing in popularity, and there are several organizations throughout the United States that participate. 11 | When correctly reared, llamas spitting at a human is a rare thing. Llamas are very social herd animals, however, and do sometimes spit at each other as a way of disciplining lower-ranked llamas in the herd. A llama's social rank in a herd is never static. They can always move up or down in the social ladder by picking small fights. This is usually done between males to see which will become dominant. Their fights are visually dramatic, with spitting, ramming each other with their chests, neck wrestling and kicking, mainly to knock the other off balance. The females are usually only seen spitting as a means of controlling other herd members. One may determine how agitated the llama is by the materials in the spit. The more irritated the llama is, the further back into each of the three stomach compartments it will try to draw materials from for its spit. 12 | While the social structure might always be changing, they live as a family and they do take care of each other. If one notices a strange noise or feels threatened, an alarm call - a loud, shrill sound which rhythmically rises and falls - is sent out and all others become alert. They will often hum to each other as a form of communication. 13 | The sound of the llama making groaning noises or going "mwa" is often a sign of fear or anger. Unhappy or agitated llamas will lay their ears back, while ears being perked upwards is a sign of happiness or curiosity. 14 | An "orgle" is the mating sound of a llama or alpaca, made by the sexually aroused male. The sound is reminiscent of gargling, but with a more forceful, buzzing edge. Males begin the sound when they become aroused and continue throughout copulation. 15 | Using llamas as livestock guards in North America began in the early 1980s, and some sheep producers have used llamas successfully since then. Some would even use them to guard their smaller cousins, the alpaca. They are used most commonly in the western regions of the United States, where larger predators, such as coyotes and feral dogs, are prevalent. Typically, a single gelding (castrated male) is used.""" 16 | 17 | completion_prompt_list = [ 18 | "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.", 19 | "hi," * 25, 20 | LLAMA_PROMPT, 21 | ] 22 | -------------------------------------------------------------------------------- /tests/integration/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/togethercomputer/together-python/554d3e820ffe07aadff258410aa7cedadeb2fc51/tests/integration/resources/__init__.py -------------------------------------------------------------------------------- /tests/integration/resources/generate_hyperparameters.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def random_temperature(): 8 | """Fixture to generate a random float between 0 and 2.""" 9 | return random.uniform(0, 2) 10 | 11 | 12 | @pytest.fixture 13 | def random_top_p(): 14 | """Fixture to generate a random float between 0 and 1.""" 15 | return random.uniform(0, 1) 16 | 17 | 18 | @pytest.fixture 19 | def random_top_k(): 20 | """Fixture to generate a random float between 0 and 100.""" 21 | return random.randint(1, 100) 22 | 23 | 24 | @pytest.fixture 25 | def random_max_tokens(): 26 | """Fixture to generate a random float between 0 and 128.""" 27 | return random.randint(1, 128) 28 | 29 | 30 | @pytest.fixture 31 | def random_repetition_penalty(): 32 | """Fixture to generate a random float between 0 and 1.""" 33 | return random.uniform(0, 2) 34 | 35 | 36 | @pytest.fixture 37 | def random_presence_penalty(): 38 | """Fixture to generate a random float between -2 and 2.""" 39 | return random.uniform(-2, 2) 40 | 41 | 42 | @pytest.fixture 43 | def random_frequency_penalty(): 44 | """Fixture to generate a random float between -2 and 2.""" 45 | return random.uniform(-2, 2) 46 | 47 | 48 | @pytest.fixture 49 | def random_min_p(): 50 | """Fixture to generate a random float between 0 and 1.""" 51 | return random.uniform(0, 1) 52 | -------------------------------------------------------------------------------- /tests/integration/resources/test_completion_stream.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from together.client import Together 6 | from together.types import CompletionChunk 7 | from together.types.common import DeltaContent, ObjectType, UsageData 8 | from together.types.completions import CompletionChoicesChunk 9 | 10 | from .generate_hyperparameters import ( 11 | random_max_tokens, # noqa 12 | random_repetition_penalty, # noqa 13 | random_temperature, # noqa 14 | random_top_k, # noqa 15 | random_top_p, # noqa 16 | ) 17 | 18 | 19 | class TestTogetherCompletionStream: 20 | @pytest.fixture 21 | def sync_together_client(self) -> Together: 22 | """ 23 | Initialize object with mocked API key 24 | """ 25 | TOGETHER_API_KEY = os.getenv("TOGETHER_API_KEY") 26 | return Together(api_key=TOGETHER_API_KEY) 27 | 28 | def test_create( 29 | self, 30 | sync_together_client, 31 | random_max_tokens, # noqa 32 | random_temperature, # noqa 33 | random_top_p, # noqa 34 | random_top_k, # noqa 35 | random_repetition_penalty, # noqa 36 | ) -> None: 37 | prompt = "The space robots have" 38 | model = "meta-llama/Llama-4-Scout-17B-16E-Instruct" 39 | stop = [""] 40 | 41 | # max_tokens should be a reasonable number for this test 42 | assert 0 < random_max_tokens < 1024 43 | 44 | assert 0 <= random_temperature <= 2 45 | 46 | assert 0 <= random_top_p <= 1 47 | 48 | assert 1 <= random_top_k <= 100 49 | 50 | assert 0 <= random_repetition_penalty <= 2 51 | 52 | response = sync_together_client.completions.create( 53 | prompt=prompt, 54 | model=model, 55 | stop=stop, 56 | max_tokens=random_max_tokens, 57 | temperature=random_temperature, 58 | top_p=random_top_p, 59 | top_k=random_top_k, 60 | repetition_penalty=random_repetition_penalty, 61 | stream=True, 62 | ) 63 | 64 | usage = None 65 | 66 | for chunk in response: 67 | assert isinstance(chunk, CompletionChunk) 68 | 69 | assert isinstance(chunk.id, str) 70 | assert isinstance(chunk.created, int) 71 | assert isinstance(chunk.object, ObjectType) 72 | 73 | if chunk.choices: 74 | assert isinstance(chunk.choices[0], CompletionChoicesChunk) 75 | assert isinstance(chunk.choices[0].index, int) 76 | assert isinstance(chunk.choices[0].delta, DeltaContent) 77 | assert isinstance(chunk.choices[0].delta.content, str) 78 | 79 | usage = chunk.usage 80 | 81 | assert isinstance(usage, UsageData) 82 | assert isinstance(usage.prompt_tokens, int) 83 | assert isinstance(usage.completion_tokens, int) 84 | assert isinstance(usage.total_tokens, int) 85 | assert usage.prompt_tokens + usage.completion_tokens == usage.total_tokens 86 | 87 | def test_prompt(self): 88 | pass 89 | 90 | def test_no_prompt(self): 91 | pass 92 | 93 | def test_model(self): 94 | pass 95 | 96 | def test_no_model(self): 97 | pass 98 | 99 | def test_max_tokens(self): 100 | pass 101 | 102 | def test_no_max_tokens(self): 103 | pass 104 | 105 | def test_high_max_tokens(self): 106 | pass 107 | 108 | def test_stop(self): 109 | pass 110 | 111 | def test_no_stop(self): 112 | pass 113 | 114 | def test_temperature(self): 115 | pass 116 | 117 | def test_top_p(self): 118 | pass 119 | 120 | def test_top_k(self): 121 | pass 122 | 123 | def test_repetition_penalty(self): 124 | pass 125 | 126 | def test_echo(self): 127 | pass 128 | 129 | def test_n(self): 130 | pass 131 | 132 | def test_safety_model(self): 133 | pass 134 | -------------------------------------------------------------------------------- /tests/unit/test_async_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest.mock import patch 3 | 4 | import pytest 5 | 6 | from together.client import AsyncTogether 7 | from together.error import AuthenticationError 8 | from together.types import TogetherClient 9 | 10 | 11 | class TestAsyncTogether: 12 | @pytest.fixture 13 | def async_together_instance(self): 14 | """ 15 | Initialize object with mocked API key 16 | """ 17 | with patch.dict("os.environ", {"TOGETHER_API_KEY": "fake_api_key"}): 18 | return AsyncTogether() 19 | 20 | def test_init_with_api_key(self, async_together_instance): 21 | """ 22 | Test API key from environment works 23 | """ 24 | 25 | assert async_together_instance.client.api_key == "fake_api_key" 26 | 27 | def test_init_without_api_key(self): 28 | """ 29 | Test API key without API key raises AuthenticationError 30 | """ 31 | 32 | with patch.dict(os.environ, {"TOGETHER_API_KEY": ""}, clear=True): 33 | with pytest.raises(AuthenticationError): 34 | AsyncTogether() 35 | 36 | def test_init_with_base_url_from_env(self): 37 | """ 38 | Test base_url from environment 39 | """ 40 | 41 | with patch.dict("os.environ", {"TOGETHER_BASE_URL": "https://example.com"}): 42 | async_together = AsyncTogether(api_key="fake_api_key") 43 | 44 | assert async_together.client.base_url == "https://example.com/" 45 | 46 | def test_init_with_default_base_url(self): 47 | """ 48 | Test default base_url 49 | """ 50 | 51 | async_together = AsyncTogether(api_key="fake_api_key") 52 | 53 | assert async_together.client.base_url == "https://api.together.xyz/v1/" 54 | 55 | def test_init_with_supplied_headers(self): 56 | """ 57 | Test initializing with supplied_headers 58 | """ 59 | 60 | supplied_headers = {"header1": "value1", "header2": "value2"} 61 | 62 | async_together = AsyncTogether( 63 | api_key="fake_api_key", supplied_headers=supplied_headers 64 | ) 65 | 66 | assert async_together.client.supplied_headers == supplied_headers 67 | 68 | def test_completions_initialized(self, async_together_instance): 69 | """ 70 | Test initializing completions 71 | """ 72 | 73 | assert async_together_instance.completions is not None 74 | 75 | assert isinstance(async_together_instance.completions._client, TogetherClient) 76 | 77 | def test_chat_initialized(self, async_together_instance): 78 | """ 79 | Test initializing chat 80 | """ 81 | 82 | assert async_together_instance.chat is not None 83 | 84 | assert isinstance(async_together_instance.chat._client, TogetherClient) 85 | 86 | assert isinstance( 87 | async_together_instance.chat.completions._client, TogetherClient 88 | ) 89 | 90 | def test_embeddings_initialized(self, async_together_instance): 91 | """ 92 | Test initializing embeddings 93 | """ 94 | 95 | assert async_together_instance.embeddings is not None 96 | 97 | assert isinstance(async_together_instance.embeddings._client, TogetherClient) 98 | 99 | def test_files_initialized(self, async_together_instance): 100 | """ 101 | Test initializing files 102 | """ 103 | 104 | assert async_together_instance.files is not None 105 | 106 | assert isinstance(async_together_instance.files._client, TogetherClient) 107 | 108 | def test_fine_tuning_initialized(self, async_together_instance): 109 | """ 110 | Test initializing fine_tuning 111 | """ 112 | 113 | assert async_together_instance.fine_tuning is not None 114 | 115 | assert isinstance(async_together_instance.fine_tuning._client, TogetherClient) 116 | 117 | def test_rerank_initialized(self, async_together_instance): 118 | """ 119 | Test initializing rerank 120 | """ 121 | 122 | assert async_together_instance.rerank is not None 123 | 124 | assert isinstance(async_together_instance.rerank._client, TogetherClient) 125 | -------------------------------------------------------------------------------- /tests/unit/test_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest.mock import patch 3 | 4 | import pytest 5 | 6 | from together.client import Together 7 | from together.error import AuthenticationError 8 | from together.types import TogetherClient 9 | 10 | 11 | class TestTogether: 12 | @pytest.fixture 13 | def sync_together_instance(self) -> None: 14 | """ 15 | Initialize object with mocked API key 16 | """ 17 | 18 | with patch.dict("os.environ", {"TOGETHER_API_KEY": "fake_api_key"}, clear=True): 19 | return Together() 20 | 21 | def test_init_with_api_key(self, sync_together_instance): 22 | """ 23 | Test API key from environment works 24 | """ 25 | assert sync_together_instance.client.api_key == "fake_api_key" 26 | 27 | def test_init_without_api_key(self): 28 | """ 29 | Test init without API key raises AuthenticationError 30 | """ 31 | 32 | with patch.dict(os.environ, {"TOGETHER_API_KEY": ""}, clear=True): 33 | with pytest.raises(AuthenticationError): 34 | Together() 35 | 36 | def test_init_with_base_url_from_env(self): 37 | """ 38 | Test base_url from environment 39 | """ 40 | 41 | with patch.dict("os.environ", {"TOGETHER_BASE_URL": "https://example.com"}): 42 | sync_together = Together(api_key="fake_api_key") 43 | 44 | assert sync_together.client.base_url == "https://example.com/" 45 | 46 | def test_init_with_default_base_url(self): 47 | """ 48 | Test default base_url 49 | """ 50 | 51 | with patch.dict("os.environ", clear=True): 52 | sync_together = Together(api_key="fake_api_key") 53 | 54 | assert sync_together.client.base_url == "https://api.together.xyz/v1/" 55 | 56 | def test_init_with_supplied_headers(self): 57 | """ 58 | Test initializing with supplied_headers 59 | """ 60 | 61 | supplied_headers = {"header1": "value1", "header2": "value2"} 62 | 63 | sync_together = Together( 64 | api_key="fake_api_key", supplied_headers=supplied_headers 65 | ) 66 | 67 | assert sync_together.client.supplied_headers == supplied_headers 68 | 69 | def test_completions_initialized(self, sync_together_instance): 70 | """ 71 | Test initializing completions 72 | """ 73 | 74 | assert sync_together_instance.completions is not None 75 | 76 | assert isinstance(sync_together_instance.completions._client, TogetherClient) 77 | 78 | def test_chat_initialized(self, sync_together_instance): 79 | """ 80 | Test initializing chat 81 | """ 82 | 83 | assert sync_together_instance.chat is not None 84 | 85 | assert isinstance(sync_together_instance.chat._client, TogetherClient) 86 | 87 | assert isinstance( 88 | sync_together_instance.chat.completions._client, TogetherClient 89 | ) 90 | 91 | def test_embeddings_initialized(self, sync_together_instance): 92 | """ 93 | Test initializing embeddings 94 | """ 95 | 96 | assert sync_together_instance.embeddings is not None 97 | 98 | assert isinstance(sync_together_instance.embeddings._client, TogetherClient) 99 | 100 | def test_files_initialized(self, sync_together_instance): 101 | """ 102 | Test initializing files 103 | """ 104 | 105 | assert sync_together_instance.files is not None 106 | 107 | assert isinstance(sync_together_instance.files._client, TogetherClient) 108 | 109 | def test_fine_tuning_initialized(self, sync_together_instance): 110 | """ 111 | Test initializing fine_tuning 112 | """ 113 | 114 | assert sync_together_instance.fine_tuning is not None 115 | 116 | assert isinstance(sync_together_instance.fine_tuning._client, TogetherClient) 117 | 118 | def test_rerank_initialized(self, sync_together_instance): 119 | """ 120 | Test initializing rerank 121 | """ 122 | 123 | assert sync_together_instance.rerank is not None 124 | 125 | assert isinstance(sync_together_instance.rerank._client, TogetherClient) 126 | -------------------------------------------------------------------------------- /tests/unit/test_files_checks.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pytest 3 | from pathlib import Path 4 | 5 | from together.constants import MIN_SAMPLES 6 | from together.utils.files import check_file 7 | 8 | 9 | def test_check_jsonl_valid_general(tmp_path: Path): 10 | # Create a valid JSONL file 11 | file = tmp_path / "valid.jsonl" 12 | content = [{"text": "Hello, world!"}, {"text": "How are you?"}] 13 | with file.open("w") as f: 14 | f.write("\n".join(json.dumps(item) for item in content)) 15 | 16 | report = check_file(file) 17 | 18 | assert report["is_check_passed"] 19 | assert report["utf8"] 20 | assert report["num_samples"] == len(content) 21 | assert report["has_min_samples"] 22 | 23 | 24 | def test_check_jsonl_valid_instruction(tmp_path: Path): 25 | # Create a valid JSONL file with instruction format 26 | file = tmp_path / "valid_instruction.jsonl" 27 | content = [ 28 | {"prompt": "Translate the following sentence.", "completion": "Hello, world!"}, 29 | { 30 | "prompt": "Summarize the text.", 31 | "completion": "Weyland-Yutani Corporation creates advanced AI.", 32 | }, 33 | ] 34 | with file.open("w") as f: 35 | f.write("\n".join(json.dumps(item) for item in content)) 36 | 37 | report = check_file(file) 38 | 39 | assert report["is_check_passed"] 40 | assert report["utf8"] 41 | assert report["num_samples"] == len(content) 42 | assert report["has_min_samples"] 43 | 44 | 45 | def test_check_jsonl_valid_conversational_single_turn(tmp_path: Path): 46 | # Create a valid JSONL file with conversational format and 1 user-assistant turn pair 47 | file = tmp_path / "valid_conversational_single_turn.jsonl" 48 | content = [ 49 | { 50 | "messages": [ 51 | {"role": "user", "content": "Hello"}, 52 | {"role": "assistant", "content": "Hi there!"}, 53 | ] 54 | }, 55 | { 56 | "messages": [ 57 | {"role": "user", "content": "How are you?"}, 58 | {"role": "assistant", "content": "I am fine."}, 59 | ] 60 | }, 61 | { 62 | "messages": [ 63 | {"role": "system", "content": "You are a kind AI"}, 64 | {"role": "user", "content": "How are you?"}, 65 | {"role": "assistant", "content": "I am fine."}, 66 | ] 67 | }, 68 | ] 69 | with file.open("w") as f: 70 | f.write("\n".join(json.dumps(item) for item in content)) 71 | 72 | report = check_file(file) 73 | 74 | assert report["is_check_passed"] 75 | assert report["utf8"] 76 | assert report["num_samples"] == len(content) 77 | assert report["has_min_samples"] 78 | 79 | 80 | def test_check_jsonl_valid_conversational_multiple_turns(tmp_path: Path): 81 | # Create a valid JSONL file with conversational format and multiple user-assistant turn pairs 82 | file = tmp_path / "valid_conversational_multiple_turns.jsonl" 83 | content = [ 84 | { 85 | "messages": [ 86 | {"role": "user", "content": "Is it going to rain today?"}, 87 | { 88 | "role": "assistant", 89 | "content": "Yes, expect showers in the afternoon.", 90 | }, 91 | {"role": "user", "content": "What is the weather like in Tokyo?"}, 92 | {"role": "assistant", "content": "It is sunny with a chance of rain."}, 93 | ] 94 | }, 95 | { 96 | "messages": [ 97 | {"role": "user", "content": "Who won the game last night?"}, 98 | {"role": "assistant", "content": "The home team won by two points."}, 99 | {"role": "user", "content": "What is the weather like in Amsterdam?"}, 100 | {"role": "assistant", "content": "It is cloudy with a chance of snow."}, 101 | ] 102 | }, 103 | { 104 | "messages": [ 105 | {"role": "system", "content": "You are a kind AI"}, 106 | {"role": "user", "content": "Who won the game last night?"}, 107 | {"role": "assistant", "content": "The home team won by two points."}, 108 | {"role": "user", "content": "What is the weather like in Amsterdam?"}, 109 | {"role": "assistant", "content": "It is cloudy with a chance of snow."}, 110 | ] 111 | }, 112 | ] 113 | with file.open("w") as f: 114 | f.write("\n".join(json.dumps(item) for item in content)) 115 | 116 | report = check_file(file) 117 | 118 | assert report["is_check_passed"] 119 | assert report["utf8"] 120 | assert report["num_samples"] == len(content) 121 | assert report["has_min_samples"] 122 | 123 | 124 | def test_check_jsonl_empty_file(tmp_path: Path): 125 | # Create an empty JSONL file 126 | file = tmp_path / "empty.jsonl" 127 | file.touch() 128 | 129 | report = check_file(file) 130 | 131 | assert not report["is_check_passed"] 132 | assert report["message"] == "File is empty" 133 | assert report["file_size"] == 0 134 | 135 | 136 | def test_check_jsonl_non_utf8(tmp_path: Path): 137 | # Create a non-UTF-8 encoded JSONL file 138 | file = tmp_path / "non_utf8.jsonl" 139 | file.write_bytes(b"\xff\xfe\xfd") 140 | 141 | report = check_file(file) 142 | 143 | assert not report["is_check_passed"] 144 | assert not report["utf8"] 145 | assert "File is not UTF-8 encoded." in report["message"] 146 | 147 | 148 | def test_check_jsonl_invalid_json(tmp_path: Path): 149 | # Create a JSONL file with invalid JSON 150 | file = tmp_path / "invalid_json.jsonl" 151 | content = [{"text": "Hello, world!"}, "Invalid JSON Line"] 152 | with file.open("w") as f: 153 | f.write("\n".join(json.dumps(item) for item in content)) 154 | 155 | report = check_file(file) 156 | 157 | assert not report["is_check_passed"] 158 | assert "Error parsing file." in report["message"] 159 | 160 | 161 | def test_check_jsonl_missing_required_field(tmp_path: Path): 162 | # Create a JSONL file missing a required field 163 | file = tmp_path / "missing_field.jsonl" 164 | content = [ 165 | {"prompt": "Translate the following sentence.", "completion": "Hello, world!"}, 166 | {"prompt": "Summarize the text."}, 167 | ] 168 | with file.open("w") as f: 169 | f.write("\n".join(json.dumps(item) for item in content)) 170 | 171 | report = check_file(file) 172 | 173 | assert not report["is_check_passed"] 174 | assert ( 175 | "Error parsing file. Could not detect a format for the line 2" 176 | in report["message"] 177 | ) 178 | 179 | 180 | def test_check_jsonl_inconsistent_dataset_format(tmp_path: Path): 181 | # Create a JSONL file with inconsistent dataset formats 182 | file = tmp_path / "inconsistent_format.jsonl" 183 | content = [ 184 | {"messages": [{"role": "user", "content": "Hi"}]}, 185 | {"text": "How are you?"}, # Missing 'messages' 186 | ] 187 | with file.open("w") as f: 188 | f.write("\n".join(json.dumps(item) for item in content)) 189 | 190 | report = check_file(file) 191 | 192 | assert not report["is_check_passed"] 193 | assert ( 194 | "All samples in the dataset must have the same dataset format" 195 | in report["message"] 196 | ) 197 | 198 | 199 | def test_check_jsonl_invalid_role(tmp_path: Path): 200 | # Create a JSONL file with an invalid role 201 | file = tmp_path / "invalid_role.jsonl" 202 | content = [{"messages": [{"role": "invalid_role", "content": "Hi"}]}] 203 | with file.open("w") as f: 204 | f.write("\n".join(json.dumps(item) for item in content)) 205 | 206 | report = check_file(file) 207 | 208 | assert not report["is_check_passed"] 209 | assert "Found invalid role `invalid_role`" in report["message"] 210 | 211 | 212 | def test_check_jsonl_non_alternating_roles(tmp_path: Path): 213 | # Create a JSONL file with non-alternating user/assistant roles 214 | file = tmp_path / "non_alternating_roles.jsonl" 215 | content = [ 216 | { 217 | "messages": [ 218 | {"role": "user", "content": "Hi"}, 219 | {"role": "user", "content": "Hello again"}, 220 | ] 221 | } 222 | ] 223 | with file.open("w") as f: 224 | f.write("\n".join(json.dumps(item) for item in content)) 225 | 226 | report = check_file(file) 227 | 228 | assert not report["is_check_passed"] 229 | assert "Invalid role turns" in report["message"] 230 | 231 | 232 | def test_check_jsonl_invalid_value_type(tmp_path: Path): 233 | # Create a JSONL file with an invalid value type 234 | file = tmp_path / "invalid_value_type.jsonl" 235 | content = [{"text": 123}] 236 | with file.open("w") as f: 237 | f.write("\n".join(json.dumps(item) for item in content)) 238 | 239 | report = check_file(file) 240 | assert not report["is_check_passed"] 241 | assert "Expected string" in report["message"] 242 | 243 | 244 | def test_check_jsonl_missing_field_in_conversation(tmp_path: Path): 245 | file = tmp_path / "missing_field_in_conversation.jsonl" 246 | content = [ 247 | { 248 | "messages": [ 249 | {"role": "user", "content": "Hi"}, 250 | {"role": "assistant"}, 251 | ] 252 | } 253 | ] 254 | with file.open("w") as f: 255 | f.write("\n".join(json.dumps(item) for item in content)) 256 | 257 | report = check_file(file) 258 | assert not report["is_check_passed"] 259 | assert "Field `content` is missing for a turn" in report["message"] 260 | 261 | 262 | def test_check_jsonl_wrong_turn_type(tmp_path: Path): 263 | file = tmp_path / "wrong_turn_type.jsonl" 264 | content = [ 265 | { 266 | "messages": [ 267 | "Hi!", 268 | {"role": "user", "content": "Hi"}, 269 | {"role": "assistant"}, 270 | ] 271 | } 272 | ] 273 | with file.open("w") as f: 274 | f.write("\n".join(json.dumps(item) for item in content)) 275 | 276 | report = check_file(file) 277 | assert not report["is_check_passed"] 278 | assert ( 279 | "Invalid format on line 1 of the input file. Expected a dictionary" 280 | in report["message"] 281 | ) 282 | 283 | 284 | def test_check_jsonl_extra_column(tmp_path: Path): 285 | file = tmp_path / "extra_column.jsonl" 286 | content = [{"text": "Hello, world!", "extra_column": "extra"}] 287 | with file.open("w") as f: 288 | f.write("\n".join(json.dumps(item) for item in content)) 289 | 290 | report = check_file(file) 291 | assert not report["is_check_passed"] 292 | assert "Found extra column" in report["message"] 293 | 294 | 295 | def test_check_jsonl_empty_messages(tmp_path: Path): 296 | file = tmp_path / "empty_messages.jsonl" 297 | content = [{"messages": []}] 298 | with file.open("w") as f: 299 | f.write("\n".join(json.dumps(item) for item in content)) 300 | 301 | report = check_file(file) 302 | assert not report["is_check_passed"] 303 | assert ( 304 | "Expected a non-empty list of messages. Found empty list" in report["message"] 305 | ) 306 | -------------------------------------------------------------------------------- /tests/unit/test_imports.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import importlib 3 | from pathlib import Path 4 | 5 | 6 | def test_importable_all() -> None: 7 | for path in glob.glob("src/together/*"): 8 | relative_path = Path(path).parts[-1] 9 | if relative_path.endswith(".typed"): 10 | continue 11 | module_name = relative_path.split(".")[0] 12 | module = importlib.import_module("together." + module_name) 13 | all_ = getattr(module, "__all__", []) 14 | for cls_ in all_: 15 | getattr(module, cls_) 16 | -------------------------------------------------------------------------------- /tests/unit/test_video_url.py: -------------------------------------------------------------------------------- 1 | from together.types.chat_completions import ( 2 | ChatCompletionMessage, 3 | ChatCompletionMessageContent, 4 | ChatCompletionMessageContentType, 5 | ChatCompletionMessageContentVideoURL, 6 | MessageRole, 7 | ) 8 | 9 | 10 | def test_video_url_message(): 11 | # Test creating a message with video_url content 12 | message = ChatCompletionMessage( 13 | role=MessageRole.USER, 14 | content=[ 15 | ChatCompletionMessageContent( 16 | type=ChatCompletionMessageContentType.TEXT, text="What's in this video?" 17 | ), 18 | ChatCompletionMessageContent( 19 | type=ChatCompletionMessageContentType.VIDEO_URL, 20 | video_url=ChatCompletionMessageContentVideoURL( 21 | url="https://example.com/video.mp4" 22 | ), 23 | ), 24 | ], 25 | ) 26 | 27 | # Verify the message structure 28 | assert message.role == MessageRole.USER 29 | assert isinstance(message.content, list) 30 | assert len(message.content) == 2 31 | 32 | # Verify text content 33 | assert message.content[0].type == ChatCompletionMessageContentType.TEXT 34 | assert message.content[0].text == "What's in this video?" 35 | assert message.content[0].video_url is None 36 | 37 | # Verify video_url content 38 | assert message.content[1].type == ChatCompletionMessageContentType.VIDEO_URL 39 | assert message.content[1].text is None 40 | assert message.content[1].video_url.url == "https://example.com/video.mp4" 41 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38, py39, py310, py311 3 | 4 | [testenv] 5 | deps = pytest 6 | commands = 7 | pytest tests/unit/ 8 | pytest tests/integration/ 9 | passenv = 10 | TOGETHER_API_KEY 11 | --------------------------------------------------------------------------------