diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..0759bcb69 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,44 @@ +name: build + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + branches: + - "**" + paths-ignore: + - "docs/**" + +concurrency: + group: build-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + name: "Build and Install" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + id: setup_python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Setup virtual environment + run: | + python -m venv .venv + - name: Install basic Python dependencies + run: | + source .venv/bin/activate + python -m pip install --upgrade pip + pip install -r dev-requirements.txt + - name: Build project + run: | + source .venv/bin/activate + python -m build + - name: Install project and other Python dependencies + run: | + source .venv/bin/activate + pip install --editable . diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index da097d237..ad5b160f1 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -29,11 +29,11 @@ jobs: - name: Setup virtual environment run: | python -m venv .venv - - name: Install basic Python dependencies + - name: Install development Python dependencies run: | source .venv/bin/activate python -m pip install --upgrade pip - pip install -r requirements.txt + pip install -r dev-requirements.txt - name: autopep8 id: autopep8 run: | diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 000000000..6ceccd56e --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,69 @@ +name: publish + +on: + workflow_dispatch: + inputs: + gitref: + type: string + description: "what git ref to build" + required: true + +jobs: + build: + name: "Build and upload wheels" + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.gitref }} + - name: Set up Python + id: setup_python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Cache virtual environment + uses: actions/cache@v3 + with: + # We are hashing requirements-dev.txt and requirements-extra.txt which + # contain all dependencies needed to run the tests and examples. + key: venv-${{ runner.os }}-${{ steps.setup_python.outputs.python-version}}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }} + path: .venv + - name: Install system packages + run: sudo apt-get install -y portaudio19-dev + - name: Setup virtual environment + run: | + python -m venv .venv + - name: Install basic Python dependencies + run: | + source .venv/bin/activate + python -m pip install --upgrade pip + pip install -r linux-py3.10-requirements.txt -r dev-requirements.txt + - name: Build project + run: | + source .venv/bin/activate + python -m build + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + path: ./dist + + publish-to-pypi: + name: "Publish to PyPI" + runs-on: ubuntu-latest + needs: [ build ] + environment: + name: pypi + url: https://pypi.org/p/dailyai + permissions: + id-token: write + steps: + - name: Download wheels + uses: actions/download-artifact@v4 + with: + path: ./dist + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + print-hash: true diff --git a/.github/workflows/publish_test.yaml b/.github/workflows/publish_test.yaml new file mode 100644 index 000000000..eba8ba4f9 --- /dev/null +++ b/.github/workflows/publish_test.yaml @@ -0,0 +1,59 @@ +name: publish-test + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + build: + name: "Build and upload wheels" + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.gitref }} + - name: Set up Python + id: setup_python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Setup virtual environment + run: | + python -m venv .venv + - name: Install basic Python dependencies + run: | + source .venv/bin/activate + python -m pip install --upgrade pip + pip install -r dev-requirements.txt + - name: Build project + run: | + source .venv/bin/activate + python -m build + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + path: ./dist + + publish-to-pypi: + name: "Test publish to PyPI" + runs-on: ubuntu-latest + needs: [ build ] + environment: + name: pypi + url: https://pypi.org/p/dailyai + permissions: + id-token: write + steps: + - name: Download wheels + uses: actions/download-artifact@v4 + with: + path: ./dist + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + print-hash: true + repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4222e1e8f..67a77c905 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -29,9 +29,9 @@ jobs: - name: Cache virtual environment uses: actions/cache@v3 with: - # TODO: we are hashing requirements.txt but that doesn't contain all - # our dependencies pinned. - key: venv-${{ runner.os }}-${{ steps.setup_python.outputs.python-version}}-${{ hashFiles('requirements.txt') }} + # We are hashing requirements-dev.txt and requirements-extra.txt which + # contain all dependencies needed to run the tests and examples. + key: venv-${{ runner.os }}-${{ steps.setup_python.outputs.python-version}}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }} path: .venv - name: Install system packages run: sudo apt-get install -y portaudio19-dev @@ -42,17 +42,8 @@ jobs: run: | source .venv/bin/activate python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Build project - run: | - source .venv/bin/activate - python -m build - - name: Install project and other Python dependencies - run: | - source .venv/bin/activate - pip install --editable . + pip install -r linux-py3.10-requirements.txt -r dev-requirements.txt - name: Test with pytest run: | source .venv/bin/activate - pip install pytest pytest --doctest-modules --ignore-glob="*to_be_updated*" src tests diff --git a/README.md b/README.md index 21e38a31d..7f3361446 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,8 @@ Build things like this: [![AI-powered voice patient intake for healthcare](https://img.youtube.com/vi/lDevgsp9vn0/0.jpg)](https://www.youtube.com/watch?v=lDevgsp9vn0) - - - **`dailyai` started as a toolkit for implementing generative AI voice bots.** Things like personal coaches, meeting assistants, story-telling toys for kids, customer support bots, and snarky social companions. - In 2023 a *lot* of us got excited about the possibility of having open-ended conversations with LLMs. It became clear pretty quickly that we were all solving the same [low-level problems](https://www.daily.co/blog/how-to-talk-to-an-llm-with-your-voice/): - low-latency, reliable audio transport - echo cancellation @@ -48,7 +44,7 @@ If you'd like to [implement a service]((https://github.com/daily-co/daily-ai-sdk ## Getting started -Today, the easiest way to get started with `dailyai` is to use [Daily](https://www.daily.co/) as your transport service. This toolkit started life as an internal SDK at Daily and millions of minutes of AI conversation have been served using it and its earlier prototype incarnations. (The [transport base class](https://github.com/daily-co/daily-ai-sdk/blob/main/src/dailyai/services/base_transport_service.py) is easy to extend, though, so feel free to submit PRs if you'd like to implement another transport service.) +Today, the easiest way to get started with `dailyai` is to use [Daily](https://www.daily.co/) as your transport service. This toolkit started life as an internal SDK at Daily and millions of minutes of AI conversation have been served using it and its earlier prototype incarnations. (The [transport base class](https://github.com/daily-co/daily-ai-sdk/blob/main/src/dailyai/transports/abstract_transport.py) is easy to extend, though, so feel free to submit PRs if you'd like to implement another transport service.) ``` # install the module @@ -58,6 +54,18 @@ pip install dailyai cp dot-env.template .env ``` +By default, in order to minimize dependencies, only the basic framework functionality is available. Some third-party AI services require additional +dependencies that you can install with: + +``` +pip install dailyai[option,...] +``` + +Your project may or may not need these, so they're made available as optional requirements. Here is a list: + +- **AI services**: `anthropic`, `azure`, `fal`, `openai`, `playht`, `silero`, `whisper` +- **Transports**: `daily`, `local`, `websocket` + ## Code examples There are two directories of examples: @@ -65,6 +73,12 @@ There are two directories of examples: - [foundational](https://github.com/daily-co/daily-ai-sdk/tree/main/examples/foundational) — demos that build on each other, introducing one or two concepts at a time - [starter apps](https://github.com/daily-co/daily-ai-sdk/tree/main/examples/starter-apps) — complete applications that you can use as starting points for development +Before running the examples you need to install the dependencies (which will install all the dependencies to run all of the examples): + +``` +pip install -r {env}-requirements.txt +``` + To run the example below you need to sign up for a [free Daily account](https://dashboard.daily.co/u/signup) and create a Daily room (so you can hear the LLM talking). After that, join the room's URL directly from a browser tab and run: ``` @@ -76,14 +90,14 @@ python examples/foundational/02-llm-say-one-thing.py _Note that you may need to set up a virtual environment before following the instructions below. For instance, you might need to run the following from the root of the repo:_ ``` -python3 -m venv env -source env/bin/activate +python3 -m venv venv +source venv/bin/activate ``` From the root of this repo, run the following: ``` -pip install -r requirements.txt +pip install -r {env}-requirements.txt -r dev-requirements.txt python -m build ``` @@ -101,13 +115,7 @@ pip install path_to_this_repo ### Running tests -To run tests you need to install `pytest`: - -``` -pip install pytest -``` - -Then, from the root directory, run: +From the root directory, run: ``` pytest --doctest-modules --ignore-glob="*to_be_updated*" src tests diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 000000000..bbd666cf6 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,6 @@ +autopep8==2.0.4 +build==1.0.3 +pip-tools==7.4.1 +pytest==8.1.1 +setuptools==69.2.0 +setuptools_scm==8.0.4 diff --git a/dot-env.template b/dot-env.template index 60e09358a..bbbef51ab 100644 --- a/dot-env.template +++ b/dot-env.template @@ -1,5 +1,25 @@ -OPENAI_API_KEY=... -ELEVENLABS_API_KEY=... -ELEVENLABS_VOICE_ID=... +# Anthropic +ANTHROPIC_API_KEY=... + +# Azure +SPEECH_KEY=... +SPEECH_REGION=... + +# Daily DAILY_API_KEY=... DAILY_SAMPLE_ROOM_URL=https://... + +# ElevenLabs +ELEVENLABS_API_KEY=... +ELEVENLABS_VOICE_ID=... + +# Fal +FAL_KEY_ID=... +FAL_KEY_SECRET=... + +# PlayHT +PLAY_HT_USER_ID=... +PLAY_HT_API_KEY=... + +# OpenAI +OPENAI_API_KEY=... diff --git a/examples/starter-apps/patient-intake.py b/examples/starter-apps/patient-intake.py index 9409d894a..553aacde5 100644 --- a/examples/starter-apps/patient-intake.py +++ b/examples/starter-apps/patient-intake.py @@ -19,12 +19,12 @@ # from dailyai.services.deepgram_ai_services import DeepgramTTSService from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService from dailyai.pipeline.frames import ( - OpenAILLMContextFrame, Frame, LLMFunctionCallFrame, LLMFunctionStartFrame, AudioFrame, ) +from dailyai.pipeline.openai_frames import OpenAILLMContextFrame from dailyai.services.ai_services import FrameLogger, AIService from openai._types import NotGiven, NOT_GIVEN diff --git a/linux-py3.10-requirements.txt b/linux-py3.10-requirements.txt new file mode 100644 index 000000000..ae6faf9e0 --- /dev/null +++ b/linux-py3.10-requirements.txt @@ -0,0 +1,319 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --all-extras pyproject.toml +# +aiohttp==3.9.3 + # via dailyai (pyproject.toml) +aiosignal==1.3.1 + # via aiohttp +anthropic==0.20.0 + # via dailyai (pyproject.toml) +anyio==4.3.0 + # via + # anthropic + # httpx + # openai + # starlette +async-timeout==4.0.3 + # via aiohttp +attrs==23.2.0 + # via + # aiohttp + # fal +av==11.0.0 + # via faster-whisper +azure-cognitiveservices-speech==1.36.0 + # via dailyai (pyproject.toml) +blinker==1.7.0 + # via flask +certifi==2024.2.2 + # via + # httpcore + # httpx + # requests +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via + # fal + # flask +colorama==0.4.6 + # via fal +coloredlogs==15.0.1 + # via onnxruntime +ctranslate2==4.1.0 + # via faster-whisper +daily-python==0.7.2 + # via dailyai (pyproject.toml) +deprecated==1.2.14 + # via opentelemetry-api +dill==0.3.7 + # via fal +distlib==0.3.8 + # via virtualenv +distro==1.9.0 + # via + # anthropic + # openai +exceptiongroup==1.2.0 + # via anyio +fal==0.12.3 + # via dailyai (pyproject.toml) +fastapi==0.99.1 + # via fal +faster-whisper==1.0.1 + # via dailyai (pyproject.toml) +filelock==3.13.3 + # via + # huggingface-hub + # pyht + # torch + # triton + # virtualenv +flask==3.0.2 + # via + # dailyai (pyproject.toml) + # flask-cors +flask-cors==4.0.0 + # via dailyai (pyproject.toml) +flatbuffers==24.3.25 + # via onnxruntime +frozenlist==1.4.1 + # via + # aiohttp + # aiosignal +fsspec==2024.3.1 + # via + # huggingface-hub + # torch +grpc-interceptor==0.15.4 + # via fal +grpcio==1.62.1 + # via + # fal + # grpc-interceptor + # isolate + # isolate-proto + # pyht +h11==0.14.0 + # via httpcore +httpcore==1.0.5 + # via httpx +httpx==0.27.0 + # via + # anthropic + # fal + # openai +huggingface-hub==0.22.2 + # via + # faster-whisper + # tokenizers +humanfriendly==10.0 + # via coloredlogs +idna==3.6 + # via + # anyio + # httpx + # requests + # yarl +importlib-metadata==7.0.0 + # via opentelemetry-api +isolate[build]==0.12.7 + # via + # fal + # isolate-proto +isolate-proto==0.3.3 + # via fal +itsdangerous==2.1.2 + # via flask +jinja2==3.1.3 + # via + # flask + # torch +markdown-it-py==3.0.0 + # via rich +markupsafe==2.1.5 + # via + # jinja2 + # werkzeug +mdurl==0.1.2 + # via markdown-it-py +mpmath==1.3.0 + # via sympy +msgpack==1.0.8 + # via fal +multidict==6.0.5 + # via + # aiohttp + # yarl +networkx==3.2.1 + # via torch +numpy==1.26.4 + # via + # ctranslate2 + # dailyai (pyproject.toml) + # onnxruntime +nvidia-cublas-cu12==12.1.3.1 + # via + # nvidia-cudnn-cu12 + # nvidia-cusolver-cu12 + # torch +nvidia-cuda-cupti-cu12==12.1.105 + # via torch +nvidia-cuda-nvrtc-cu12==12.1.105 + # via torch +nvidia-cuda-runtime-cu12==12.1.105 + # via torch +nvidia-cudnn-cu12==8.9.2.26 + # via torch +nvidia-cufft-cu12==11.0.2.54 + # via torch +nvidia-curand-cu12==10.3.2.106 + # via torch +nvidia-cusolver-cu12==11.4.5.107 + # via torch +nvidia-cusparse-cu12==12.1.0.106 + # via + # nvidia-cusolver-cu12 + # torch +nvidia-nccl-cu12==2.19.3 + # via torch +nvidia-nvjitlink-cu12==12.4.127 + # via + # nvidia-cusolver-cu12 + # nvidia-cusparse-cu12 +nvidia-nvtx-cu12==12.1.105 + # via torch +onnxruntime==1.17.1 + # via faster-whisper +openai==1.14.2 + # via dailyai (pyproject.toml) +opentelemetry-api==1.24.0 + # via + # fal + # opentelemetry-sdk +opentelemetry-sdk==1.24.0 + # via fal +opentelemetry-semantic-conventions==0.45b0 + # via opentelemetry-sdk +packaging==24.0 + # via + # fal + # huggingface-hub + # onnxruntime +pathspec==0.11.2 + # via fal +pillow==10.2.0 + # via + # dailyai (pyproject.toml) + # fal +platformdirs==4.2.0 + # via + # isolate + # virtualenv +portalocker==2.8.2 + # via fal +protobuf==4.25.3 + # via + # isolate + # isolate-proto + # onnxruntime + # pyht +pyaudio==0.2.14 + # via dailyai (pyproject.toml) +pydantic==1.10.15 + # via + # anthropic + # fal + # fastapi + # openai +pygments==2.17.2 + # via rich +pyht==0.0.26 + # via dailyai (pyproject.toml) +pyjwt==2.8.0 + # via fal +python-dateutil==2.9.0.post0 + # via fal +python-dotenv==1.0.1 + # via dailyai (pyproject.toml) +pyyaml==6.0.1 + # via + # ctranslate2 + # huggingface-hub + # isolate +requests==2.31.0 + # via + # huggingface-hub + # pyht +rich==13.7.1 + # via fal +six==1.16.0 + # via python-dateutil +sniffio==1.3.1 + # via + # anthropic + # anyio + # httpx + # openai +starlette==0.27.0 + # via fastapi +structlog==22.3.0 + # via fal +sympy==1.12 + # via + # onnxruntime + # torch +tblib==3.0.0 + # via isolate +tokenizers==0.15.2 + # via + # anthropic + # faster-whisper +torch==2.2.1 + # via + # dailyai (pyproject.toml) + # torchaudio +torchaudio==2.2.1 + # via dailyai (pyproject.toml) +tqdm==4.66.2 + # via + # huggingface-hub + # openai +triton==2.2.0 + # via torch +types-python-dateutil==2.9.0.20240316 + # via fal +typing-extensions==4.10.0 + # via + # anthropic + # anyio + # dailyai (pyproject.toml) + # fal + # fastapi + # huggingface-hub + # openai + # opentelemetry-sdk + # pydantic + # torch +urllib3==2.2.1 + # via requests +virtualenv==20.25.1 + # via isolate +websockets==12.0 + # via + # dailyai (pyproject.toml) + # fal +werkzeug==3.0.2 + # via flask +wrapt==1.16.0 + # via deprecated +yarl==1.9.4 + # via aiohttp +zipp==3.18.1 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/macos-py3.10-requirements.txt b/macos-py3.10-requirements.txt new file mode 100644 index 000000000..cdbe80270 --- /dev/null +++ b/macos-py3.10-requirements.txt @@ -0,0 +1,285 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --all-extras pyproject.toml +# +aiohttp==3.9.3 + # via dailyai (pyproject.toml) +aiosignal==1.3.1 + # via aiohttp +anthropic==0.20.0 + # via dailyai (pyproject.toml) +anyio==4.3.0 + # via + # anthropic + # httpx + # openai + # starlette +async-timeout==4.0.3 + # via aiohttp +attrs==23.2.0 + # via + # aiohttp + # fal +av==11.0.0 + # via faster-whisper +azure-cognitiveservices-speech==1.36.0 + # via dailyai (pyproject.toml) +blinker==1.7.0 + # via flask +certifi==2024.2.2 + # via + # httpcore + # httpx + # requests +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via + # fal + # flask +colorama==0.4.6 + # via fal +coloredlogs==15.0.1 + # via onnxruntime +ctranslate2==4.1.0 + # via faster-whisper +daily-python==0.7.2 + # via dailyai (pyproject.toml) +deprecated==1.2.14 + # via opentelemetry-api +dill==0.3.7 + # via fal +distlib==0.3.8 + # via virtualenv +distro==1.9.0 + # via + # anthropic + # openai +exceptiongroup==1.2.0 + # via anyio +fal==0.12.3 + # via dailyai (pyproject.toml) +fastapi==0.99.1 + # via fal +faster-whisper==1.0.1 + # via dailyai (pyproject.toml) +filelock==3.13.3 + # via + # huggingface-hub + # pyht + # torch + # virtualenv +flask==3.0.2 + # via + # dailyai (pyproject.toml) + # flask-cors +flask-cors==4.0.0 + # via dailyai (pyproject.toml) +flatbuffers==24.3.25 + # via onnxruntime +frozenlist==1.4.1 + # via + # aiohttp + # aiosignal +fsspec==2024.3.1 + # via + # huggingface-hub + # torch +grpc-interceptor==0.15.4 + # via fal +grpcio==1.62.1 + # via + # fal + # grpc-interceptor + # isolate + # isolate-proto + # pyht +h11==0.14.0 + # via httpcore +httpcore==1.0.5 + # via httpx +httpx==0.27.0 + # via + # anthropic + # fal + # openai +huggingface-hub==0.22.2 + # via + # faster-whisper + # tokenizers +humanfriendly==10.0 + # via coloredlogs +idna==3.6 + # via + # anyio + # httpx + # requests + # yarl +importlib-metadata==7.0.0 + # via opentelemetry-api +isolate[build]==0.12.7 + # via + # fal + # isolate-proto +isolate-proto==0.3.3 + # via fal +itsdangerous==2.1.2 + # via flask +jinja2==3.1.3 + # via + # flask + # torch +markdown-it-py==3.0.0 + # via rich +markupsafe==2.1.5 + # via + # jinja2 + # werkzeug +mdurl==0.1.2 + # via markdown-it-py +mpmath==1.3.0 + # via sympy +msgpack==1.0.8 + # via fal +multidict==6.0.5 + # via + # aiohttp + # yarl +networkx==3.2.1 + # via torch +numpy==1.26.4 + # via + # ctranslate2 + # dailyai (pyproject.toml) + # onnxruntime +onnxruntime==1.17.1 + # via faster-whisper +openai==1.14.2 + # via dailyai (pyproject.toml) +opentelemetry-api==1.24.0 + # via + # fal + # opentelemetry-sdk +opentelemetry-sdk==1.24.0 + # via fal +opentelemetry-semantic-conventions==0.45b0 + # via opentelemetry-sdk +packaging==24.0 + # via + # fal + # huggingface-hub + # onnxruntime +pathspec==0.11.2 + # via fal +pillow==10.2.0 + # via + # dailyai (pyproject.toml) + # fal +platformdirs==4.2.0 + # via + # isolate + # virtualenv +portalocker==2.8.2 + # via fal +protobuf==4.25.3 + # via + # isolate + # isolate-proto + # onnxruntime + # pyht +pyaudio==0.2.14 + # via dailyai (pyproject.toml) +pydantic==1.10.15 + # via + # anthropic + # fal + # fastapi + # openai +pygments==2.17.2 + # via rich +pyht==0.0.26 + # via dailyai (pyproject.toml) +pyjwt==2.8.0 + # via fal +python-dateutil==2.9.0.post0 + # via fal +python-dotenv==1.0.1 + # via dailyai (pyproject.toml) +pyyaml==6.0.1 + # via + # ctranslate2 + # huggingface-hub + # isolate +requests==2.31.0 + # via + # huggingface-hub + # pyht +rich==13.7.1 + # via fal +six==1.16.0 + # via python-dateutil +sniffio==1.3.1 + # via + # anthropic + # anyio + # httpx + # openai +starlette==0.27.0 + # via fastapi +structlog==22.3.0 + # via fal +sympy==1.12 + # via + # onnxruntime + # torch +tblib==3.0.0 + # via isolate +tokenizers==0.15.2 + # via + # anthropic + # faster-whisper +torch==2.2.1 + # via + # dailyai (pyproject.toml) + # torchaudio +torchaudio==2.2.1 + # via dailyai (pyproject.toml) +tqdm==4.66.2 + # via + # huggingface-hub + # openai +types-python-dateutil==2.9.0.20240316 + # via fal +typing-extensions==4.10.0 + # via + # anthropic + # anyio + # dailyai (pyproject.toml) + # fal + # fastapi + # huggingface-hub + # openai + # opentelemetry-sdk + # pydantic + # torch +urllib3==2.2.1 + # via requests +virtualenv==20.25.1 + # via isolate +websockets==12.0 + # via + # dailyai (pyproject.toml) + # fal +werkzeug==3.0.2 + # via flask +wrapt==1.16.0 + # via deprecated +yarl==1.9.4 + # via aiohttp +zipp==3.18.1 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/pyproject.toml b/pyproject.toml index 18ca2e56b..ebffca741 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["setuptools"] +requires = ["setuptools>=64", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [project] name = "dailyai" -version = "0.0.3.1" +dynamic = ["version"] description = "An open source framework for real-time, multi-modal, conversational AI applications" license = { text = "BSD 2-Clause License" } readme = "README.md" @@ -20,31 +20,35 @@ classifiers = [ "Topic :: Scientific/Engineering :: Artificial Intelligence" ] dependencies = [ - "aiohttp", - "anthropic", - "azure-cognitiveservices-speech", - "daily-python", - "fal", - "faster_whisper", - "flask", - "flask_cors", - "google-cloud-texttospeech", - "numpy", - "openai", - "Pillow", - "pyht", - "python-dotenv", - "typing-extensions", - "websockets" + "aiohttp==3.9.3", + "numpy==1.26.4", + "Pillow==10.2.0", + "typing-extensions==4.10.0", ] [project.urls] Source = "https://github.com/daily-co/daily-ai-sdk" Website = "https://daily.co" +[project.optional-dependencies] +anthropic = [ "anthropic==0.20.0" ] +azure = [ "azure-cognitiveservices-speech==1.36.0" ] +daily = [ "daily-python==0.7.2" ] +examples = [ "python-dotenv==1.0.1", "flask==3.0.2", "flask_cors==4.0.0" ] +fal = [ "fal==0.12.3" ] +local = [ "pyaudio==0.2.14" ] +openai = [ "openai==1.14.2" ] +playht = [ "pyht==0.0.26" ] +silero = [ "torch==2.2.1", "torchaudio==2.2.1" ] +websocket = [ "websockets==12.0" ] +whisper = [ "faster_whisper==1.0.1" ] + [tool.setuptools.packages.find] # All the following settings are optional: where = ["src"] [tool.pytest.ini_options] pythonpath = ["src"] + +[tool.setuptools_scm] +# Empty diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 6f4b399f1..000000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -autopep8==2.0.4 -build==1.0.3 -packaging==23.2 -pyproject_hooks==1.0.0 diff --git a/src/dailyai/pipeline/frames.py b/src/dailyai/pipeline/frames.py index 658f3c357..4942747b8 100644 --- a/src/dailyai/pipeline/frames.py +++ b/src/dailyai/pipeline/frames.py @@ -1,9 +1,6 @@ from dataclasses import dataclass from typing import Any, List -from dailyai.services.openai_llm_context import OpenAILLMContext -import dailyai.pipeline.protobufs.frames_pb2 as frame_protos - class Frame: def __str__(self): @@ -135,14 +132,6 @@ class LLMMessagesFrame(Frame): messages: List[dict] -@dataclass() -class OpenAILLMContextFrame(Frame): - """Like an LLMMessagesFrame, but with extra context specific to the - OpenAI API. The context in this message is also mutable, and will be - changed by the OpenAIContextAggregator frame processor.""" - context: OpenAILLMContext - - @dataclass() class ReceivedAppMessageFrame(Frame): message: Any diff --git a/src/dailyai/pipeline/opeanai_llm_aggregator.py b/src/dailyai/pipeline/opeanai_llm_aggregator.py index 38c0f4566..b4b254087 100644 --- a/src/dailyai/pipeline/opeanai_llm_aggregator.py +++ b/src/dailyai/pipeline/opeanai_llm_aggregator.py @@ -4,15 +4,21 @@ Frame, LLMResponseEndFrame, LLMResponseStartFrame, - OpenAILLMContextFrame, TextFrame, TranscriptionFrame, UserStartedSpeakingFrame, UserStoppedSpeakingFrame, ) +from dailyai.pipeline.openai_frames import OpenAILLMContextFrame from dailyai.services.openai_llm_context import OpenAILLMContext -from openai.types.chat import ChatCompletionRole +try: + from openai.types.chat import ChatCompletionRole +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use OpenAI, you need to `pip install dailyai[openai]`. Also, set `OPENAI_API_KEY` environment variable.") + raise Exception(f"Missing module: {e}") class OpenAIContextAggregator(FrameProcessor): diff --git a/src/dailyai/pipeline/openai_frames.py b/src/dailyai/pipeline/openai_frames.py new file mode 100644 index 000000000..2a14c670e --- /dev/null +++ b/src/dailyai/pipeline/openai_frames.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass + +from dailyai.pipeline.frames import Frame +from dailyai.services.openai_llm_context import OpenAILLMContext + + +@dataclass() +class OpenAILLMContextFrame(Frame): + """Like an LLMMessagesFrame, but with extra context specific to the + OpenAI API. The context in this message is also mutable, and will be + changed by the OpenAIContextAggregator frame processor.""" + context: OpenAILLMContext diff --git a/src/dailyai/services/anthropic_llm_service.py b/src/dailyai/services/anthropic_llm_service.py index ea48950e7..44c045992 100644 --- a/src/dailyai/services/anthropic_llm_service.py +++ b/src/dailyai/services/anthropic_llm_service.py @@ -1,9 +1,16 @@ from typing import AsyncGenerator -from anthropic import AsyncAnthropic from dailyai.pipeline.frames import Frame, LLMMessagesFrame, TextFrame from dailyai.services.ai_services import LLMService +try: + from anthropic import AsyncAnthropic +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use Anthropic, you need to `pip install dailyai[anthropic]`. Also, set `ANTHROPIC_API_KEY` environment variable.") + raise Exception(f"Missing module: {e}") + class AnthropicLLMService(LLMService): diff --git a/src/dailyai/services/azure_ai_services.py b/src/dailyai/services/azure_ai_services.py index b6ca1e4fd..0f0151144 100644 --- a/src/dailyai/services/azure_ai_services.py +++ b/src/dailyai/services/azure_ai_services.py @@ -9,12 +9,18 @@ from PIL import Image # See .env.example for Azure configuration needed -from azure.cognitiveservices.speech import ( - SpeechSynthesizer, - SpeechConfig, - ResultReason, - CancellationReason, -) +try: + from azure.cognitiveservices.speech import ( + SpeechSynthesizer, + SpeechConfig, + ResultReason, + CancellationReason, + ) +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use Azure TTS, you need to `pip install dailyai[azure]`. Also, set `SPEECH_KEY` and `SPEECH_REGION` environment variables.") + raise Exception(f"Missing module: {e}") from dailyai.services.openai_api_llm_service import BaseOpenAILLMService diff --git a/src/dailyai/services/fal_ai_services.py b/src/dailyai/services/fal_ai_services.py index 10343f97c..9130b062b 100644 --- a/src/dailyai/services/fal_ai_services.py +++ b/src/dailyai/services/fal_ai_services.py @@ -1,4 +1,3 @@ -import fal import aiohttp import asyncio import io @@ -10,7 +9,13 @@ from dailyai.services.ai_services import ImageGenService -# Fal expects FAL_KEY_ID and FAL_KEY_SECRET to be set in the env +try: + import fal +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use Fal, you need to `pip install dailyai[fal]`. Also, set `FAL_KEY_ID` and `FAL_KEY_SECRET` environment variables.") + raise Exception(f"Missing module: {e}") class FalImageGenService(ImageGenService): diff --git a/src/dailyai/services/open_ai_services.py b/src/dailyai/services/open_ai_services.py index 61963769a..9d177f8b7 100644 --- a/src/dailyai/services/open_ai_services.py +++ b/src/dailyai/services/open_ai_services.py @@ -1,12 +1,20 @@ import aiohttp from PIL import Image import io -from openai import AsyncOpenAI from dailyai.services.ai_services import ImageGenService from dailyai.services.openai_api_llm_service import BaseOpenAILLMService +try: + from openai import AsyncOpenAI +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use OpenAI, you need to `pip install dailyai[openai]`. Also, set `OPENAI_API_KEY` environment variable.") + raise Exception(f"Missing module: {e}") + + class OpenAILLMService(BaseOpenAILLMService): def __init__(self, model="gpt-4", * args, **kwargs): diff --git a/src/dailyai/services/openai_api_llm_service.py b/src/dailyai/services/openai_api_llm_service.py index d36e878e2..2ddfc7796 100644 --- a/src/dailyai/services/openai_api_llm_service.py +++ b/src/dailyai/services/openai_api_llm_service.py @@ -1,7 +1,6 @@ import json import time from typing import AsyncGenerator, List -from openai import AsyncOpenAI, AsyncStream from dailyai.pipeline.frames import ( Frame, LLMFunctionCallFrame, @@ -9,17 +8,25 @@ LLMMessagesFrame, LLMResponseEndFrame, LLMResponseStartFrame, - OpenAILLMContextFrame, TextFrame, ) from dailyai.services.ai_services import LLMService +from dailyai.pipeline.openai_frames import OpenAILLMContextFrame from dailyai.services.openai_llm_context import OpenAILLMContext -from openai.types.chat import ( - ChatCompletion, - ChatCompletionChunk, - ChatCompletionMessageParam, -) +try: + from openai import AsyncOpenAI, AsyncStream + + from openai.types.chat import ( + ChatCompletion, + ChatCompletionChunk, + ChatCompletionMessageParam, + ) +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use OpenAI, you need to `pip install dailyai[openai]`. Also, set `OPENAI_API_KEY` environment variable.") + raise Exception(f"Missing module: {e}") class BaseOpenAILLMService(LLMService): diff --git a/src/dailyai/services/openai_llm_context.py b/src/dailyai/services/openai_llm_context.py index ce705f547..2d16c3cb6 100644 --- a/src/dailyai/services/openai_llm_context.py +++ b/src/dailyai/services/openai_llm_context.py @@ -1,11 +1,18 @@ from typing import List -from openai._types import NOT_GIVEN, NotGiven -from openai.types.chat import ( - ChatCompletionToolParam, - ChatCompletionToolChoiceOptionParam, - ChatCompletionMessageParam, -) +try: + from openai._types import NOT_GIVEN, NotGiven + + from openai.types.chat import ( + ChatCompletionToolParam, + ChatCompletionToolChoiceOptionParam, + ChatCompletionMessageParam, + ) +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use OpenAI, you need to `pip install dailyai[openai]`. Also, set `OPENAI_API_KEY` environment variable.") + raise Exception(f"Missing module: {e}") class OpenAILLMContext: diff --git a/src/dailyai/services/playht_ai_service.py b/src/dailyai/services/playht_ai_service.py index 350e5cb88..291855264 100644 --- a/src/dailyai/services/playht_ai_service.py +++ b/src/dailyai/services/playht_ai_service.py @@ -1,11 +1,18 @@ import io import struct -from pyht import Client -from pyht.client import TTSOptions -from pyht.protos.api_pb2 import Format from dailyai.services.ai_services import TTSService +try: + from pyht import Client + from pyht.client import TTSOptions + from pyht.protos.api_pb2 import Format +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use PlayHT, you need to `pip install dailyai[playht]`. Also, set `PLAY_HT_USER_ID` and `PLAY_HT_API_KEY` environment variables.") + raise Exception(f"Missing module: {e}") + class PlayHTAIService(TTSService): diff --git a/src/dailyai/services/whisper_ai_services.py b/src/dailyai/services/whisper_ai_services.py index cc657e6cf..ddddb4f98 100644 --- a/src/dailyai/services/whisper_ai_services.py +++ b/src/dailyai/services/whisper_ai_services.py @@ -3,10 +3,18 @@ from enum import Enum import logging from typing import BinaryIO -from faster_whisper import WhisperModel from dailyai.services.local_stt_service import LocalSTTService +try: + from faster_whisper import WhisperModel +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use Whisper, you need to `pip install dailyai[whisper]`.") + raise Exception(f"Missing module: {e}") + + class Model(Enum): """Class of basic Whisper model selection options""" TINY = "tiny" diff --git a/src/dailyai/transports/daily_transport.py b/src/dailyai/transports/daily_transport.py index d510cadbb..66798782f 100644 --- a/src/dailyai/transports/daily_transport.py +++ b/src/dailyai/transports/daily_transport.py @@ -15,14 +15,21 @@ from threading import Event -from daily import ( - EventHandler, - CallClient, - Daily, - VirtualCameraDevice, - VirtualMicrophoneDevice, - VirtualSpeakerDevice, -) +try: + from daily import ( + EventHandler, + CallClient, + Daily, + VirtualCameraDevice, + VirtualMicrophoneDevice, + VirtualSpeakerDevice, + ) +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use the Daily transport, you need to `pip install dailyai[daily]`.") + raise Exception(f"Missing module: {e}") + from dailyai.transports.threaded_transport import ThreadedTransport diff --git a/src/dailyai/transports/local_transport.py b/src/dailyai/transports/local_transport.py index 4b24fb535..e79b147a8 100644 --- a/src/dailyai/transports/local_transport.py +++ b/src/dailyai/transports/local_transport.py @@ -4,17 +4,18 @@ from dailyai.transports.threaded_transport import ThreadedTransport +try: + import pyaudio +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use the local transport, you need to `pip install dailyai[local]`. On MacOS, you also need to `brew install portaudio`.") + raise Exception(f"Missing module: {e}") + class LocalTransport(ThreadedTransport): def __init__(self, **kwargs): super().__init__(**kwargs) - try: - global pyaudio - import pyaudio - except ModuleNotFoundError as e: - print(f"Exception: {e}") - print("In order to use the local transport, you'll need to `pip install pyaudio`. On MacOS, you'll also need to `brew install portaudio`.") - raise Exception(f"Missing module: {e}") self._sample_width = kwargs.get("sample_width") or 2 self._n_channels = kwargs.get("n_channels") or 1 self._tk_root = kwargs.get("tk_root") or None diff --git a/src/dailyai/transports/threaded_transport.py b/src/dailyai/transports/threaded_transport.py index 033cb97a2..9adb496d4 100644 --- a/src/dailyai/transports/threaded_transport.py +++ b/src/dailyai/transports/threaded_transport.py @@ -1,7 +1,6 @@ from abc import abstractmethod import asyncio import itertools -import logging import numpy as np import queue diff --git a/src/dailyai/transports/websocket_transport.py b/src/dailyai/transports/websocket_transport.py index 0784eb89f..f5009a02e 100644 --- a/src/dailyai/transports/websocket_transport.py +++ b/src/dailyai/transports/websocket_transport.py @@ -1,7 +1,6 @@ import asyncio import time from typing import AsyncGenerator, List -import websockets from dailyai.pipeline.frame_processor import FrameProcessor from dailyai.pipeline.frames import AudioFrame, ControlFrame, EndFrame, Frame, TTSEndFrame, TTSStartFrame, TextFrame @@ -10,6 +9,14 @@ from dailyai.transports.abstract_transport import AbstractTransport from dailyai.transports.threaded_transport import ThreadedTransport +try: + import websockets +except ModuleNotFoundError as e: + print(f"Exception: {e}") + print( + "In order to use the websocket transport, you need to `pip install dailyai[websocket]`.") + raise Exception(f"Missing module: {e}") + class WebSocketFrameProcessor(FrameProcessor): """This FrameProcessor filters and mutates frames before they're sent over the websocket. diff --git a/tests/integration/integration_azure_llm.py b/tests/integration/integration_azure_llm.py index 41cb8ee6c..d4744bd8b 100644 --- a/tests/integration/integration_azure_llm.py +++ b/tests/integration/integration_azure_llm.py @@ -1,8 +1,6 @@ import asyncio import os -from dailyai.pipeline.frames import ( - OpenAILLMContextFrame, -) +from dailyai.pipeline.openai_frames import OpenAILLMContextFrame from dailyai.services.azure_ai_services import AzureLLMService from dailyai.services.openai_llm_context import OpenAILLMContext diff --git a/tests/integration/integration_ollama_llm.py b/tests/integration/integration_ollama_llm.py index 527a20e98..2ac90ce65 100644 --- a/tests/integration/integration_ollama_llm.py +++ b/tests/integration/integration_ollama_llm.py @@ -1,7 +1,5 @@ import asyncio -from dailyai.pipeline.frames import ( - OpenAILLMContextFrame, -) +from dailyai.pipeline.openai_frames import OpenAILLMContextFrame from dailyai.services.openai_llm_context import OpenAILLMContext from openai.types.chat import ( diff --git a/tests/integration/integration_openai_llm.py b/tests/integration/integration_openai_llm.py index baea80d00..fa4a449ec 100644 --- a/tests/integration/integration_openai_llm.py +++ b/tests/integration/integration_openai_llm.py @@ -1,8 +1,6 @@ import asyncio import os -from dailyai.pipeline.frames import ( - OpenAILLMContextFrame, -) +from dailyai.pipeline.openai_frames import OpenAILLMContextFrame from dailyai.services.openai_llm_context import OpenAILLMContext from openai.types.chat import (