mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-23 05:55:16 +08:00
core/memory: order sessions by recency and touch on activity
List/get-latest by updated_at; bump updated_at when messages are appended so workspace resume picks the lately-used session.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import UTC, datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Self
|
from typing import TYPE_CHECKING, Self
|
||||||
|
|
||||||
@@ -124,10 +125,10 @@ class MemoryStore:
|
|||||||
uid: int | None = None,
|
uid: int | None = None,
|
||||||
workspace: str | Path | None = None,
|
workspace: str | Path | None = None,
|
||||||
) -> Sequence[Session]:
|
) -> Sequence[Session]:
|
||||||
"""List sessions; optionally filter by user and/or bound workspace path."""
|
"""List sessions (most recently updated first); filter by user/workspace."""
|
||||||
ws = normalize_workspace(workspace)
|
ws = normalize_workspace(workspace)
|
||||||
async with self._session_factory() as session:
|
async with self._session_factory() as session:
|
||||||
stmt = select(Session).order_by(Session.sid)
|
stmt = select(Session).order_by(Session.updated_at.desc(), Session.sid.desc())
|
||||||
if uid is not None:
|
if uid is not None:
|
||||||
stmt = stmt.where(Session.uid == uid)
|
stmt = stmt.where(Session.uid == uid)
|
||||||
if ws is not None:
|
if ws is not None:
|
||||||
@@ -135,6 +136,27 @@ class MemoryStore:
|
|||||||
result = await session.execute(stmt)
|
result = await session.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
|
async def get_latest_session(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
uid: int | None = None,
|
||||||
|
workspace: str | Path | None = None,
|
||||||
|
) -> Session | None:
|
||||||
|
"""Return the most recently updated session for the filters, if any."""
|
||||||
|
sessions = await self.list_sessions(uid=uid, workspace=workspace)
|
||||||
|
return sessions[0] if sessions else None
|
||||||
|
|
||||||
|
async def touch_session(self, sid: int) -> Session | None:
|
||||||
|
"""Bump ``updated_at`` so the session ranks as lately used."""
|
||||||
|
async with self._session_factory() as session:
|
||||||
|
row = await session.get(Session, sid)
|
||||||
|
if row is None:
|
||||||
|
return None
|
||||||
|
row.updated_at = datetime.now(UTC)
|
||||||
|
await session.commit()
|
||||||
|
await session.refresh(row)
|
||||||
|
return row
|
||||||
|
|
||||||
async def update_session_workspace(
|
async def update_session_workspace(
|
||||||
self,
|
self,
|
||||||
sid: int,
|
sid: int,
|
||||||
@@ -148,6 +170,7 @@ class MemoryStore:
|
|||||||
msg = f"session not found: {sid}"
|
msg = f"session not found: {sid}"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
row.workspace = ws
|
row.workspace = ws
|
||||||
|
row.updated_at = datetime.now(UTC)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
await session.refresh(row)
|
await session.refresh(row)
|
||||||
return row
|
return row
|
||||||
@@ -167,6 +190,9 @@ class MemoryStore:
|
|||||||
seq = 0 if last_seq is None else last_seq + 1
|
seq = 0 if last_seq is None else last_seq + 1
|
||||||
row = Message(sid=sid, seq=seq, data=data)
|
row = Message(sid=sid, seq=seq, data=data)
|
||||||
session.add(row)
|
session.add(row)
|
||||||
|
chat = await session.get(Session, sid)
|
||||||
|
if chat is not None:
|
||||||
|
chat.updated_at = datetime.now(UTC)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
await session.refresh(row)
|
await session.refresh(row)
|
||||||
return row
|
return row
|
||||||
|
|||||||
@@ -61,6 +61,26 @@ async def test_list_sessions(store: MemoryStore) -> None:
|
|||||||
assert s2.sid in ids
|
assert s2.sid in ids
|
||||||
|
|
||||||
|
|
||||||
|
async def test_list_sessions_newest_first(store: MemoryStore) -> None:
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from plyngent.lmproto.openai_compatible.model import UserChatMessage
|
||||||
|
|
||||||
|
s1 = await store.create_session(name="old")
|
||||||
|
await asyncio.sleep(0.02)
|
||||||
|
s2 = await store.create_session(name="new")
|
||||||
|
latest = await store.get_latest_session()
|
||||||
|
assert latest is not None
|
||||||
|
assert latest.sid == s2.sid
|
||||||
|
# Activity on s1 makes it the latest
|
||||||
|
_ = await store.append_message(s1.sid, UserChatMessage(content="ping"))
|
||||||
|
latest2 = await store.get_latest_session()
|
||||||
|
assert latest2 is not None
|
||||||
|
assert latest2.sid == s1.sid
|
||||||
|
ordered = await store.list_sessions()
|
||||||
|
assert ordered[0].sid == s1.sid
|
||||||
|
|
||||||
|
|
||||||
async def test_session_workspace_binding(store: MemoryStore, tmp_path: object) -> None:
|
async def test_session_workspace_binding(store: MemoryStore, tmp_path: object) -> None:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user