diff --git a/.github/CI_DOCUMENTATION.md b/.github/CI_DOCUMENTATION.md new file mode 100644 index 0000000..1bfb2fa --- /dev/null +++ b/.github/CI_DOCUMENTATION.md @@ -0,0 +1,137 @@ +# CI/CD Documentation + +Этот проект использует GitHub Actions для автоматического тестирования на разных версиях Python и операционных системах. + +## 🔄 Workflow Overview + +### 1. **CI (Primary Pipeline)** - `.github/workflows/ci.yml` +Основной CI pipeline, который запускается при каждом push и pull request. + +**Задачи:** +- **Lint**: Проверка кода с помощью ruff, pyright, mypy +- **Build**: Сборка пакета +- **Test**: Полное тестирование на matrix из: + - **Python версии**: 3.8, 3.9, 3.10, 3.11, 3.12 + - **Операционные системы**: Ubuntu, Windows, macOS +- **Docs**: Генерация документации (только Ubuntu) +- **Coverage**: Генерация отчетов покрытия кода +- **Examples**: Тестирование примеров кода с реальным API + +**Особенности:** +- ✅ Кроссплатформенная установка Rye +- ✅ Отдельные команды для Windows (обход Makefile) +- ✅ Матричное тестирование: 15 комбинаций (5 Python × 3 OS) +- ✅ Условная загрузка артефактов покрытия (только Ubuntu + Python 3.12) + +### 2. **Python Compatibility Check** - `.github/workflows/compatibility-check.yml` +Быстрая проверка совместимости со всеми версиями Python. + +**Задачи:** +- **Compatibility Test**: Проверка импорта и базовых тестов на Python 3.8-3.12 +- **Quick Install Test**: Установка через pip на всех ОС + +**Особенности:** +- ⚡ Быстрый запуск (15 минут) +- 🔄 Запускается на push/PR и для develop ветки +- 📦 Тестирует установку через pip (не Rye) + +### 3. **Windows Tests** - `.github/workflows/windows-test.yml` +Специализированное тестирование для Windows без Makefile. + +**Задачи:** +- **Windows Test**: Прямое использование pip и pytest +- **Windows Build Test**: Тестирование сборки пакета + +**Особенности:** +- 🪟 Обход проблем с Makefile на Windows +- 📅 Ежедневный запуск по расписанию (cron) +- 🧪 Прямые Python команды вместо make + +### 4. **Test Status Summary** - `.github/workflows/status.yml` +Мониторинг статуса всех тестов. + +**Задачи:** +- **Status Summary**: Отображение результатов всех workflow +- **Failure Notification**: Автоматическое создание issues при ошибках + +**Особенности:** +- 📊 Сводный отчет по всем тестам +- 🚨 Автоматическое создание issues для main ветки +- 🔗 Ссылки на failed runs + +## 📋 Test Matrix + +| OS | Python Versions | Workflow | Frequency | +|---|---|---|---| +| Ubuntu | 3.8, 3.9, 3.10, 3.11, 3.12 | CI, Compatibility | Push/PR | +| Windows | 3.8, 3.9, 3.10, 3.11, 3.12 | CI, Windows Tests | Push/PR + Daily | +| macOS | 3.8, 3.9, 3.10, 3.11, 3.12 | CI | Push/PR | + +**Всего комбинаций:** 15 (основной CI) + 5 (compatibility) + 5 (Windows) = **25 test jobs** + +## 🚀 Triggers + +### Push Events +- `main` branch: Все workflow + примеры + документация +- Feature branches: CI + Compatibility + +### Pull Request Events +- Targeting `main`: Все workflow +- Targeting `develop`: Compatibility + +### Scheduled Events +- Windows Tests: Ежедневно в 02:00 UTC + +## 🔧 Environment Variables + +Для запуска примеров требуются secrets: +```yaml +EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} +EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} +EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} +EVOLUTION_PROJECT_ID: ${{ secrets.EVOLUTION_PROJECT_ID }} +``` + +## 📊 Artifacts + +- **dist/**: Собранные пакеты (.whl, .tar.gz) +- **coverage/**: Отчеты покрытия кода (HTML, XML, JSON) +- **docs/**: Сгенерированная документация + +## 🛠️ Tools & Dependencies + +- **Build System**: Rye (primary), pip (fallback) +- **Linting**: ruff, pyright, mypy +- **Testing**: pytest, pytest-cov, pytest-asyncio +- **Documentation**: Sphinx +- **Coverage**: coverage.py + badge generation + +## 🔍 Debugging Failed Tests + +1. **Check Status Summary**: Workflow status показывает общее состояние +2. **Review Logs**: Каждый job имеет детальные логи +3. **Auto Issues**: Для main ветки создаются автоматические issues +4. **Matrix Strategy**: `fail-fast: false` позволяет видеть все ошибки + +## 📈 Performance Optimization + +- **Parallel Jobs**: Максимальное использование GitHub Actions runners +- **Selective Runs**: + - Примеры только на PR/main + - Coverage только Ubuntu + Python 3.12 + - Документация только на Linux +- **Caching**: Rye and pip dependencies cached +- **Timeouts**: Разумные timeout для каждого job + +## 🏷️ Badge Status + +README.md содержит бейджи для мониторинга: +- CI status +- Python compatibility +- Windows tests +- Platform support +- Python version coverage + +--- + +> **Примечание**: Этот CI/CD setup обеспечивает надежное тестирование на всех поддерживаемых платформах и версиях Python, гарантируя высокое качество и совместимость пакета. \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 682809c..db8a048 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,14 +63,20 @@ jobs: path: dist/ test: - timeout-minutes: 20 - name: test - runs-on: ubuntu-latest + timeout-minutes: 30 + name: test (Python ${{ matrix.python-version }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} needs: [lint, build] + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + fail-fast: false steps: - uses: actions/checkout@v4 - - name: Install Rye + - name: Install Rye (Unix) + if: runner.os != 'Windows' run: | curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH @@ -78,8 +84,25 @@ jobs: RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - - name: Install dependencies + - name: Install Rye (Windows) + if: runner.os == 'Windows' + run: | + Invoke-WebRequest -Uri "https://github.com/astral-sh/rye/releases/download/0.44.0/rye-x86_64-windows.exe" -OutFile "rye-installer.exe" + .\rye-installer.exe --yes + echo "$env:USERPROFILE\.rye\shims" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + shell: powershell + + - name: Configure Rye Python version + run: rye pin ${{ matrix.python-version }} + + - name: Install dependencies (Unix) + if: runner.os != 'Windows' run: make install-dev + + - name: Install dependencies (Windows) + if: runner.os == 'Windows' + run: | + rye sync --all-features - name: Download build artifacts uses: actions/download-artifact@v4 @@ -87,10 +110,17 @@ jobs: name: dist path: dist/ - - name: Run tests with coverage + - name: Run tests with coverage (Unix) + if: runner.os != 'Windows' run: make test + + - name: Run tests with coverage (Windows) + if: runner.os == 'Windows' + run: | + rye run pytest tests/ -v --cov=evolution_openai --cov-report=html --cov-report=term --cov-report=xml:coverage.xml --cov-report=json:coverage.json - - name: Upload coverage artifacts + - name: Upload coverage artifacts (Ubuntu only) + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' uses: actions/upload-artifact@v4 with: name: coverage @@ -154,14 +184,20 @@ jobs: test-examples: timeout-minutes: 20 - name: test-examples - runs-on: ubuntu-latest + name: test-examples (Python ${{ matrix.python-version }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} needs: [test] if: github.event_name == 'pull_request' && github.base_ref == 'main' || github.event_name == 'push' && github.ref == 'refs/heads/main' + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.10", "3.12"] # Тестируем примеры только на двух версиях для экономии ресурсов + fail-fast: false steps: - uses: actions/checkout@v4 - - name: Install Rye + - name: Install Rye (Unix) + if: runner.os != 'Windows' run: | curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH @@ -169,41 +205,103 @@ jobs: RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - - name: Install dependencies + - name: Install Rye (Windows) + if: runner.os == 'Windows' + run: | + Invoke-WebRequest -Uri "https://github.com/astral-sh/rye/releases/download/0.44.0/rye-x86_64-windows.exe" -OutFile "rye-installer.exe" + .\rye-installer.exe --yes + echo "$env:USERPROFILE\.rye\shims" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + shell: powershell + + - name: Configure Rye Python version + run: rye pin ${{ matrix.python-version }} + + - name: Install dependencies (Unix) + if: runner.os != 'Windows' run: make install-dev + + - name: Install dependencies (Windows) + if: runner.os == 'Windows' + run: rye sync --all-features - - name: Run basic examples + - name: Run basic examples (Unix) + if: runner.os != 'Windows' env: EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} run: make run-examples + + - name: Run basic examples (Windows) + if: runner.os == 'Windows' + env: + EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} + EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} + EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} + run: rye run python examples/basic_usage.py - - name: Run async examples + - name: Run async examples (Unix) + if: runner.os != 'Windows' env: EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} run: make run-async + + - name: Run async examples (Windows) + if: runner.os == 'Windows' + env: + EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} + EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} + EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} + run: rye run python examples/async_examples.py - - name: Run streaming examples + - name: Run streaming examples (Unix) + if: runner.os != 'Windows' env: EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} run: make run-streaming + + - name: Run streaming examples (Windows) + if: runner.os == 'Windows' + env: + EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} + EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} + EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} + run: rye run python examples/streaming_examples.py - - name: Run token management examples + - name: Run token management examples (Unix) + if: runner.os != 'Windows' env: EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} run: make run-tokens + + - name: Run token management examples (Windows) + if: runner.os == 'Windows' + env: + EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} + EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} + EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} + run: rye run python examples/token_management.py - - name: Run foundation models examples + - name: Run foundation models examples (Unix) + if: runner.os != 'Windows' + env: + EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} + EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} + EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} + EVOLUTION_PROJECT_ID: ${{ secrets.EVOLUTION_PROJECT_ID }} + run: make run-foundation-models + + - name: Run foundation models examples (Windows) + if: runner.os == 'Windows' env: EVOLUTION_KEY_ID: ${{ secrets.EVOLUTION_KEY_ID }} EVOLUTION_SECRET: ${{ secrets.EVOLUTION_SECRET }} EVOLUTION_BASE_URL: ${{ secrets.EVOLUTION_BASE_URL }} EVOLUTION_PROJECT_ID: ${{ secrets.EVOLUTION_PROJECT_ID }} - run: make run-foundation-models \ No newline at end of file + run: rye run python examples/foundation_models_example.py \ No newline at end of file diff --git a/.github/workflows/compatibility-check.yml b/.github/workflows/compatibility-check.yml new file mode 100644 index 0000000..f8ef6fc --- /dev/null +++ b/.github/workflows/compatibility-check.yml @@ -0,0 +1,82 @@ +name: Python Compatibility Check + +on: + push: + branches: + - main + - develop + pull_request: + branches: + - main + - develop + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + compatibility-test: + timeout-minutes: 15 + name: Python ${{ matrix.python-version }} compatibility + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Configure Rye Python version + run: rye pin ${{ matrix.python-version }} + + - name: Install dependencies + run: rye sync --all-features + + - name: Check package imports + run: rye run python -c "import evolution_openai; print(f'✅ Package imported successfully on Python ${{ matrix.python-version }}')" + + - name: Run basic unit tests + run: rye run pytest tests/test_client.py tests/test_token_manager.py -v --tb=short + + - name: Run type checking + run: | + rye run pyright src/evolution_openai/ + rye run mypy src/evolution_openai/ + + # Минимальная установка и проверка импорта на всех ОС + quick-install-test: + timeout-minutes: 10 + name: Quick install test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install package via pip + run: | + python -m pip install --upgrade pip + python -m pip install . + + - name: Test basic import + run: python -c "import evolution_openai; print('✅ Package imports successfully')" + + - name: Test basic client creation + run: python -c "from evolution_openai import EvolutionOpenAI; client = EvolutionOpenAI(api_key='test', secret_key='test'); print('✅ Client created successfully')" \ No newline at end of file diff --git a/.github/workflows/status.yml b/.github/workflows/status.yml new file mode 100644 index 0000000..3ae8a00 --- /dev/null +++ b/.github/workflows/status.yml @@ -0,0 +1,105 @@ +name: Test Status Summary + +# Этот workflow запускается после завершения основных тестов +on: + workflow_run: + workflows: ["CI", "Python Compatibility Check", "Windows Tests"] + types: + - completed + +jobs: + test-status: + runs-on: ubuntu-latest + name: Test Status Summary + + steps: + - name: Check CI Status + run: | + echo "## 🧪 Test Status Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ github.event.workflow_run.conclusion }}" == "success" ]; then + echo "✅ **${{ github.event.workflow_run.name }}** - Passed" >> $GITHUB_STEP_SUMMARY + elif [ "${{ github.event.workflow_run.conclusion }}" == "failure" ]; then + echo "❌ **${{ github.event.workflow_run.name }}** - Failed" >> $GITHUB_STEP_SUMMARY + else + echo "⚠️ **${{ github.event.workflow_run.name }}** - ${{ github.event.workflow_run.conclusion }}" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Workflow:** ${{ github.event.workflow_run.name }}" >> $GITHUB_STEP_SUMMARY + echo "**Event:** ${{ github.event.workflow_run.event }}" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** ${{ github.event.workflow_run.head_branch }}" >> $GITHUB_STEP_SUMMARY + echo "**Commit:** ${{ github.event.workflow_run.head_sha }}" >> $GITHUB_STEP_SUMMARY + echo "**Run ID:** ${{ github.event.workflow_run.id }}" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 🔗 Links" >> $GITHUB_STEP_SUMMARY + echo "- [View Run](${{ github.event.workflow_run.html_url }})" >> $GITHUB_STEP_SUMMARY + echo "- [Repository](https://github.com/${{ github.repository }})" >> $GITHUB_STEP_SUMMARY + + notify-failure: + runs-on: ubuntu-latest + name: Notify on Failure + if: ${{ github.event.workflow_run.conclusion == 'failure' }} + + steps: + - name: Create Issue on Failure + uses: actions/github-script@v7 + with: + script: | + // Создаем issue только для основной ветки + if (context.payload.workflow_run.head_branch !== 'main') { + console.log('Skipping issue creation for non-main branch'); + return; + } + + const title = `🚨 Test Failure: ${context.payload.workflow_run.name}`; + const body = ` + ## Test Failure Report + + **Workflow:** ${context.payload.workflow_run.name} + **Branch:** ${context.payload.workflow_run.head_branch} + **Commit:** ${context.payload.workflow_run.head_sha} + **Run ID:** ${context.payload.workflow_run.id} + + ### Details + - **Event:** ${context.payload.workflow_run.event} + - **Status:** ${context.payload.workflow_run.conclusion} + - **Run URL:** ${context.payload.workflow_run.html_url} + + ### Actions Required + - [ ] Investigate the failing tests + - [ ] Fix the issues + - [ ] Verify the fix works across all Python versions and OS + - [ ] Close this issue when resolved + + --- + *This issue was automatically created by the Test Status workflow.* + `; + + // Проверяем, есть ли уже открытый issue для этого workflow + const existingIssues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: 'automated-test-failure', + per_page: 100 + }); + + const existingIssue = existingIssues.data.find(issue => + issue.title.includes(context.payload.workflow_run.name) + ); + + if (!existingIssue) { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: title, + body: body, + labels: ['automated-test-failure', 'bug', 'ci'] + }); + console.log('Created issue for test failure'); + } else { + console.log('Issue already exists for this workflow failure'); + } \ No newline at end of file diff --git a/.github/workflows/windows-test.yml b/.github/workflows/windows-test.yml new file mode 100644 index 0000000..22cb9de --- /dev/null +++ b/.github/workflows/windows-test.yml @@ -0,0 +1,100 @@ +name: Windows Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + # Запуск каждый день в 02:00 UTC + - cron: '0 2 * * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + windows-test: + timeout-minutes: 25 + name: Windows Test (Python ${{ matrix.python-version }}) + runs-on: windows-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install build pytest pytest-cov pytest-asyncio pytest-mock responses time-machine dirty-equals + python -m pip install -e . + + - name: Check package import + run: python -c "import evolution_openai; print('✅ Package imported successfully')" + + - name: Run tests + run: | + python -m pytest tests/ -v --tb=short --cov=evolution_openai --cov-report=term + env: + PYTHONPATH: ${{ github.workspace }}/src + + - name: Test examples (without API calls) + run: | + python -c " + import sys + sys.path.insert(0, 'src') + from evolution_openai import EvolutionOpenAI + # Тестируем создание клиента + client = EvolutionOpenAI(api_key='test', secret_key='test') + print('✅ Client created successfully on Windows') + + # Тестируем базовые методы без API вызовов + try: + from evolution_openai.token_manager import TokenManager + token_manager = TokenManager('test_key', 'test_secret') + print('✅ TokenManager created successfully') + except Exception as e: + print(f'❌ TokenManager test failed: {e}') + sys.exit(1) + " + + windows-build-test: + timeout-minutes: 15 + name: Windows Build Test + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + python -m pip install build twine + + - name: Build package + run: python -m build + + - name: Check package + run: python -m twine check dist/* + + - name: Install built package + run: | + $wheel = Get-ChildItem dist/*.whl | Select-Object -First 1 + python -m pip install $wheel.FullName + + - name: Test installed package + run: python -c "import evolution_openai; print('✅ Built package works on Windows')" \ No newline at end of file diff --git a/README.md b/README.md index 7a36f1b..340fdb9 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@ [![PyPI version](https://badge.fury.io/py/evolution-openai.svg)](https://badge.fury.io/py/evolution-openai) [![Coverage](https://cloud-ru-tech.github.io/evolution-openai-python/badges/coverage.svg)](https://github.com/cloud-ru-tech/evolution-openai-python/actions) +[![CI](https://github.com/cloud-ru-tech/evolution-openai-python/workflows/CI/badge.svg)](https://github.com/cloud-ru-tech/evolution-openai-python/actions/workflows/ci.yml) +[![Python Compatibility](https://github.com/cloud-ru-tech/evolution-openai-python/workflows/Python%20Compatibility%20Check/badge.svg)](https://github.com/cloud-ru-tech/evolution-openai-python/actions/workflows/compatibility-check.yml) +[![Windows Tests](https://github.com/cloud-ru-tech/evolution-openai-python/workflows/Windows%20Tests/badge.svg)](https://github.com/cloud-ru-tech/evolution-openai-python/actions/workflows/windows-test.yml) +[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/) +[![Platforms](https://img.shields.io/badge/platforms-Linux%20%7C%20macOS%20%7C%20Windows-green.svg)](https://github.com/cloud-ru-tech/evolution-openai-python/actions) + **Полностью совместимый** Evolution OpenAI client с автоматическим управлением токенами. Просто замените `OpenAI` на `EvolutionOpenAI` и все будет работать! ## 🎯 Особенности