c889a57b6b
Test Suites / Build CI Environment (push) Has been cancelled
Test Suites / Basic Tests (push) Has been cancelled
Test Suites / End-to-End Tests (push) Has been cancelled
Test Suites / CLI Tests (push) Has been cancelled
Test Suites / Slow End-to-End Tests (push) Has been cancelled
Test Suites / Graph Database Tests (push) Has been cancelled
Test Suites / Vector DB Tests (push) Has been cancelled
Test Suites / Temporal Graph Test (push) Has been cancelled
Test Suites / Search Test on Different DBs (push) Has been cancelled
Test Suites / Example Tests (push) Has been cancelled
Test Suites / Notebook Tests (push) Has been cancelled
Test Suites / OS and Python Tests Ubuntu (push) Has been cancelled
Test Suites / OS and Python Tests Extended (push) Has been cancelled
Test Suites / LLM Test Suite (push) Has been cancelled
Test Suites / S3 File Storage Test (push) Has been cancelled
Test Suites / Run Integration Tests (push) Has been cancelled
Test Suites / MCP Tests (push) Has been cancelled
Test Suites / Docker Compose Test (push) Has been cancelled
Test Suites / Docker CI test (push) Has been cancelled
Test Suites / Relational DB Migration Tests (push) Has been cancelled
Test Suites / Distributed Cognee Test (push) Has been cancelled
Test Suites / DB Examples Tests (push) Has been cancelled
Test Suites / Test Completion Status (push) Has been cancelled
Test Suites / Claude Code Review (push) Has been cancelled
Test Suites / basic checks (push) Has been cancelled
build | Build and Push Cognee MCP Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
build | Build and Push Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.11) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.12) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (kuzu, kuzu) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (neo4j, neo4j) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Examples (push) Has been cancelled
Weighted Edges Tests / Code Quality for Weighted Edges (push) Has been cancelled
142 lines
5.7 KiB
Python
142 lines
5.7 KiB
Python
"""Collect memory-operation events from the session layer for the Memory tab.
|
|
|
|
The "operation bench": searches run against the backend (with a session)
|
|
record which graph elements produced each answer (``used_graph_element_ids``),
|
|
and feedback entries record how those answers were rated. This module turns
|
|
that history into timeline events for the visualization, so backend activity
|
|
appears on the Memory tab automatically — no hand-built ``search_events``.
|
|
|
|
Two event kinds are emitted per the renderer's contract:
|
|
|
|
- ``search`` — one per QA entry: the query, answer, and the graph elements
|
|
retrieved to produce it (spotlight overlay).
|
|
- ``improve`` — one per *rated* QA entry: the same elements plus the rating,
|
|
representing the reinforcement that ``improve()`` applies to them via
|
|
``apply_feedback_weights`` (reinforcement overlay).
|
|
"""
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from cognee.shared.logging_utils import get_logger
|
|
|
|
logger = get_logger("visualization.session_events")
|
|
|
|
# Bound the number of sessions scanned when none are specified explicitly.
|
|
MAX_SESSIONS_SCANNED = 10
|
|
|
|
|
|
def map_session_entries_to_events(session_id: str, entries: List[Any]) -> List[Dict[str, Any]]:
|
|
"""Map SessionQAEntry records to Memory-tab timeline events (pure).
|
|
|
|
Emits a ``search`` event per entry and, when the entry carries a feedback
|
|
score, an ``improve`` event immediately after it (the renderer's sort is
|
|
stable, so equal timestamps preserve this order).
|
|
"""
|
|
events: List[Dict[str, Any]] = []
|
|
for entry in entries:
|
|
used = getattr(entry, "used_graph_element_ids", None) or {}
|
|
node_ids = list(used.get("node_ids") or [])
|
|
edge_ids = list(used.get("edge_ids") or [])
|
|
question = getattr(entry, "question", "") or ""
|
|
if not question.strip() and not node_ids:
|
|
# Placeholder/system entries with neither a query nor provenance
|
|
# add nothing to the timeline story.
|
|
continue
|
|
|
|
base = {
|
|
"session_id": session_id,
|
|
"qa_id": getattr(entry, "qa_id", None),
|
|
"time": getattr(entry, "time", "") or "",
|
|
"question": question,
|
|
"node_ids": node_ids,
|
|
"edge_ids": edge_ids,
|
|
}
|
|
events.append({**base, "kind": "search", "answer": getattr(entry, "answer", "") or ""})
|
|
|
|
score = getattr(entry, "feedback_score", None)
|
|
if score is not None:
|
|
memify_metadata = getattr(entry, "memify_metadata", None) or {}
|
|
events.append(
|
|
{
|
|
**base,
|
|
"kind": "improve",
|
|
"rating": score,
|
|
"feedback_text": getattr(entry, "feedback_text", None),
|
|
"applied": bool(memify_metadata.get("feedback_weights_applied")),
|
|
}
|
|
)
|
|
return events
|
|
|
|
|
|
async def _list_recent_session_ids(user_uuid, limit: int) -> List[str]:
|
|
"""Most recently active session ids for a user, from the lifecycle table.
|
|
|
|
``user_uuid`` must be the raw UUID (the column is UUID-typed; a string
|
|
fails the SQLAlchemy bind with "'str' object has no attribute 'hex'").
|
|
"""
|
|
from sqlalchemy import select
|
|
|
|
from cognee.infrastructure.databases.relational import get_relational_engine
|
|
from cognee.modules.session_lifecycle.models import SessionRecord
|
|
|
|
engine = get_relational_engine()
|
|
async with engine.get_async_session() as session:
|
|
rows = (
|
|
(
|
|
await session.execute(
|
|
select(SessionRecord)
|
|
.where(SessionRecord.user_id == user_uuid)
|
|
.order_by(SessionRecord.last_activity_at.desc())
|
|
.limit(limit)
|
|
)
|
|
)
|
|
.scalars()
|
|
.all()
|
|
)
|
|
return [str(row.session_id) for row in rows]
|
|
|
|
|
|
async def collect_session_events(
|
|
user=None,
|
|
session_ids: Optional[List[str]] = None,
|
|
max_sessions: int = MAX_SESSIONS_SCANNED,
|
|
) -> List[Dict[str, Any]]:
|
|
"""Best-effort collection of search/improve events from the session cache.
|
|
|
|
Never raises: any unavailable layer (cache disabled, no lifecycle table,
|
|
no sessions) degrades to an empty list so visualization always renders.
|
|
"""
|
|
try:
|
|
from cognee.infrastructure.session.get_session_manager import get_session_manager
|
|
from cognee.modules.users.methods import get_default_user
|
|
|
|
if user is None:
|
|
user = await get_default_user()
|
|
user_id = str(user.id)
|
|
|
|
session_manager = get_session_manager()
|
|
if not session_manager.is_available:
|
|
logger.debug("Session cache unavailable; no operation events collected.")
|
|
return []
|
|
|
|
if session_ids is None:
|
|
try:
|
|
session_ids = await _list_recent_session_ids(user.id, max_sessions)
|
|
except Exception as error: # noqa: BLE001 — lifecycle table may not exist
|
|
logger.debug("Session listing unavailable (%s); no events collected.", error)
|
|
return []
|
|
|
|
events: List[Dict[str, Any]] = []
|
|
for session_id in session_ids[:max_sessions]:
|
|
entries = await session_manager.get_session(user_id=user_id, session_id=session_id)
|
|
if isinstance(entries, list) and entries:
|
|
events.extend(map_session_entries_to_events(session_id, entries))
|
|
|
|
events.sort(key=lambda event: (event.get("time") or "", event["kind"] == "improve"))
|
|
if events:
|
|
logger.info("Collected %d session operation events for the Memory tab.", len(events))
|
|
return events
|
|
except Exception as error: # noqa: BLE001 — visualization must never fail on this
|
|
logger.warning("Session event collection failed; rendering without them: %s", error)
|
|
return []
|