feat(rag): RAG scoping and context isolation controls
- Migration 0030: add conversations.rag_project_id (NULL=orphan-only, -1=all notes, positive=project), projects.auto_summary and projects.summary_updated_at - Three-value scope semantics thread from build_context() → semantic search and keyword fallback via orphan_only + effective_project_id - Project summarization background job (generate_project_summary, backfill_project_summaries) called via Ollama; triggered on project update and note saves (debounced 1h); runs at startup - New LLM tools: search_projects (SequenceMatcher scoring on title+description+auto_summary) and set_rag_scope (persists to DB, workspace-guarded, emits new_rag_scope in SSE done event) - execute_tool() accepts conv_id + workspace_project_id; generation_task passes both and captures scope changes for SSE done enrichment - Frontend: Conversation type gets rag_project_id; chat store adds ragProjectId computed + updateRagScope(); SSE done handler syncs scope - ChatView: replace sidebar ProjectSelector with a scope chip pill above the input bar, animated dropdown, pulse on model-driven scope change Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,29 @@ def _normalize_tags(tags: list[str]) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
async def _maybe_trigger_project_summary(user_id: int, project_id: int) -> None:
|
||||
"""Fire generate_project_summary() if the project summary is missing or >1h old."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from fabledassistant.models.project import Project
|
||||
from fabledassistant.services.projects import generate_project_summary
|
||||
try:
|
||||
async with async_session() as session:
|
||||
project = (await session.execute(
|
||||
select(Project).where(Project.id == project_id)
|
||||
)).scalars().first()
|
||||
if project is None:
|
||||
return
|
||||
stale = (
|
||||
project.summary_updated_at is None
|
||||
or (datetime.now(timezone.utc) - project.summary_updated_at) > timedelta(hours=1)
|
||||
)
|
||||
if stale:
|
||||
asyncio.create_task(generate_project_summary(user_id, project_id))
|
||||
except Exception:
|
||||
logger.debug("_maybe_trigger_project_summary failed for project %d", project_id, exc_info=True)
|
||||
|
||||
|
||||
async def create_note(
|
||||
user_id: int,
|
||||
title: str = "",
|
||||
@@ -61,7 +84,11 @@ async def create_note(
|
||||
session.add(note)
|
||||
await session.commit()
|
||||
await session.refresh(note)
|
||||
return note
|
||||
|
||||
if project_id is not None:
|
||||
await _maybe_trigger_project_summary(user_id, project_id)
|
||||
|
||||
return note
|
||||
|
||||
|
||||
async def get_note(user_id: int, note_id: int) -> Note | None:
|
||||
@@ -222,6 +249,9 @@ async def update_note(user_id: int, note_id: int, **fields: object) -> Note | No
|
||||
from fabledassistant.services.note_versions import create_version
|
||||
await create_version(user_id, note_id, old_body, old_title, old_tags)
|
||||
|
||||
if note.project_id is not None:
|
||||
await _maybe_trigger_project_summary(user_id, note.project_id)
|
||||
|
||||
return note
|
||||
|
||||
|
||||
@@ -303,6 +333,7 @@ async def search_notes_for_context(
|
||||
exclude_ids: set[int] | None = None,
|
||||
limit: int = 3,
|
||||
project_id: int | None = None,
|
||||
orphan_only: bool = False,
|
||||
) -> list[Note]:
|
||||
"""Search notes by keywords with OR logic. Optimized for context building — no count query."""
|
||||
async with async_session() as session:
|
||||
@@ -313,7 +344,9 @@ async def search_notes_for_context(
|
||||
keyword_filters.append(or_(Note.title.ilike(pattern), Note.body.ilike(pattern)))
|
||||
|
||||
query = select(Note).where(Note.user_id == user_id, or_(*keyword_filters))
|
||||
if project_id is not None:
|
||||
if orphan_only:
|
||||
query = query.where(Note.project_id.is_(None))
|
||||
elif project_id is not None:
|
||||
query = query.where(Note.project_id == project_id)
|
||||
if exclude_ids:
|
||||
query = query.where(Note.id.notin_(exclude_ids))
|
||||
|
||||
Reference in New Issue
Block a user