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
127 lines
4.8 KiB
Python
127 lines
4.8 KiB
Python
"""SQLAlchemy models for session lifecycle."""
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import Column, DateTime, Float, Integer, String, Text, UUID
|
|
|
|
from cognee.infrastructure.databases.relational import Base
|
|
|
|
|
|
class SessionRecord(Base):
|
|
"""One row per (user, session_id).
|
|
|
|
Narrow by design: lifecycle + aggregate counters only. The QA
|
|
entries and trace steps themselves live in the session cache
|
|
(Redis / FS) and are referenced here only indirectly via
|
|
``session_id`` (which is a string, not a FK — cache IDs aren't
|
|
FK-able).
|
|
|
|
Status is stored as a string but treated as an enum at the app
|
|
layer (see ``SessionStatus``). The ``abandoned`` transition is
|
|
inferred at read time from ``last_activity_at`` rather than
|
|
written — a running session that's been idle past the threshold
|
|
is reported as abandoned without any sweeper touching the row.
|
|
"""
|
|
|
|
__tablename__ = "session_records"
|
|
|
|
# Session ID is a caller-provided string (e.g. "cc_myproj_ab12cd34ef56"
|
|
# from the Claude Code plugin). Scoped per user — same string from
|
|
# two users is two different sessions.
|
|
session_id = Column(String, primary_key=True)
|
|
user_id = Column(UUID, primary_key=True, index=True)
|
|
|
|
dataset_id = Column(UUID, nullable=True, index=True)
|
|
|
|
# Stored status. "abandoned" is the only value inferred at read
|
|
# time instead of being stored — everything else (running,
|
|
# completed, failed) is explicitly set.
|
|
status = Column(String, nullable=False, default="running", index=True)
|
|
|
|
started_at = Column(
|
|
DateTime(timezone=True),
|
|
nullable=False,
|
|
default=lambda: datetime.now(timezone.utc),
|
|
)
|
|
last_activity_at = Column(
|
|
DateTime(timezone=True),
|
|
nullable=False,
|
|
default=lambda: datetime.now(timezone.utc),
|
|
index=True,
|
|
)
|
|
ended_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
# Aggregate counters accumulated by LLMGateway / SessionManager hooks.
|
|
tokens_in = Column(Integer, nullable=False, default=0)
|
|
tokens_out = Column(Integer, nullable=False, default=0)
|
|
cost_usd = Column(Float, nullable=False, default=0.0)
|
|
|
|
# Cheap per-session counters useful for the dashboard without
|
|
# scanning the cache.
|
|
error_count = Column(Integer, nullable=False, default=0)
|
|
|
|
# Last-seen model string — informational only. Per-model cost
|
|
# aggregates live in ``SessionModelUsage`` so mixed-model sessions
|
|
# attribute correctly.
|
|
last_model = Column(Text, nullable=True)
|
|
|
|
def to_dict(self) -> dict:
|
|
started = getattr(self, "started_at", None)
|
|
last_act = getattr(self, "last_activity_at", None)
|
|
ended = getattr(self, "ended_at", None)
|
|
dataset = getattr(self, "dataset_id", None)
|
|
return {
|
|
"session_id": self.session_id,
|
|
"user_id": str(self.user_id),
|
|
"dataset_id": str(dataset) if dataset is not None else None,
|
|
"status": self.status,
|
|
"started_at": started.isoformat() if started is not None else None,
|
|
"last_activity_at": last_act.isoformat() if last_act is not None else None,
|
|
"ended_at": ended.isoformat() if ended is not None else None,
|
|
"tokens_in": self.tokens_in,
|
|
"tokens_out": self.tokens_out,
|
|
"cost_usd": self.cost_usd,
|
|
"error_count": self.error_count,
|
|
"last_model": self.last_model,
|
|
}
|
|
|
|
|
|
class SessionModelUsage(Base):
|
|
"""Per-(session, user, model) token + cost aggregate.
|
|
|
|
Populated by ``accumulate_usage`` when an LLM call fires inside a
|
|
tracked session scope. Normalizing this out of ``SessionRecord``
|
|
lets mixed-model sessions (e.g. embedding calls + completion calls
|
|
on different models) attribute cost correctly in
|
|
``GET /api/v1/sessions/cost-by-model``.
|
|
"""
|
|
|
|
__tablename__ = "session_model_usage"
|
|
|
|
session_id = Column(String, primary_key=True)
|
|
user_id = Column(UUID, primary_key=True, index=True)
|
|
model = Column(Text, primary_key=True)
|
|
|
|
tokens_in = Column(Integer, nullable=False, default=0)
|
|
tokens_out = Column(Integer, nullable=False, default=0)
|
|
cost_usd = Column(Float, nullable=False, default=0.0)
|
|
|
|
updated_at = Column(
|
|
DateTime(timezone=True),
|
|
nullable=False,
|
|
default=lambda: datetime.now(timezone.utc),
|
|
onupdate=lambda: datetime.now(timezone.utc),
|
|
)
|
|
|
|
def to_dict(self) -> dict:
|
|
updated = getattr(self, "updated_at", None)
|
|
return {
|
|
"session_id": self.session_id,
|
|
"user_id": str(self.user_id),
|
|
"model": self.model,
|
|
"tokens_in": self.tokens_in,
|
|
"tokens_out": self.tokens_out,
|
|
"cost_usd": self.cost_usd,
|
|
"updated_at": updated.isoformat() if updated is not None else None,
|
|
}
|