generated from john/python-template
188 lines
6.2 KiB
Markdown
188 lines
6.2 KiB
Markdown
## Step 3: services/transcription.py + providers/
|
|
|
|
### Objective
|
|
|
|
Implement the **AI transcription integration layer** so the app can:
|
|
|
|
1. Read the curated prompt from `PROMPT_DIR`
|
|
2. Send prompt + image to the configured provider (OpenRouter)
|
|
3. Return normalized transcription output (or structured failure)
|
|
|
|
This corresponds to MVP Step 3 from `docs/mvp.md`:
|
|
- `services/transcription.py`
|
|
- `providers/` adapter(s)
|
|
|
|
---
|
|
|
|
## Scope for Step 3
|
|
|
|
### In scope
|
|
- Provider abstraction and OpenRouter adapter
|
|
- Prompt file loading utility in service layer
|
|
- Image payload preparation
|
|
- One high-level transcription service function usable by Step 4 worker
|
|
- Unit tests (mocked provider SDK, no external calls)
|
|
|
|
### Out of scope
|
|
- Job polling/background loop (Step 4)
|
|
- DB status transition orchestration in worker loop (Step 4)
|
|
- UI invocation/wiring (Step 5)
|
|
|
|
---
|
|
|
|
## Planned Deliverables
|
|
|
|
### Source files
|
|
- `src/transcription/providers/base.py`
|
|
- `src/transcription/providers/openrouter.py`
|
|
- `src/transcription/providers/__init__.py` (exports + factory)
|
|
- `src/transcription/services/transcription.py`
|
|
- `src/transcription/services/__init__.py` (optional export)
|
|
|
|
### Tests
|
|
- `tests/test_providers_openrouter.py`
|
|
- `tests/test_transcription_service.py`
|
|
|
|
---
|
|
|
|
## Design Decisions (before coding)
|
|
|
|
1. **Provider interface first**
|
|
- Define a stable contract independent of SDK specifics.
|
|
- Prevent Step 4 from depending on raw SDK response shapes.
|
|
|
|
2. **Service returns normalized result object**
|
|
- Include: `text`, `provider`, `model`, `raw_error`/exception metadata.
|
|
- Worker can map this cleanly to `Transcript` and `JobStatus`.
|
|
|
|
3. **Prompt loaded from file at call time**
|
|
- Uses `get_settings().prompt_dir / "transcribe_document.md"`.
|
|
- Keeps prompt edits hot-swappable without code changes.
|
|
|
|
4. **Clear exception boundary**
|
|
- SDK/network/model failures become predictable domain exceptions:
|
|
- `ProviderError`
|
|
- `PromptLoadError`
|
|
- `TranscriptionError` (optional top-level wrapper)
|
|
|
|
5. **Model resolution policy**
|
|
- Use `settings.provider_model` if set
|
|
- Otherwise use adapter default constant (e.g., vision-capable model slug)
|
|
|
|
---
|
|
|
|
## Task-by-Task Execution Checklist
|
|
|
|
## Phase A — Provider contract
|
|
|
|
- [ ] Create `src/transcription/providers/base.py`
|
|
- [ ] Define protocol/ABC for transcription providers:
|
|
- [ ] method signature accepts prompt text + image bytes (or data URL) + mime type
|
|
- [ ] returns normalized text result (and optional metadata)
|
|
- [ ] Define shared provider exceptions:
|
|
- [ ] `ProviderError`
|
|
- [ ] optional subclasses (`ProviderAuthError`, `ProviderResponseError`)
|
|
|
|
---
|
|
|
|
## Phase B — OpenRouter adapter
|
|
|
|
- [ ] Create `src/transcription/providers/openrouter.py`
|
|
- [ ] Implement `OpenRouterTranscriptionProvider` with:
|
|
- [ ] config-driven API key usage
|
|
- [ ] optional referer/title attribution headers
|
|
- [ ] model resolution fallback when `provider_model` is unset
|
|
- [ ] Implement request building:
|
|
- [ ] prompt included as instruction content
|
|
- [ ] image included in supported format for vision call
|
|
- [ ] Implement response parsing:
|
|
- [ ] extract final transcript text from SDK response
|
|
- [ ] validate non-empty text
|
|
- [ ] Wrap SDK failures into `ProviderError` with clean message
|
|
|
|
---
|
|
|
|
## Phase C — Provider factory
|
|
|
|
- [ ] Update `src/transcription/providers/__init__.py`
|
|
- [ ] Add `get_transcription_provider()` factory:
|
|
- [ ] reads `settings.provider`
|
|
- [ ] returns OpenRouter adapter for `openrouter`
|
|
- [ ] raises explicit error for unsupported provider values
|
|
|
|
---
|
|
|
|
## Phase D — Transcription service (Step 3 core)
|
|
|
|
- [ ] Create `src/transcription/services/transcription.py`
|
|
- [ ] Add prompt loader function:
|
|
- [ ] default file: `transcribe_document.md`
|
|
- [ ] raises `PromptLoadError` on missing/empty file
|
|
- [ ] Add image loader/validator:
|
|
- [ ] path existence check
|
|
- [ ] allowed mime detection (`.jpg/.jpeg/.png/.tiff/.pdf` policy aligned to MVP)
|
|
- [ ] Add high-level function (name example):
|
|
- [ ] `transcribe_document_image(image_path, prompt_name="transcribe_document.md")`
|
|
- [ ] loads prompt + image
|
|
- [ ] calls provider from factory
|
|
- [ ] returns normalized transcription result object
|
|
- [ ] Add structured logging at key boundaries:
|
|
- [ ] prompt loaded
|
|
- [ ] provider invoked
|
|
- [ ] success/failure outcome (no sensitive data in logs)
|
|
|
|
---
|
|
|
|
## Phase E — Tests (mocked, deterministic)
|
|
|
|
## `tests/test_providers_openrouter.py`
|
|
- [ ] test adapter initializes from settings
|
|
- [ ] test model fallback when `provider_model is None`
|
|
- [ ] test referer/title options are included when set
|
|
- [ ] test successful SDK response parses transcript text
|
|
- [ ] test SDK exception maps to `ProviderError`
|
|
- [ ] test empty/invalid response maps to `ProviderError`
|
|
|
|
## `tests/test_transcription_service.py`
|
|
- [ ] test prompt loader reads canonical prompt file
|
|
- [ ] test missing prompt raises `PromptLoadError`
|
|
- [ ] test transcription function loads file and calls provider once
|
|
- [ ] test image path missing raises clear error
|
|
- [ ] test provider error is propagated/wrapped predictably
|
|
- [ ] test returned result includes transcript text and metadata
|
|
|
|
> Keep these unit tests mocked (no real OpenRouter calls in default suite).
|
|
|
|
---
|
|
|
|
## Phase F — Verification commands
|
|
|
|
- [ ] `uv run pytest tests/test_providers_openrouter.py -q`
|
|
- [ ] `uv run pytest tests/test_transcription_service.py -q`
|
|
- [ ] `uv run pytest -q`
|
|
|
|
---
|
|
|
|
## Implementation Notes / Guardrails
|
|
|
|
- Avoid coupling Step 3 service to DB models directly (that belongs in Step 4 orchestration).
|
|
- Do not silently swallow provider errors.
|
|
- Keep prompt filename stable (`transcribe_document.md`) unless explicitly parameterized.
|
|
- Keep request/response normalization inside provider adapter, not worker/UI layers.
|
|
|
|
---
|
|
|
|
## Definition of Done (Step 3)
|
|
|
|
Step 3 is done when:
|
|
|
|
1. Provider abstraction exists and OpenRouter adapter is implemented.
|
|
2. Service can transcribe a local image using prompt file content.
|
|
3. Failures are returned as structured exceptions, not raw SDK traceback noise.
|
|
4. Unit tests for provider and service pass.
|
|
5. Full suite remains green under `uv run pytest -q`.
|
|
6. Step 4 can call a single service function to process queued jobs.
|
|
|
|
---
|
|
|
|
If you want, I can now convert this into a **PR-ready markdown checklist** (same format as Step 2) and then implement it once you confirm. |