feat(memory): add session-based chat history and user metadata retrieval
- Add ChatSessionCache to manage chat history per session - Add SEARCH_USER_METADATA cypher query for retrieving user entity metadata - Add "str" mode support to StructResponse for raw text extraction - Add content_str field to MemorySearchResult for pre-formatted content - Fix sandbox URL by removing hardcoded port - Add description field to entity search results - Remove history from UserInput schema, use session_id instead
This commit is contained in:
77
api/app/utils/tmp_session.py
Normal file
77
api/app/utils/tmp_session.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
import redis.asyncio as redis
|
||||
|
||||
from app.aioRedis import get_redis_connection
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_TTL = 3600
|
||||
|
||||
|
||||
class ChatSessionCache:
|
||||
"""Cache user-AI conversation history in Redis with TTL-based expiry.
|
||||
|
||||
Usage::
|
||||
|
||||
cache = ChatSessionCache(session_id="user_123")
|
||||
await cache.append("user", "Hello")
|
||||
await cache.append("assistant", "Hi there!")
|
||||
history = await cache.get_history()
|
||||
"""
|
||||
|
||||
def __init__(self, session_id: str, ttl: int = DEFAULT_TTL):
|
||||
self.session_id = session_id
|
||||
self.ttl = ttl
|
||||
self._key = f"chat:session:{session_id}"
|
||||
|
||||
@staticmethod
|
||||
async def _client() -> redis.StrictRedis:
|
||||
return await get_redis_connection()
|
||||
|
||||
async def append(self, role: str, content: str) -> None:
|
||||
r = await self._client()
|
||||
entry = json.dumps({"role": role, "content": content}, ensure_ascii=False)
|
||||
await r.rpush(self._key, entry)
|
||||
await r.expire(self._key, self.ttl)
|
||||
|
||||
async def append_many(self, messages: list[dict[str, str]]) -> None:
|
||||
"""Batch append messages. Each dict should have ``role`` and ``content`` keys."""
|
||||
if not messages:
|
||||
return
|
||||
r = await self._client()
|
||||
entries = [
|
||||
json.dumps(m, ensure_ascii=False)
|
||||
for m in messages
|
||||
if "role" in m and "content" in m
|
||||
]
|
||||
if entries:
|
||||
await r.rpush(self._key, *entries)
|
||||
await r.expire(self._key, self.ttl)
|
||||
|
||||
async def get_history(self) -> list[dict[str, str]]:
|
||||
r = await self._client()
|
||||
raw = await r.lrange(self._key, 0, -1)
|
||||
return [json.loads(item) for item in raw]
|
||||
|
||||
async def get_history_text(self, user_label: str = "User", ai_label: str = "Assistant") -> str:
|
||||
"""Return conversation as a formatted text block."""
|
||||
history = await self.get_history()
|
||||
lines = []
|
||||
for msg in history:
|
||||
role = msg.get("role", "")
|
||||
content = msg.get("content", "")
|
||||
label = user_label if role == "user" else ai_label if role == "assistant" else role
|
||||
lines.append(f"{label}: {content}")
|
||||
return "\n".join(lines)
|
||||
|
||||
async def reset(self) -> None:
|
||||
"""Delete the session from Redis."""
|
||||
r = await self._client()
|
||||
await r.delete(self._key)
|
||||
|
||||
async def touch(self) -> None:
|
||||
"""Refresh the TTL without modifying data."""
|
||||
r = await self._client()
|
||||
await r.expire(self._key, self.ttl)
|
||||
Reference in New Issue
Block a user