Refactor chat memory context loading and implement previous session history retrieval

This commit is contained in:
2026-06-15 22:54:46 +02:00
parent a5ad7ab261
commit ebfa50bf03

View File

@@ -556,34 +556,16 @@ def _dedupe_hits(candidates: Sequence[Dict[str, Any]], top_k: int, *, minimum_sc
return hits
def build_chat_memory_context(
db: Session,
prompt: str,
def _context_payload_from_hits(
hits: Sequence[Dict[str, Any]],
*,
exclude_session_id: Optional[str] = None,
top_k: int = DEFAULT_MEMORY_TOP_K,
context_character_budget: int = DEFAULT_MEMORY_CONTEXT_CHARS,
context_character_budget: int,
intro: str,
) -> Dict[str, Any]:
tokens = _query_tokens(prompt)
if not tokens:
return {"context_block": "", "sources": [], "hits": []}
candidate_limit = max(80, top_k * 24)
candidates = _load_fts_candidates(db, tokens, exclude_session_id=exclude_session_id, limit=candidate_limit)
if len(candidates) < top_k:
candidates.extend(_load_table_candidates(db, tokens, exclude_session_id=exclude_session_id, limit=max(300, candidate_limit)))
if len(candidates) < top_k:
candidates.extend(_load_live_candidates(db, tokens, exclude_session_id=exclude_session_id, limit=1500))
hits = _dedupe_hits(candidates, max(1, top_k))
if not hits:
return {"context_block": "", "sources": [], "hits": []}
header = (
"<chat_memory_context>\n"
"Relevant excerpts from previous chats. Use these only when they help answer the current request; "
"they may be outdated and should not override explicit current context or fresh retrieved evidence."
)
header = f"<chat_memory_context>\n{intro}"
blocks = [header]
sources: List[Dict[str, Any]] = []
remaining = max(600, int(context_character_budget)) - len(header) - len("</chat_memory_context>")
@@ -617,4 +599,128 @@ def build_chat_memory_context(
if remaining <= 0:
break
blocks.append("</chat_memory_context>")
return {"context_block": "\n\n".join(blocks), "sources": sources, "hits": hits[:len(sources)]}
return {"context_block": "\n\n".join(blocks), "sources": sources, "hits": list(hits[:len(sources)])}
def _load_previous_session_hits(
db: Session,
*,
exclude_session_id: Optional[str],
limit: int,
) -> List[Dict[str, Any]]:
current_created_at = None
if exclude_session_id:
try:
current_created_at = db.execute(
text("SELECT created_at FROM chat_sessions WHERE session_id = :session_id"),
{"session_id": exclude_session_id},
).scalar()
except Exception:
current_created_at = None
try:
previous_session_id = db.execute(text(
"""
SELECT t.session_id
FROM chat_memory_turns t
JOIN chat_sessions s ON s.session_id = t.session_id
WHERE (:exclude_session_id IS NULL OR t.session_id != :exclude_session_id)
AND (:current_created_at IS NULL OR s.created_at < :current_created_at)
GROUP BY t.session_id, s.created_at
ORDER BY s.created_at DESC
LIMIT 1
"""
), {
"exclude_session_id": exclude_session_id,
"current_created_at": current_created_at,
}).scalar()
except Exception:
previous_session_id = None
if not previous_session_id:
return []
try:
rows = db.execute(text(
"""
SELECT
turn_key, session_id, session_name, user_message_id, assistant_message_id,
user_message_row_id, assistant_message_row_id, user_content, assistant_content,
attachment_summary, search_text, created_at
FROM chat_memory_turns
WHERE session_id = :session_id
ORDER BY assistant_message_row_id ASC
LIMIT :limit
"""
), {
"session_id": previous_session_id,
"limit": max(1, limit),
}).mappings().all()
except Exception:
return []
hits: List[Dict[str, Any]] = []
for row in rows:
item = _row_mapping(row)
item["_score"] = 1.0
hits.append(item)
return hits
def build_chat_memory_context(
db: Session,
prompt: str,
*,
exclude_session_id: Optional[str] = None,
top_k: int = DEFAULT_MEMORY_TOP_K,
context_character_budget: int = DEFAULT_MEMORY_CONTEXT_CHARS,
) -> Dict[str, Any]:
if _LAST_CONVERSATION_RE.search(str(prompt or "")):
hits = _load_previous_session_hits(db, exclude_session_id=exclude_session_id, limit=max(1, top_k))
return _context_payload_from_hits(
hits,
context_character_budget=context_character_budget,
intro=(
"Most recent previous chat. Use this to answer questions about the last or previous conversation; "
"do not treat it as fresh external evidence."
),
)
core_tokens = _query_tokens(prompt)
if not core_tokens:
return {"context_block": "", "sources": [], "hits": []}
tokens = _expanded_query_tokens(core_tokens)
minimum_score = _minimum_memory_score(len(core_tokens))
candidate_limit = max(80, top_k * 24)
candidates = _load_fts_candidates(
db,
tokens,
exclude_session_id=exclude_session_id,
limit=candidate_limit,
query_token_count=len(core_tokens),
)
if len(candidates) < top_k:
candidates.extend(_load_table_candidates(
db,
tokens,
exclude_session_id=exclude_session_id,
limit=max(300, candidate_limit),
query_token_count=len(core_tokens),
))
if len(candidates) < top_k:
candidates.extend(_load_live_candidates(
db,
tokens,
exclude_session_id=exclude_session_id,
limit=1500,
query_token_count=len(core_tokens),
))
hits = _dedupe_hits(candidates, max(1, top_k), minimum_score=minimum_score)
return _context_payload_from_hits(
hits,
context_character_budget=context_character_budget,
intro=(
"Relevant excerpts from previous chats. Use these only when they help answer the current request; "
"they may be outdated and should not override explicit current context or fresh retrieved evidence."
),
)