feat(search): add semantic similar session matching via Voyage AI embeddings
Adds vector-based similar session discovery using the existing Voyage AI
embedding infrastructure and pgvector cosine similarity search.
- New AISessionEmbedding model with vector(1024) column
- session_embedding_service: generate + upsert embeddings, find similar sessions
- Embeddings generated on session create (from problem_summary/domain) and
updated on resolve (adds resolution_summary)
- GET /ai-sessions/{id}/similar endpoint returns top-N similar sessions
- Migration a7c9e3b1f402 creates ai_session_embeddings table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,7 @@ from .notification_log import NotificationLog
|
||||
from .notification import Notification
|
||||
from .psa_activity_log import PsaActivityLog
|
||||
from .file_upload import FileUpload
|
||||
from .ai_session_embedding import AISessionEmbedding
|
||||
|
||||
__all__ = [
|
||||
"User",
|
||||
@@ -110,4 +111,5 @@ __all__ = [
|
||||
"Notification",
|
||||
"PsaActivityLog",
|
||||
"FileUpload",
|
||||
"AISessionEmbedding",
|
||||
]
|
||||
|
||||
53
backend/app/models/ai_session_embedding.py
Normal file
53
backend/app/models/ai_session_embedding.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""AI session embedding storage for similar-session matching.
|
||||
|
||||
Stores vector embeddings of AI session content (problem summary, resolution,
|
||||
domain) for cosine similarity search via pgvector. One embedding per session.
|
||||
"""
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import String, Text, DateTime, ForeignKey, Index
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
try:
|
||||
from pgvector.sqlalchemy import Vector
|
||||
except ImportError:
|
||||
Vector = None
|
||||
|
||||
|
||||
class AISessionEmbedding(Base):
|
||||
__tablename__ = "ai_session_embeddings"
|
||||
__table_args__ = (
|
||||
Index("ix_ai_session_embeddings_account_id", "account_id"),
|
||||
Index("ix_ai_session_embeddings_session_id", "session_id", unique=True),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
session_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("ai_sessions.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
)
|
||||
account_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("accounts.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
)
|
||||
chunk_text: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
embedding_model: Mapped[str] = mapped_column(
|
||||
String(50), nullable=False, default="voyage-3.5"
|
||||
)
|
||||
# The embedding column is created via migration with vector(1024) type
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
default=lambda: datetime.now(timezone.utc),
|
||||
onupdate=lambda: datetime.now(timezone.utc),
|
||||
)
|
||||
Reference in New Issue
Block a user