Summarize tone: re-summarize in a chosen voice (backend)

POST /reprocess?job=summarize&tone=<voice> persists the voice on the job
row (queue carries only ids, so a sweep re-enqueue keeps it), the LLM
prompt appends 'write every field in a <tone> tone — the tone colors
the wording, never the facts', and the resulting summary records its
tone (SummaryOut.tone). Plain re-summarize clears a previous tone.
Migration tone0000000001 (summaries.tone, processing_jobs.tone).
78 passed, 1 skipped; ruff clean.
This commit is contained in:
avi 2026-09-15 14:24:36 -05:00
commit b63f49241d
11 changed files with 82 additions and 13 deletions

View file

@ -110,6 +110,7 @@ class SummaryOut(ORMModel):
version: int
provider: str
model: str | None
tone: str | None = None
content: dict
edited_by_user: bool
created_at: datetime

View file

@ -366,13 +366,15 @@ async def reprocess_recording(
session: SessionDep,
job: str = Query(default="summarize", pattern="^(transcribe|summarize)$"),
model: str | None = Query(default=None, max_length=64),
tone: str | None = Query(default=None, max_length=64),
):
"""Force one pipeline stage to run again (Summarize / Re-transcribe).
Unlike the enqueue-on-finalize path, this ignores prior success: a
summary the user wants regenerated (better model, new prompt) is a
deliberate request. Running jobs are left alone (409 instead of a
duplicate).
duplicate). ``tone`` (summarize only) restyles the summary's voice,
e.g. "sarcastic" — facts stay faithful to the transcript.
"""
from shonar.db.models import JobStatus, ProcessingStatus
from shonar.db.models import JobType as JT
@ -380,6 +382,7 @@ async def reprocess_recording(
rec = await _owned_recording(session, user, recording_id)
job_type = JT(job)
tone = (tone.strip() or None) if (job_type is JT.summarize and tone) else None
if job_type is JT.summarize and await proc.latest_transcript_text(
session, rec.id
) is None:
@ -398,7 +401,8 @@ async def reprocess_recording(
):
raise HTTPException(409, "That stage is already running.")
if existing is None:
session.add(ProcessingJob(recording_id=rec.id, job_type=job_type))
session.add(ProcessingJob(recording_id=rec.id, job_type=job_type,
tone=tone))
else:
existing.status = JobStatus.queued
existing.attempt = 0
@ -407,6 +411,9 @@ async def reprocess_recording(
existing.progress = None
existing.started_at = None
existing.finished_at = None
# Tone rides the job row (the queue carries only ids): a plain
# re-summarize must clear a previous tone, not inherit it.
existing.tone = tone
if job_type is JT.transcribe:
if model is not None:
# A re-transcribe may switch models; the saved per-recording