fix(advice): a race in the shared repository, and the prompt naming the wrong reading
Two findings from the re-review of PR #14. A race that recreated the leak ------------------------------- `get_shared_repository()` did an unsynchronised check-then-set on a module global. Eight concurrent advice threads on a cold process each saw None and each built their own repository - so eight MongoClients and twenty-five threads. The fix that was meant to remove the leak reintroduced it, just bounded by a race instead of by the number of readings. Measured before: 8 repositories built, 25 threads. Measured after: 1 repository built, 4 threads. There is a lock, and the check is repeated inside it. The prompt was about the wrong reading -------------------------------------- The advice targeted the right reading, but the PROMPT said "give advice for the most recent reading" - and when the brewer corrects an OLDER reading, the most recent one in the history is a different reading. So the model was asked about the wrong number while the advice was filed against the right one. The reading is now named in the instruction (with its date and gravity), marked in the reading list as the one to advise on, and the system prompt says to advise about the marked reading because it may not be the most recent one. A corrected reading is also flagged as such, so the model knows it may differ from what it said before. 167 tests (up from 164).
This commit is contained in:
@@ -53,7 +53,7 @@ def advice_enabled() -> bool:
|
||||
return os.getenv("LLM_ADVICE", "1").strip().lower() not in {"0", "false", "no"}
|
||||
|
||||
|
||||
def _start_advice(run_id: str, reading_id: str) -> None:
|
||||
def _start_advice(run_id: str, reading_id: str, corrected: bool = False) -> None:
|
||||
"""Generate the LLM's advice for a reading, in the background.
|
||||
|
||||
The advice is a separate field on the reading, so the brewer's own
|
||||
@@ -131,7 +131,7 @@ def _start_advice(run_id: str, reading_id: str) -> None:
|
||||
|
||||
thread = threading.Thread(
|
||||
target=brew_advice.generate_advice_in_background,
|
||||
args=(run_info, latest, readings, write),
|
||||
args=(run_info, latest, readings, write, corrected),
|
||||
daemon=True,
|
||||
)
|
||||
thread.start()
|
||||
@@ -208,6 +208,14 @@ def index():
|
||||
"warning",
|
||||
)
|
||||
run_id = str(form.beer_run_id.data)
|
||||
# Whether the reading was already there: a re-submitted reading
|
||||
# is a CORRECTION, and the advice should say so.
|
||||
existing = repo().reading_docs_for_run(run_id)
|
||||
was_existing = any(
|
||||
str(doc.get("date")) == form.Date.data.isoformat()
|
||||
for doc in existing
|
||||
)
|
||||
|
||||
reading_id = repo().upsert_reading(
|
||||
beer_run_id=run_id,
|
||||
beer_type=(form.beer_run_type.data or "").strip(),
|
||||
@@ -221,7 +229,7 @@ def index():
|
||||
# The advice is generated in the background: recording a reading
|
||||
# must not block on the model. The page shows the reading straight
|
||||
# away and the advice is there on a later visit.
|
||||
_start_advice(run_id, reading_id)
|
||||
_start_advice(run_id, reading_id, corrected=was_existing)
|
||||
return redirect(url_for("brews.index"))
|
||||
|
||||
runs = repo().runs()
|
||||
|
||||
Reference in New Issue
Block a user