Fix re-summarize running one voice behind: commit the job row before poking the inline queue.
The reprocess endpoint poked transport_enqueue while its request session was still uncommitted; the inline worker woke instantly, read the job row with its own session, and summarized with the PREVIOUS click's tone (TRACE: click Funny -> worker read 'dry, witty'; click Neutral -> ran 'funny'). The UI disabled the voice chips for the whole wrong-voice run, which read as 'spinner does nothing until I leave and come back'. Rows are the real queue: commit before the poke. Regression test pins worker tone to the requested tone and Neutral clearing a prior voice; verified red without the fix, green with it.
This commit is contained in:
parent
39aaa04a00
commit
11bb13cb3d
3 changed files with 54 additions and 0 deletions
|
|
@ -441,6 +441,14 @@ async def reprocess_recording(
|
||||||
rec.processing_status = ProcessingStatus.processing
|
rec.processing_status = ProcessingStatus.processing
|
||||||
rec.processing_error = None
|
rec.processing_error = None
|
||||||
await session.flush()
|
await session.flush()
|
||||||
|
# Commit BEFORE poking the queue: the inline worker wakes the moment
|
||||||
|
# the item lands in the asyncio queue and reads the job row with its
|
||||||
|
# OWN session, while this request session only commits after the
|
||||||
|
# response completes. Without this the worker summarizes with the
|
||||||
|
# PREVIOUS click's tone (TRACE showed every click running one voice
|
||||||
|
# behind: click Funny -> worker read 'dry, witty'). The rows are the
|
||||||
|
# real queue; they must be durable before the poke.
|
||||||
|
await session.commit()
|
||||||
await proc.transport_enqueue(job_type, rec.id)
|
await proc.transport_enqueue(job_type, rec.id)
|
||||||
rows = await session.scalars(
|
rows = await session.scalars(
|
||||||
select(ProcessingJob)
|
select(ProcessingJob)
|
||||||
|
|
|
||||||
|
|
@ -94,10 +94,12 @@ class FakeLlm:
|
||||||
def __init__(self, fail=None):
|
def __init__(self, fail=None):
|
||||||
self.fail = fail
|
self.fail = fail
|
||||||
self.seen = []
|
self.seen = []
|
||||||
|
self.seen_tones = []
|
||||||
|
|
||||||
async def summarize(self, transcript, *, title=None, tone=None,
|
async def summarize(self, transcript, *, title=None, tone=None,
|
||||||
on_progress=None):
|
on_progress=None):
|
||||||
self.seen.append(transcript)
|
self.seen.append(transcript)
|
||||||
|
self.seen_tones.append(tone)
|
||||||
if self.fail is not None:
|
if self.fail is not None:
|
||||||
raise self.fail
|
raise self.fail
|
||||||
return SummaryResult(
|
return SummaryResult(
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,50 @@ async def test_reprocess_reruns_in_place_with_model(client, monkeypatch):
|
||||||
await inline_queue.stop()
|
await inline_queue.stop()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_reprocess_summarize_uses_requested_tone(client, monkeypatch):
|
||||||
|
"""Regression: the inline worker used to read the job row BEFORE the
|
||||||
|
reprocess request committed, so every re-summarize ran with the
|
||||||
|
PREVIOUS click's tone (click Funny -> 'dry, witty' summary; click
|
||||||
|
Neutral -> 'funny' summary). The endpoint now commits before poking
|
||||||
|
the queue; this pins the worker's tone to the tone in the request."""
|
||||||
|
llm = FakeLlm()
|
||||||
|
use_fakes(monkeypatch, FakeTranscriber(), llm)
|
||||||
|
use_inline(monkeypatch)
|
||||||
|
await inline_queue.start()
|
||||||
|
try:
|
||||||
|
token = await user_tokens(client, email="reproc-tone@shonar.dev")
|
||||||
|
h = {"Authorization": f"Bearer {token}"}
|
||||||
|
rec = await upload_recording(client, token, client_id="reproc-tone-1")
|
||||||
|
await _wait_terminal(rec["id"], want=2)
|
||||||
|
|
||||||
|
# Re-summarize asking for a NEW voice.
|
||||||
|
r = await client.post(
|
||||||
|
f"/api/v1/recordings/{rec['id']}/reprocess"
|
||||||
|
"?job=summarize&tone=sarcastic",
|
||||||
|
headers=h,
|
||||||
|
)
|
||||||
|
assert r.status_code == 200, r.text
|
||||||
|
await _wait_terminal(rec["id"], want=2)
|
||||||
|
# The LLM call for this job must have carried 'sarcastic', not
|
||||||
|
# whatever the first (auto) pass used.
|
||||||
|
assert llm.seen_tones[-1] == "sarcastic", llm.seen_tones
|
||||||
|
s = await client.get(f"/api/v1/recordings/{rec['id']}/summary", headers=h)
|
||||||
|
assert s.json()["tone"] == "sarcastic"
|
||||||
|
|
||||||
|
# And Neutral (tone=None) must CLEAR the voice, not inherit it.
|
||||||
|
r = await client.post(
|
||||||
|
f"/api/v1/recordings/{rec['id']}/reprocess?job=summarize",
|
||||||
|
headers=h,
|
||||||
|
)
|
||||||
|
assert r.status_code == 200, r.text
|
||||||
|
await _wait_terminal(rec["id"], want=2)
|
||||||
|
assert llm.seen_tones[-1] is None, llm.seen_tones
|
||||||
|
s = await client.get(f"/api/v1/recordings/{rec['id']}/summary", headers=h)
|
||||||
|
assert s.json()["tone"] is None
|
||||||
|
finally:
|
||||||
|
await inline_queue.stop()
|
||||||
|
|
||||||
|
|
||||||
async def test_reprocess_validation(client, monkeypatch):
|
async def test_reprocess_validation(client, monkeypatch):
|
||||||
token = await user_tokens(client, email="reproc2@shonar.dev")
|
token = await user_tokens(client, email="reproc2@shonar.dev")
|
||||||
h = {"Authorization": f"Bearer {token}"}
|
h = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue