mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 10:15:27 -04:00
fix(tasks): read Memory.text in classify_events personal context (#3640)
The classify_events task pulled user memories to give the LLM personal context, but read `m.content`, which the Memory ORM does not have (the column is `text`). That raised AttributeError on the first row; the surrounding except swallowed it and logged at debug, so the personal-context block was silently always empty and events were classified without it. Extract the rendering into `_memory_context_lines` (reads `text`, robust via getattr, keeps the 200-char and 40-line caps) and raise the swallowed-exception log to warning so a future schema mismatch is visible. Adds tests/test_classify_events_memory_text.py for the field, truncation, blank skipping, missing-attr robustness, and the line cap.
This commit is contained in:
committed by
GitHub
parent
8bf8212846
commit
f5b91f1e9e
+21
-8
@@ -579,6 +579,24 @@ def _classify_event_heuristic(summary: str) -> tuple:
|
|||||||
return etype, None
|
return etype, None
|
||||||
|
|
||||||
|
|
||||||
|
def _memory_context_lines(mems, limit: int = 40) -> list:
|
||||||
|
"""Render Memory rows into short personal-context bullets for event classify.
|
||||||
|
|
||||||
|
Reads the Memory ORM `text` column. The previous inline code read a
|
||||||
|
non-existent `content` attribute, so it raised AttributeError on the first
|
||||||
|
row, the surrounding except swallowed it, and the classifier ran with no
|
||||||
|
personal context at all. getattr keeps it robust to future schema drift.
|
||||||
|
"""
|
||||||
|
lines: list = []
|
||||||
|
for m in mems:
|
||||||
|
c = (getattr(m, "text", "") or "").strip()
|
||||||
|
if c:
|
||||||
|
lines.append(f"- {c[:200]}")
|
||||||
|
if len(lines) >= limit:
|
||||||
|
break
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
async def action_classify_events(owner: str, **kwargs) -> Tuple[str, bool]:
|
async def action_classify_events(owner: str, **kwargs) -> Tuple[str, bool]:
|
||||||
"""Hybrid classification of upcoming calendar events: fast heuristic for
|
"""Hybrid classification of upcoming calendar events: fast heuristic for
|
||||||
obvious cases, LLM fallback for ambiguous ones. Assigns event_type +
|
obvious cases, LLM fallback for ambiguous ones. Assigns event_type +
|
||||||
@@ -614,16 +632,11 @@ async def action_classify_events(owner: str, **kwargs) -> Tuple[str, bool]:
|
|||||||
try:
|
try:
|
||||||
from core.database import Memory as _Mem
|
from core.database import Memory as _Mem
|
||||||
_mems = db.query(_Mem).filter(_Mem.owner == owner).limit(60).all() if owner else []
|
_mems = db.query(_Mem).filter(_Mem.owner == owner).limit(60).all() if owner else []
|
||||||
if _mems:
|
_lines = _memory_context_lines(_mems)
|
||||||
_lines = []
|
|
||||||
for m in _mems:
|
|
||||||
c = (m.content or "").strip()
|
|
||||||
if c:
|
|
||||||
_lines.append(f"- {c[:200]}")
|
|
||||||
if _lines:
|
if _lines:
|
||||||
_memory_context = "USER CONTEXT (relationships, work, life):\n" + "\n".join(_lines[:40]) + "\n\n"
|
_memory_context = "USER CONTEXT (relationships, work, life):\n" + "\n".join(_lines) + "\n\n"
|
||||||
except Exception as _me:
|
except Exception as _me:
|
||||||
logger.debug(f"Could not load memory for classify: {_me}")
|
logger.warning(f"Could not load memory for classify: {_me}")
|
||||||
|
|
||||||
classified_h = 0
|
classified_h = 0
|
||||||
classified_llm = 0
|
classified_llm = 0
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""classify_events must read the Memory `text` column, not a non-existent
|
||||||
|
`content` attribute.
|
||||||
|
|
||||||
|
The previous inline loop did `m.content`, which raised AttributeError on the
|
||||||
|
first Memory row; the surrounding except swallowed it, so the personal-context
|
||||||
|
block the LLM relies on was always empty. The logic now lives in
|
||||||
|
`_memory_context_lines`, which reads `text`.
|
||||||
|
"""
|
||||||
|
from src.builtin_actions import _memory_context_lines
|
||||||
|
|
||||||
|
|
||||||
|
class _Mem:
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
|
||||||
|
def test_uses_text_and_truncates_and_skips_blank():
|
||||||
|
lines = _memory_context_lines([_Mem("Alice is my spouse"), _Mem(" "), _Mem("y" * 250)])
|
||||||
|
assert lines[0] == "- Alice is my spouse"
|
||||||
|
assert len(lines) == 2 # the blank row is skipped
|
||||||
|
assert lines[1] == "- " + "y" * 200 # truncated to 200 chars
|
||||||
|
|
||||||
|
|
||||||
|
def test_skips_rows_without_text_attribute():
|
||||||
|
class _Bad: # mimics a schema where the attribute is absent
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert _memory_context_lines([_Bad(), _Mem("ok")]) == ["- ok"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_respects_limit():
|
||||||
|
mems = [_Mem(f"memory {i}") for i in range(50)]
|
||||||
|
assert len(_memory_context_lines(mems, limit=40)) == 40
|
||||||
Reference in New Issue
Block a user