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:
@@ -208,6 +208,8 @@ async def run_generation(
|
||||
|
||||
last_flush = time.monotonic()
|
||||
all_tool_calls: list[dict] = []
|
||||
new_rag_scope: object = False # sentinel; set to int|None when scope changes
|
||||
new_rag_scope_label: str | None = None
|
||||
|
||||
try:
|
||||
cancelled = False
|
||||
@@ -280,7 +282,16 @@ async def run_generation(
|
||||
buf.content_so_far += err_text
|
||||
research_completed = True
|
||||
else:
|
||||
result = await execute_tool(user_id, tool_name, arguments)
|
||||
result = await execute_tool(
|
||||
user_id, tool_name, arguments,
|
||||
conv_id=conv_id,
|
||||
workspace_project_id=workspace_project_id,
|
||||
)
|
||||
|
||||
# Capture RAG scope change for SSE done event
|
||||
if result.get("type") == "rag_scope_set" and result.get("success"):
|
||||
new_rag_scope = arguments.get("project_id")
|
||||
new_rag_scope_label = result.get("scope_label")
|
||||
|
||||
timing["tools"].append({"name": tool_name, "ms": int((time.monotonic() - t_tool) * 1000)})
|
||||
logger.info("Tool %s result: success=%s", tool_name, result.get("success"))
|
||||
@@ -352,7 +363,11 @@ async def run_generation(
|
||||
|
||||
buf.state = GenerationState.COMPLETED
|
||||
buf.finished_at = time.monotonic()
|
||||
buf.append_event("done", {"done": True, "message_id": msg_id, "timing": timing})
|
||||
done_payload: dict = {"done": True, "message_id": msg_id, "timing": timing}
|
||||
if new_rag_scope is not False:
|
||||
done_payload["new_rag_scope"] = new_rag_scope
|
||||
done_payload["new_rag_scope_label"] = new_rag_scope_label
|
||||
buf.append_event("done", done_payload)
|
||||
|
||||
# Fire push notification when complete (non-critical, fire-and-forget)
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user