# PyAI Pipecat starter

Plug PyAI speech services into the transport and LLM pipeline you already use.

## Requirements

Python 3.10–3.13. Only a PyAI key for the included text-to-WAV pipeline. A conversational bot additionally needs a Pipecat transport and an LLM; follow the linked integration guide.

## Run

```sh
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
cp .env.example .env
# Fill in .env, then export its values in this shell:
set -a
. ./.env
set +a
python main.py
```

The downloadable starter is a complete text-to-audio pipeline. It does not open a microphone, room, or phone call.

Select a voice from `GET https://api.pyai.com/v1/voices`. Use the current voice/language support table rather than assuming every voice supports every language. PCM, WAV and G.711 support streaming; MP3 and Opus are buffered.

Keep `.env` out of Git. These starters use server-side credentials.

[Full guide](https://docs.pyai.com/guides/pipecat) · [Agent setup](https://pyai.com/build-with-ai) · [API contract](https://api.pyai.com/openapi.json)

## Download from your terminal

```sh
curl -fSL https://pyai.com/starters/pyai-pipecat.tar.gz -o pyai-pipecat.tar.gz
tar -xzf pyai-pipecat.tar.gz
cd pyai-pipecat
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
cp .env.example .env
# Fill in .env, then export its values in this shell:
set -a
. ./.env
set +a
python main.py
```

## .env.example

```text
PYAI_API_KEY=replace_me
PYAI_VOICE=stock_emma_en_gb
```

## main.py

```python
"""A complete text-to-audio Pipecat pipeline; no transport or LLM account needed."""
import asyncio
import os
import time
import wave
from pipecat.frames.frames import EndFrame, Frame, TTSAudioRawFrame, TTSSpeakFrame
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineParams, PipelineTask
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
from pipecat_pyai import PyAITTSService


class SaveAudio(FrameProcessor):
    def __init__(self, output):
        super().__init__()
        self.output = output
        self.received = 0
        self.started = time.perf_counter()

    async def process_frame(self, frame: Frame, direction: FrameDirection):
        await super().process_frame(frame, direction)
        if isinstance(frame, TTSAudioRawFrame):
            if not self.received:
                print(f"First audio frame: {(time.perf_counter() - self.started) * 1000:.0f} ms")
            self.output.writeframesraw(frame.audio)
            self.received += len(frame.audio)
        await self.push_frame(frame, direction)


async def main():
    with wave.open("speech.wav", "wb") as output:
        output.setparams((1, 2, 24000, 0, "NONE", "not compressed"))
        sink = SaveAudio(output)
        tts = PyAITTSService(voice=os.getenv("PYAI_VOICE", "stock_emma_en_gb"))
        task = PipelineTask(Pipeline([tts, sink]), params=PipelineParams(audio_out_sample_rate=24000))
        await task.queue_frames([TTSSpeakFrame("Hello from PyAI and Pipecat."), EndFrame()])
        await PipelineRunner().run(task)
        if not sink.received:
            raise RuntimeError("No audio frames received")
        print("Saved speech.wav")


if __name__ == "__main__":
    asyncio.run(main())
```

## requirements.txt

```text
pipecat-pyai==0.1.2
pipecat-ai==1.8.1
```
