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>
54 lines
1.8 KiB
Python
54 lines
1.8 KiB
Python
"""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),
|
|
)
|