feat(advice): LLM brewing advice on every observation #14
@@ -716,6 +716,12 @@ table.data .comment { color: var(--text-muted); font-size: .84rem; }
|
|||||||
color: var(--amber-text);
|
color: var(--amber-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The advice on the active-beers card and the run detail page - the
|
||||||
|
full text, not the clamped table cell. */
|
||||||
|
.advice--reading {
|
||||||
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Advice that could not be generated - it says why. */
|
/* Advice that could not be generated - it says why. */
|
||||||
.advice--failed {
|
.advice--failed {
|
||||||
border-left-color: var(--danger);
|
border-left-color: var(--danger);
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ def _start_advice(run_id: str, reading_id: str, corrected: bool = False) -> None
|
|||||||
comment is never touched.
|
comment is never touched.
|
||||||
|
|
||||||
The background thread cannot use the request-scoped repository - that
|
The background thread cannot use the request-scoped repository - that
|
||||||
client is closed when the request ends - so it builds its own.
|
client is closed when the request ends - so it uses the shared
|
||||||
|
process-wide one.
|
||||||
"""
|
"""
|
||||||
if not reading_id:
|
if not reading_id:
|
||||||
return
|
return
|
||||||
@@ -130,7 +131,18 @@ def _start_advice(run_id: str, reading_id: str, corrected: bool = False) -> None
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
shared = get_shared_repository()
|
shared = get_shared_repository()
|
||||||
shared.set_reading_advice(read_id, text, ok, expected_sg=advice_sg)
|
written = shared.set_reading_advice(read_id, text, ok,
|
||||||
|
expected_sg=advice_sg)
|
||||||
|
if not written:
|
||||||
|
# The reading was corrected while the advice was being
|
||||||
|
# generated, so the advice no longer matches it. That is
|
||||||
|
# the guard working - the fresh advice for the corrected
|
||||||
|
# reading will land in its own turn.
|
||||||
|
log.info(
|
||||||
|
"the advice for reading %s was not stored: the reading "
|
||||||
|
"no longer holds the gravity it was written for",
|
||||||
|
read_id,
|
||||||
|
)
|
||||||
except Exception: # noqa: BLE001 - never let the thread die noisily
|
except Exception: # noqa: BLE001 - never let the thread die noisily
|
||||||
log.exception("could not store the generated advice")
|
log.exception("could not store the generated advice")
|
||||||
|
|
||||||
|
|||||||
@@ -498,6 +498,20 @@ def test_the_advice_is_about_the_reading_that_was_saved():
|
|||||||
"the advice was generated about the wrong reading"
|
"the advice was generated about the wrong reading"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# And the PROMPT must ask about that reading - not just pass it
|
||||||
|
# through. Asserting only which dict was passed would not have
|
||||||
|
# caught the prompt still saying "give advice for the most recent
|
||||||
|
# reading", which is the bug that made the model advise about the
|
||||||
|
# wrong number.
|
||||||
|
prompt = brew_advice.build_prompt(
|
||||||
|
{"canonical_name": "Ale"},
|
||||||
|
captured["reading"],
|
||||||
|
captured["history"],
|
||||||
|
)
|
||||||
|
assert "THIS IS THE READING TO ADVISE ON" in prompt
|
||||||
|
assert "2026-01-01" in prompt
|
||||||
|
assert "most recent reading" not in prompt
|
||||||
|
|
||||||
|
|
||||||
def test_the_shared_repository_is_built_only_once():
|
def test_the_shared_repository_is_built_only_once():
|
||||||
"""Concurrent advice threads must not each build their own client.
|
"""Concurrent advice threads must not each build their own client.
|
||||||
|
|||||||
Reference in New Issue
Block a user