d6f4a6dbb6
- 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>
26 lines
807 B
Python
26 lines
807 B
Python
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import DateTime, ForeignKey, Integer
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from fabledassistant.models import Base
|
|
|
|
|
|
class NoteEmbedding(Base):
|
|
"""Stores the embedding vector for a note, used for semantic search."""
|
|
|
|
__tablename__ = "note_embeddings"
|
|
|
|
note_id: Mapped[int] = mapped_column(
|
|
Integer,
|
|
ForeignKey("notes.id", ondelete="CASCADE"),
|
|
primary_key=True,
|
|
)
|
|
user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
|
embedding: Mapped[list] = mapped_column(JSONB, nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True),
|
|
default=lambda: datetime.now(timezone.utc),
|
|
)
|