Add semantic note search (nomic-embed-text) and per-conversation note cache
- New NoteEmbedding model + migration 0014 stores float embeddings (JSONB) - services/embeddings.py: get_embedding, upsert_note_embedding, semantic_search_notes (cosine similarity), backfill_note_embeddings - build_context() now tries semantic search first, falls back to keyword search; accepts cached_note_ids to reuse last-turn notes and stabilise the system prompt prefix for Ollama's KV cache - generation_buffer.py: per-conversation note ID cache (get/set/clear) - generation_task.py: passes cached IDs into build_context, updates cache after each turn, and invalidates it after create_note/update_note/create_task - app.py: pulls nomic-embed-text at startup and launches a background backfill to embed all existing notes (30 s delay so Ollama has time to load the model) - routes/notes.py + services/tools.py: fire-and-forget embedding update on every note create or update via the API or LLM tool calls Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Tool definitions and executor for LLM tool calling."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -22,6 +23,15 @@ from fabledassistant.services.tag_suggestions import suggest_tags
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _schedule_embedding(note_id: int, user_id: int, title: str, body: str) -> None:
|
||||
"""Fire-and-forget: update the embedding for a note after it's created/modified."""
|
||||
from fabledassistant.services.embeddings import upsert_note_embedding
|
||||
text = f"{title}\n{body}".strip() if body else (title or "")
|
||||
if text:
|
||||
asyncio.create_task(upsert_note_embedding(note_id, user_id, text))
|
||||
|
||||
|
||||
# Core tools — always available
|
||||
_CORE_TOOLS = [
|
||||
{
|
||||
@@ -546,6 +556,7 @@ async def execute_tool(user_id: int, tool_name: str, arguments: dict) -> dict:
|
||||
due_date=_parse_due_date(arguments.get("due_date")),
|
||||
)
|
||||
suggested = await suggest_tags(user_id, task_title, task_body)
|
||||
_schedule_embedding(note.id, user_id, task_title, task_body)
|
||||
return {
|
||||
"success": True,
|
||||
"type": "task",
|
||||
@@ -568,6 +579,7 @@ async def execute_tool(user_id: int, tool_name: str, arguments: dict) -> dict:
|
||||
body=note_body,
|
||||
)
|
||||
suggested = await suggest_tags(user_id, note_title, note_body)
|
||||
_schedule_embedding(note.id, user_id, note_title, note_body)
|
||||
return {
|
||||
"success": True,
|
||||
"type": "note",
|
||||
@@ -616,6 +628,7 @@ async def execute_tool(user_id: int, tool_name: str, arguments: dict) -> dict:
|
||||
return {"success": False, "error": "Failed to update note."}
|
||||
|
||||
suggested = await suggest_tags(user_id, updated.title, updated.body or "")
|
||||
_schedule_embedding(updated.id, user_id, updated.title, updated.body or "")
|
||||
return {
|
||||
"success": True,
|
||||
"type": "note_updated",
|
||||
|
||||
Reference in New Issue
Block a user