Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions authors/lucas_chinook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Author: Lucas Chinook Title: AI Engineer Description: Lucas Chinook builds
developer automation, AI-assisted coding workflows, and reproducible cloud
development guides. Lucas focuses on practical systems that engineers can
verify locally before they rely on them in production. Author GitHub:
[GitHub](https://github.com/chinook1001)
20 changes: 20 additions & 0 deletions definitions/20260530_definition_async_speech_transcription.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: 'Async Speech Transcription'
description: 'A speech-to-text workflow that submits audio as a job, polls for completion, and then fetches the transcript.'
date: 2026-05-30
author: 'Lucas Chinook'
---

# Async Speech Transcription

## Definition

Async speech transcription is a speech-to-text pattern where an application uploads audio, creates a transcription job, waits for the service to process that job, and fetches the transcript after completion.

## Context and Usage

Async transcription is useful when audio may be large, processing may take longer than a single HTTP request, or the speech provider offers extra analysis features such as speaker labels, formatting, summaries, or entity extraction.

For command-line tools, the async pattern usually has four parts: upload the media file, submit a job that references the uploaded audio, poll the job status, and write the finished transcript to local output.

That shape keeps the local workflow predictable while still letting the provider run longer processing in the background.
246 changes: 246 additions & 0 deletions guides/20260530_run_assemblyai_transcription_with_sapat_in_daytona.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
---
title: "Run AssemblyAI Transcription With Sapat in Daytona"
description: "Build and verify an AssemblyAI-backed Sapat transcription workflow inside a reproducible Daytona workspace."
date: 2026-05-30
author: "Lucas Chinook"
tags: ["daytona", "sapat", "assemblyai", "speech-to-text"]
---

# Run AssemblyAI Transcription With Sapat in Daytona

# Introduction

Video recordings are easy to collect and hard to operationalize.
A product demo, a customer interview, or a long engineering review can hold useful details, but those details stay trapped until someone turns the audio into searchable text.
Sapat helps with that by converting media files, sending the audio to a speech-to-text provider, and writing a transcript next to the original file.

This guide shows how to run Sapat with AssemblyAI in a Daytona workspace.
The workflow uses an [async speech transcription](../definitions/20260530_definition_async_speech_transcription.md) provider: Sapat uploads the converted audio, submits a transcript job, polls until AssemblyAI finishes processing, and writes the transcript locally.
Daytona gives the process a clean development environment, so the same commands work for local review, provider testing, and future CI checks.

The companion Sapat implementation is in [nibzard/sapat#60](https://github.com/nibzard/sapat/pull/60). It adds the `assemblyai` provider to Sapat's current provider registry after the new plugin architecture refactor.

![Sapat AssemblyAI workflow](./assets/20260530_run_assemblyai_transcription_with_sapat_in_daytona_workflow.svg)

## TL;DR

- Use Daytona to create a reproducible Python workspace for Sapat.
- Install the AssemblyAI-enabled Sapat branch or use upstream Sapat after the provider PR lands.
- Store `ASSEMBLYAI_API_KEY` in `.env`, never in code or transcripts.
- Run Sapat with `--provider assemblyai` and a model fallback such as `universal-3-pro,universal-2`.
- Validate with mocked tests before using real audio, then inspect the generated transcript file.

## Prerequisites

You need:

- A GitHub account and access to a Daytona workspace.
- Python 3.9 or newer in the workspace.
- `ffmpeg`, because Sapat converts source media before transcription.
- An AssemblyAI API key stored outside Git.
- A small MP4 or audio sample that you are allowed to upload to a third-party service.

For reference, AssemblyAI documents file upload through its [upload endpoint](https://www.assemblyai.com/docs/api-reference/files/upload) and transcript job creation through its [transcript submit endpoint](https://www.assemblyai.com/docs/api-reference/transcripts/submit).
The Sapat provider follows that upload, submit, poll, fetch sequence.

## Step 1: Create a Daytona Workspace

Start with an empty repository or a small project repository that contains sample media. For a new scratch project, create these files:

```bash
mkdir sapat-assemblyai-daytona
cd sapat-assemblyai-daytona
mkdir -p .devcontainer samples transcripts
```

Add a `.devcontainer/devcontainer.json` file:

```json
{
"name": "sapat-assemblyai-workspace",
"image": "mcr.microsoft.com/devcontainers/python:3.11-bookworm",
"features": {
"ghcr.io/devcontainers/features/ffmpeg:1": {}
},
"postCreateCommand": "python -m pip install --upgrade pip",
"customizations": {
"vscode": {
"extensions": ["ms-python.python"]
}
}
}
```

Push the repository, open it in Daytona, and wait for the workspace to finish building. The important part is not the exact base image. The important part is that every contributor gets Python, `ffmpeg`, and the same project layout instead of debugging transcription from a half-configured laptop.

## Step 2: Install the AssemblyAI-Enabled Sapat Branch

Until the provider PR is merged, install the companion branch directly:

```bash
python -m venv .venv
. .venv/bin/activate
pip install "git+https://github.com/chinook1001/sapat@codex/add-assemblyai-provider"
```

After the provider lands upstream, switch back to the normal repository:

```bash
pip install "git+https://github.com/nibzard/sapat"
```

Confirm that the CLI is available:

```bash
sapat --help
```

You should see a `--provider` option. Sapat discovers providers dynamically, so the `assemblyai` provider only appears when the required environment variable is present.

## Step 3: Configure AssemblyAI Without Leaking Secrets

Create a local `.env` file:

```bash
cat > .env <<'EOF'
ASSEMBLYAI_API_KEY=replace-with-your-key
ASSEMBLYAI_SPEAKER_LABELS=false
ASSEMBLYAI_PUNCTUATE=true
ASSEMBLYAI_FORMAT_TEXT=true
EOF
```

Then add `.env` to `.gitignore`:

```bash
printf ".env\ntranscripts/\n" >> .gitignore
```

The provider also supports `ASSEMBLYAI_BASE_URL` for mocked or proxied validation, plus `ASSEMBLYAI_POLL_INTERVAL_SECONDS` and `ASSEMBLYAI_TIMEOUT_SECONDS` if you need to tune long jobs. For normal usage, the API key is the only required value.

## Step 4: Run a Single Transcription

Place a short video or audio file in `samples/`. Keep the first test boring: ten to thirty seconds is enough to prove the provider path.

```bash
sapat samples/product-demo.mp4 \
--provider assemblyai \
--model "universal-3-pro,universal-2" \
--language en \
--quality M \
--transcription-prompt "Product names: Daytona, Sapat, AssemblyAI"
```

The model value is comma-separated on purpose. AssemblyAI's current transcript API accepts a `speech_models` array, and the provider turns the CLI string into that list. In this example, Sapat requests `universal-3-pro` first and keeps `universal-2` as a fallback.

When the command finishes, look next to the input file for the generated transcript. If you are processing sensitive material, move transcripts into a private storage path instead of committing them to the repository.

## What Happens Behind the Command

It helps to know what is happening under the hood before you scale this workflow to real project recordings.
Sapat first converts the input media to the provider's preferred audio format.
For the AssemblyAI provider, that is MP3, which keeps the first implementation compatible with the existing Sapat conversion pipeline.

After conversion, the provider opens the audio file as binary data and sends it to AssemblyAI's upload endpoint.
AssemblyAI returns an `upload_url`.
Sapat does not treat that URL as the transcript; it uses the URL as an input to a second request that creates a transcript job.
That second request carries the selected `speech_models`, language configuration, optional prompt, and optional formatting flags.

The provider then polls the transcript endpoint until the job is completed, failed, or timed out.
That polling loop is the reason this provider lives on Sapat's async provider base instead of the simpler OpenAI-compatible provider helper.
When AssemblyAI reports `completed`, Sapat fetches the finished transcript payload and maps it into Sapat's shared `TranscriptionResult` object.

This design is useful in review because each boundary is testable without real audio or a real key.
The mocked tests can prove that Sapat sends the correct upload request, submits the correct JSON payload, handles queued and completed statuses, exposes transcript text, and raises an error for failed jobs.
Real API keys are only needed for manual smoke tests.

## Step 5: Run a Directory Batch

Sapat also accepts a directory of MP4 files:

```bash
sapat samples \
--provider assemblyai \
--model "universal-3-pro,universal-2" \
--language en \
--quality L
```

For batch jobs, start with low or medium quality unless your audio is hard to understand.
Higher quality can improve recognition in noisy files, but it also increases upload size and provider processing time.
Keep source media, generated MP3s, and transcripts out of Git unless they are intentionally public fixtures.

For team usage, add a simple naming rule before the first batch.
For example, keep source files in `samples/raw/`, write final transcripts to `transcripts/reviewed/`, and move any failed or unclear outputs to `transcripts/needs-human-review/`.
That small convention prevents reviewers from mixing raw generated transcripts with edited notes.

Also decide what should happen to source media after the transcript is approved.
Some teams keep source recordings in private object storage and only commit cleaned summaries.
Others keep neither recordings nor transcripts in Git, and use the repository only for the repeatable transcription workflow.
The safer default is to keep private media outside the repository unless the file is intentionally public.

## Step 6: Validate Before Real Usage

The provider implementation was designed so that tests do not need a real AssemblyAI API key. It uses mocked HTTP calls to verify the upload, transcript submission, polling, completion, registry discovery, and error paths.

Inside a checkout of the companion Sapat branch, run:

```bash
python -m venv /tmp/sapat-test-venv
/tmp/sapat-test-venv/bin/python -m pip install pytest pytest-mock black click requests python-dotenv openai halo
PYTHONPATH="$PWD" /tmp/sapat-test-venv/bin/python -m pytest tests -q
PYTHONPATH="$PWD" /tmp/sapat-test-venv/bin/python -m black --check \
sapat/providers/assemblyai.py \
sapat/providers/async_poll.py \
tests/providers/test_group_b.py \
tests/test_registry.py
```

The current provider PR validation passed with `183 passed` and one local LibreSSL warning from `urllib3`. That warning came from the macOS Python SSL build and was not a project test failure.

## Review Checklist

Before opening a content or provider PR, check the workflow like a maintainer would.
This is especially important for transcription providers because a broken provider may still look fine if the guide is polished.

- The provider should be hidden when `ASSEMBLYAI_API_KEY` is missing.
- The provider should appear through Sapat's dynamic registry when the key is present.
- Tests should mock HTTP traffic instead of uploading real media.
- The request payload should include `audio_url` and `speech_models`.
- The polling path should cover at least queued, completed, and failed statuses.
- The guide should never include a real API key, private media file, generated transcript, or payout detail.

For content review, verify that the reader can copy the commands into a Daytona workspace and understand which steps are temporary branch review steps.
Once the Sapat provider merges, update the install command to use upstream Sapat rather than the companion branch.
That keeps the article evergreen instead of pinning readers to a contributor branch forever.

## Troubleshooting

**Problem:** `Provider 'assemblyai' is not available.`

**Solution:** Confirm that `.env` is being loaded and that `ASSEMBLYAI_API_KEY` is set in the Daytona terminal where you run Sapat. Sapat hides providers whose required environment variables are missing.

**Problem:** The command waits longer than expected.

**Solution:** AssemblyAI processing is asynchronous. Use a shorter test file first, then raise `ASSEMBLYAI_TIMEOUT_SECONDS` for long recordings. If your file is very large, split it before upload or run smaller batches.

**Problem:** The transcript is technically correct but misses product names.

**Solution:** Use `--transcription-prompt` with domain terms, acronyms, and speaker names. Keep the prompt short and factual. Do not paste private meeting notes into a prompt unless the recording itself is already approved for upload.

**Problem:** The transcript should include speaker labels.

**Solution:** Set `ASSEMBLYAI_SPEAKER_LABELS=true` in `.env`. Speaker labels are useful for interviews, support calls, and engineering reviews where ownership matters.

## Conclusion

You now have a Daytona workflow that installs Sapat, enables the AssemblyAI provider, keeps credentials out of Git, and turns media files into local transcripts. The same setup works for a one-off product demo or for a repeatable batch job across a folder of recordings.

The strongest habit is to validate the provider with mocked tests before sending real audio. Once that path is green, Daytona gives every reviewer the same environment, Sapat handles conversion and output, and AssemblyAI handles the long-running transcription job.

## References

- [AssemblyAI upload endpoint](https://www.assemblyai.com/docs/api-reference/files/upload)
- [AssemblyAI transcript submission endpoint](https://www.assemblyai.com/docs/api-reference/transcripts/submit)
- [Sapat AssemblyAI provider PR](https://github.com/nibzard/sapat/pull/60)
- [Daytona installation documentation](https://www.daytona.io/docs/installation/installation/)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.