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
23 changes: 23 additions & 0 deletions backend/PIPECAT_MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Pipecat backend alignment

The FastAPI backend now mirrors Pipecat's session-aware routing. Existing `.env`
values remain compatible through the `PipecatSettings` shim, which reuses the
legacy `Settings` model while exposing `asr_model_path`, `tokenizer_path`, and
`vad_model_path` aliases expected by Pipecat components.

## Routing changes
- `POST /api/sessions` creates or upserts a Pipecat session (you can provide
your own `session_id`).
- `POST /api/sessions/{session_id}/transcriptions` sends audio for that session.
- `POST /api/transcriptions` remains as a compatibility shim that routes through
a default session.

## Settings surface
- The existing environment variables continue to work. Paths defined under
`models__parakeet_model_path`, `models__parakeet_tokenizer_path`, and
`models__silero_vad_path` are now surfaced to Pipecat as
`asr_model_path`, `tokenizer_path`, and `vad_model_path`.
- API prefix remains driven by `API_PREFIX` (`/api` by default) to match
Pipecat's defaults.

No data migrations are required; model downloads remain in the same locations.
34 changes: 3 additions & 31 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
from __future__ import annotations

import json
from typing import Annotated

from fastapi import FastAPI, File, Form, UploadFile
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from loguru import logger

from app.config import Settings, get_settings
from app.models.requests import TranscriptionRequest
from app.models.responses import TranscriptionResult
from app.services.transcription_service import ParakeetTranscriptionService
from app.pipecat import router as pipecat_router


def create_app() -> FastAPI:
settings = get_settings()
app = FastAPI(title="Parakeet Local", version="1.0.0")

app.add_middleware(
Expand All @@ -25,27 +17,7 @@ def create_app() -> FastAPI:
allow_headers=["*"],
)

service = ParakeetTranscriptionService(settings)

@app.get(f"{settings.api_prefix}/health")
async def healthcheck() -> dict[str, str]:
return {"status": "ok"}

@app.post(f"{settings.api_prefix}/transcriptions", response_model=TranscriptionResult)
async def transcribe_audio(
file: UploadFile = File(...),
payload: Annotated[str | None, Form()] = None,
) -> TranscriptionResult:
body = TranscriptionRequest()
if payload:
try:
body = TranscriptionRequest(**json.loads(payload))
except json.JSONDecodeError as exc:
logger.warning("Failed to decode payload JSON: {}", exc)
audio_bytes = await file.read()
result = service.transcribe_bytes(audio_bytes, request=body, filename=file.filename)
return result

app.include_router(pipecat_router)
return app


Expand Down
6 changes: 6 additions & 0 deletions backend/app/pipecat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Pipecat-compatible backend wiring for Parakeet Local."""

from app.pipecat.api import router
from app.pipecat.settings import PipecatSettings, get_pipecat_settings

__all__ = ["router", "PipecatSettings", "get_pipecat_settings"]
66 changes: 66 additions & 0 deletions backend/app/pipecat/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from __future__ import annotations

import json
from typing import Annotated

from fastapi import APIRouter, File, Form, UploadFile
from loguru import logger

from app.pipecat.schemas import (
SessionCreateRequest,
SessionCreateResponse,
TranscriptionRequest,
TranscriptionResult,
)
from app.pipecat.session import SessionManager
from app.pipecat.settings import get_pipecat_settings

router = APIRouter()

settings = get_pipecat_settings()
session_manager = SessionManager(settings)


@router.get(f"{settings.api_prefix}/health")
async def healthcheck() -> dict[str, str]:
return {"status": "ok"}


@router.post(f"{settings.api_prefix}/sessions", response_model=SessionCreateResponse)
async def create_session(payload: SessionCreateRequest | None = None) -> SessionCreateResponse:
return session_manager.create_session(payload)


@router.post(f"{settings.api_prefix}/sessions/{{session_id}}/transcriptions", response_model=TranscriptionResult)
async def transcribe_audio(
session_id: str,
file: UploadFile = File(...),
payload: Annotated[str | None, Form()] = None,
) -> TranscriptionResult:
body = TranscriptionRequest(session_id=session_id)
if payload:
try:
body = TranscriptionRequest(**json.loads(payload))
except json.JSONDecodeError as exc:
logger.warning("Failed to decode payload JSON: {}", exc)
body = TranscriptionRequest(session_id=session_id)

if body.session_id and body.session_id != session_id:
logger.warning(
"Payload session_id %s does not match path session_id %s; using path value",
body.session_id,
session_id,
)
body.session_id = session_id
audio_bytes = await file.read()
result = session_manager.transcribe(session_id, body, audio_bytes, filename=file.filename)
return result


@router.post(f"{settings.api_prefix}/transcriptions", response_model=TranscriptionResult)
async def transcribe_with_default_session(
file: UploadFile = File(...),
payload: Annotated[str | None, Form()] = None,
) -> TranscriptionResult:
default_session = session_manager.create_session(SessionCreateRequest(session_id="default"))
return await transcribe_audio(default_session.session_id, file=file, payload=payload)
Loading