205 lines
7.3 KiB
Python
205 lines
7.3 KiB
Python
|
|
from sqlalchemy import func, or_, select
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
from app.models.brain import BrainCandidate, BrainEvent, BrainMemory, BrainTag
|
||
|
|
from app.services.graph_service import GraphService
|
||
|
|
|
||
|
|
|
||
|
|
class BrainService:
|
||
|
|
def __init__(self, db: AsyncSession):
|
||
|
|
self.db = db
|
||
|
|
|
||
|
|
async def create_event(
|
||
|
|
self,
|
||
|
|
user_id: str,
|
||
|
|
*,
|
||
|
|
source_type: str,
|
||
|
|
source_id: str,
|
||
|
|
event_type: str,
|
||
|
|
title: str | None = None,
|
||
|
|
content_summary: str | None = None,
|
||
|
|
raw_excerpt: str | None = None,
|
||
|
|
metadata_: dict | None = None,
|
||
|
|
importance_signal: float = 0.0,
|
||
|
|
) -> BrainEvent:
|
||
|
|
event = BrainEvent(
|
||
|
|
user_id=user_id,
|
||
|
|
source_type=source_type,
|
||
|
|
source_id=source_id,
|
||
|
|
event_type=event_type,
|
||
|
|
title=title,
|
||
|
|
content_summary=content_summary,
|
||
|
|
raw_excerpt=raw_excerpt,
|
||
|
|
metadata_=metadata_,
|
||
|
|
importance_signal=importance_signal,
|
||
|
|
status="pending",
|
||
|
|
)
|
||
|
|
self.db.add(event)
|
||
|
|
await self.db.flush()
|
||
|
|
return event
|
||
|
|
|
||
|
|
async def recall_memories(self, user_id: str, current_query: str, top_k: int = 3) -> list[BrainMemory]:
|
||
|
|
query_tokens = [token.strip().lower() for token in current_query.split() if token.strip()]
|
||
|
|
statement = select(BrainMemory).where(
|
||
|
|
BrainMemory.user_id == user_id,
|
||
|
|
BrainMemory.status == "active",
|
||
|
|
)
|
||
|
|
if query_tokens:
|
||
|
|
statement = statement.where(
|
||
|
|
or_(
|
||
|
|
*[
|
||
|
|
or_(
|
||
|
|
BrainMemory.title.ilike(f"%{token}%"),
|
||
|
|
BrainMemory.content.ilike(f"%{token}%"),
|
||
|
|
)
|
||
|
|
for token in query_tokens
|
||
|
|
]
|
||
|
|
)
|
||
|
|
)
|
||
|
|
result = await self.db.execute(
|
||
|
|
statement.order_by(BrainMemory.importance.desc(), BrainMemory.created_at.desc()).limit(top_k)
|
||
|
|
)
|
||
|
|
memories = list(result.scalars().all())
|
||
|
|
if memories or query_tokens:
|
||
|
|
return memories
|
||
|
|
|
||
|
|
fallback_result = await self.db.execute(
|
||
|
|
select(BrainMemory)
|
||
|
|
.where(BrainMemory.user_id == user_id, BrainMemory.status == "active")
|
||
|
|
.order_by(BrainMemory.importance.desc(), BrainMemory.created_at.desc())
|
||
|
|
.limit(top_k)
|
||
|
|
)
|
||
|
|
return list(fallback_result.scalars().all())
|
||
|
|
|
||
|
|
async def get_overview(self, user_id: str) -> dict:
|
||
|
|
active_memory_count = (
|
||
|
|
await self.db.execute(
|
||
|
|
select(func.count()).select_from(BrainMemory).where(
|
||
|
|
BrainMemory.user_id == user_id,
|
||
|
|
BrainMemory.status == "active",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
).scalar() or 0
|
||
|
|
|
||
|
|
important_tag_count = (
|
||
|
|
await self.db.execute(
|
||
|
|
select(func.count()).select_from(BrainTag).where(
|
||
|
|
BrainTag.user_id == user_id,
|
||
|
|
BrainTag.priority == "important",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
).scalar() or 0
|
||
|
|
|
||
|
|
secondary_tag_count = (
|
||
|
|
await self.db.execute(
|
||
|
|
select(func.count()).select_from(BrainTag).where(
|
||
|
|
BrainTag.user_id == user_id,
|
||
|
|
BrainTag.priority == "secondary",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
).scalar() or 0
|
||
|
|
|
||
|
|
recent_memory_result = await self.db.execute(
|
||
|
|
select(BrainMemory.title)
|
||
|
|
.where(BrainMemory.user_id == user_id, BrainMemory.status == "active")
|
||
|
|
.order_by(BrainMemory.importance.desc(), BrainMemory.created_at.desc())
|
||
|
|
.limit(5)
|
||
|
|
)
|
||
|
|
recent_memory_titles = list(recent_memory_result.scalars().all())
|
||
|
|
|
||
|
|
return {
|
||
|
|
"active_memory_count": active_memory_count,
|
||
|
|
"important_tag_count": important_tag_count,
|
||
|
|
"secondary_tag_count": secondary_tag_count,
|
||
|
|
"recent_memory_titles": recent_memory_titles,
|
||
|
|
}
|
||
|
|
|
||
|
|
async def list_memories(self, user_id: str) -> list[BrainMemory]:
|
||
|
|
result = await self.db.execute(
|
||
|
|
select(BrainMemory)
|
||
|
|
.where(BrainMemory.user_id == user_id, BrainMemory.status == "active")
|
||
|
|
.order_by(BrainMemory.importance.desc(), BrainMemory.created_at.desc())
|
||
|
|
)
|
||
|
|
return list(result.scalars().all())
|
||
|
|
|
||
|
|
async def list_tags(self, user_id: str) -> dict:
|
||
|
|
important_result = await self.db.execute(
|
||
|
|
select(BrainTag)
|
||
|
|
.where(BrainTag.user_id == user_id, BrainTag.priority == "important")
|
||
|
|
.order_by(BrainTag.score.desc(), BrainTag.created_at.desc())
|
||
|
|
)
|
||
|
|
secondary_result = await self.db.execute(
|
||
|
|
select(BrainTag)
|
||
|
|
.where(BrainTag.user_id == user_id, BrainTag.priority == "secondary")
|
||
|
|
.order_by(BrainTag.score.desc(), BrainTag.created_at.desc())
|
||
|
|
)
|
||
|
|
return {
|
||
|
|
"important": list(important_result.scalars().all()),
|
||
|
|
"secondary": list(secondary_result.scalars().all()),
|
||
|
|
}
|
||
|
|
|
||
|
|
async def list_events(self, user_id: str) -> list[BrainEvent]:
|
||
|
|
result = await self.db.execute(
|
||
|
|
select(BrainEvent)
|
||
|
|
.where(BrainEvent.user_id == user_id)
|
||
|
|
.order_by(BrainEvent.created_at.desc())
|
||
|
|
)
|
||
|
|
return list(result.scalars().all())
|
||
|
|
|
||
|
|
async def run_learning(self, user_id: str) -> dict:
|
||
|
|
pending_events_result = await self.db.execute(
|
||
|
|
select(BrainEvent)
|
||
|
|
.where(BrainEvent.user_id == user_id, BrainEvent.status == "pending")
|
||
|
|
.order_by(BrainEvent.created_at.asc())
|
||
|
|
)
|
||
|
|
pending_events = list(pending_events_result.scalars().all())
|
||
|
|
pending_count = len(pending_events)
|
||
|
|
|
||
|
|
candidates_created = 0
|
||
|
|
memories_promoted = 0
|
||
|
|
|
||
|
|
if pending_events:
|
||
|
|
candidate = BrainCandidate(
|
||
|
|
user_id=user_id,
|
||
|
|
candidate_type="daily_learning",
|
||
|
|
title="Daily learning synthesis",
|
||
|
|
summary=f"Processed {pending_count} pending brain events.",
|
||
|
|
importance_score=float(pending_count),
|
||
|
|
confidence_score=1.0,
|
||
|
|
status="promoted",
|
||
|
|
source_event_ids=[event.id for event in pending_events],
|
||
|
|
)
|
||
|
|
self.db.add(candidate)
|
||
|
|
await self.db.flush()
|
||
|
|
candidates_created = 1
|
||
|
|
|
||
|
|
memory = BrainMemory(
|
||
|
|
user_id=user_id,
|
||
|
|
memory_type="daily_learning",
|
||
|
|
title="Daily learning synthesis",
|
||
|
|
content=f"Processed {pending_count} pending brain events.",
|
||
|
|
importance=max(pending_count, 1),
|
||
|
|
confidence=1.0,
|
||
|
|
status="active",
|
||
|
|
origin_candidate_id=candidate.id,
|
||
|
|
origin_source_types=sorted({event.source_type for event in pending_events}),
|
||
|
|
)
|
||
|
|
self.db.add(memory)
|
||
|
|
memories_promoted = 1
|
||
|
|
|
||
|
|
for event in pending_events:
|
||
|
|
event.status = "processed"
|
||
|
|
event.processed_at = memory.created_at
|
||
|
|
|
||
|
|
await self.db.commit()
|
||
|
|
else:
|
||
|
|
await self.db.commit()
|
||
|
|
|
||
|
|
await GraphService(self.db).build_graph(user_id)
|
||
|
|
|
||
|
|
return {
|
||
|
|
"events_considered": pending_count,
|
||
|
|
"candidates_created": candidates_created,
|
||
|
|
"memories_promoted": memories_promoted,
|
||
|
|
}
|