generated from john/python-template
Step 2 implemented
This commit is contained in:
+188
@@ -0,0 +1,188 @@
|
||||
## 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.
|
||||
Reference in New Issue
Block a user