fix(advice): stale advice must not overwrite a correction
The review found a race between the background advice thread and the
brewer correcting a reading.
`set_reading_advice` wrote unconditionally by id. The advice is
generated in the background, so if the brewer corrects a reading while
the advice for the OLD gravity is still being generated, the slow thread
lands afterwards and overwrites the fresh advice with advice about a
reading that no longer exists. The reading ends up with the final
gravity from the correction and the advice from before it.
Reproduced against the live database:
wrote advice for 1050
wrote advice for 1020
late stale write matched: 0
final sg: 1020 | final advice: 'ADVICE FOR THE CORRECTION'
The write is now conditional on the reading still holding the gravity
the advice was generated for, so a late thread matches nothing.
Also
----
`finish_reason` is now checked. A reply that stopped at the token
ceiling is incomplete - it may be advice that stops mid-sentence - and
it was being stored as if it were the whole answer. It is now marked
as cut off.
173 tests (up from 171).
This commit is contained in:
@@ -223,20 +223,36 @@ class BeerRepository:
|
||||
return str(existing.get("_id")) if existing else ""
|
||||
|
||||
def set_reading_advice(self, reading_id: str, advice: str,
|
||||
ok: bool = True) -> int:
|
||||
ok: bool = True,
|
||||
expected_sg: int | None = None) -> int:
|
||||
"""Attach the LLM's advice to a reading.
|
||||
|
||||
The advice lives in its own field, so the brewer's own comment is
|
||||
never overwritten. ``ok`` says whether the advice was actually
|
||||
generated or is a note explaining that it could not be.
|
||||
|
||||
``expected_sg`` is the gravity the advice was generated FOR. The
|
||||
write only happens if the reading still holds that gravity.
|
||||
|
||||
This matters because the advice is generated in the background: if
|
||||
the brewer corrects a reading while the advice for the old value
|
||||
is still being generated, the slow thread would land afterwards
|
||||
and overwrite the fresh advice with advice about a reading that
|
||||
no longer exists. Without the guard the run ends up with the
|
||||
final gravity from the correction and the advice from before it.
|
||||
"""
|
||||
doc_id = _as_doc_id(reading_id)
|
||||
if not doc_id:
|
||||
return 0
|
||||
|
||||
query: dict = {"_id": {"$in": doc_id}}
|
||||
if expected_sg is not None:
|
||||
query["sg"] = str(expected_sg)
|
||||
|
||||
# `$in` matches either form. A plain list would be an exact
|
||||
# ARRAY match in Mongo, which is not what is wanted here.
|
||||
result = self.readings.update_one(
|
||||
{"_id": {"$in": doc_id}},
|
||||
query,
|
||||
{"$set": {
|
||||
"advice": str(advice),
|
||||
"advice_ok": bool(ok),
|
||||
|
||||
Reference in New Issue
Block a user