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:
@@ -0,0 +1,83 @@
|
||||
"""add ai_session_embeddings table for similar-session matching
|
||||
|
||||
Revision ID: a7c9e3b1f402
|
||||
Revises: dbf67047d4c8
|
||||
Create Date: 2026-03-20
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "a7c9e3b1f402"
|
||||
down_revision: Union[str, None] = "dbf67047d4c8"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# pgvector extension should already exist from migration 042
|
||||
op.execute("CREATE EXTENSION IF NOT EXISTS vector")
|
||||
|
||||
op.create_table(
|
||||
"ai_session_embeddings",
|
||||
sa.Column(
|
||||
"id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
server_default=sa.text("gen_random_uuid()"),
|
||||
),
|
||||
sa.Column(
|
||||
"session_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("ai_sessions.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"account_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("accounts.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("chunk_text", sa.Text(), nullable=False),
|
||||
sa.Column(
|
||||
"embedding_model",
|
||||
sa.String(50),
|
||||
nullable=False,
|
||||
server_default="voyage-3.5",
|
||||
),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
),
|
||||
)
|
||||
|
||||
# Add vector column via raw SQL (pgvector type not in SA dialect)
|
||||
op.execute(
|
||||
"ALTER TABLE ai_session_embeddings ADD COLUMN embedding vector(1024)"
|
||||
)
|
||||
|
||||
op.create_index(
|
||||
"ix_ai_session_embeddings_session_id",
|
||||
"ai_session_embeddings",
|
||||
["session_id"],
|
||||
unique=True,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_ai_session_embeddings_account_id",
|
||||
"ai_session_embeddings",
|
||||
["account_id"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("ai_session_embeddings")
|
||||
Reference in New Issue
Block a user