mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-18 23:57:20 +00:00
chore: merge master into dev/4.11.x
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
"""add llm reasoning config
|
||||
|
||||
Revision ID: 0018_llm_reasoning_config
|
||||
Revises: 0017_oss_workspace_identity
|
||||
Create Date: 2026-07-27
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = '0018_llm_reasoning_config'
|
||||
down_revision = '0017_oss_workspace_identity'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
_LLM_MODELS = sa.table(
|
||||
'llm_models',
|
||||
sa.column('reasoning_config', sa.JSON()),
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
inspector = sa.inspect(conn)
|
||||
if 'llm_models' not in inspector.get_table_names():
|
||||
return
|
||||
|
||||
columns = {column['name'] for column in inspector.get_columns('llm_models')}
|
||||
if 'reasoning_config' in columns:
|
||||
return
|
||||
|
||||
op.add_column(
|
||||
'llm_models',
|
||||
sa.Column(
|
||||
'reasoning_config',
|
||||
sa.JSON(),
|
||||
nullable=True,
|
||||
server_default=sa.text('\'{"level":"provider_default"}\''),
|
||||
),
|
||||
)
|
||||
conn.execute(_LLM_MODELS.update().values(reasoning_config={'level': 'provider_default'}))
|
||||
with op.batch_alter_table('llm_models') as batch_op:
|
||||
batch_op.alter_column('reasoning_config', existing_type=sa.JSON(), nullable=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
inspector = sa.inspect(conn)
|
||||
if 'llm_models' not in inspector.get_table_names():
|
||||
return
|
||||
columns = {column['name'] for column in inspector.get_columns('llm_models')}
|
||||
if 'reasoning_config' in columns:
|
||||
with op.batch_alter_table('llm_models') as batch_op:
|
||||
batch_op.drop_column('reasoning_config')
|
||||
@@ -0,0 +1,43 @@
|
||||
"""enable 3072-dimensional pgvector embeddings
|
||||
|
||||
Revision ID: 001a_pgvector_dimension_3072
|
||||
Revises: 0019_single_workspace_owner
|
||||
Create Date: 2026-08-05
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = '001a_pgvector_dimension_3072'
|
||||
down_revision = '0019_single_workspace_owner'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
_TABLE = 'langbot_vectors'
|
||||
_CHECK = 'ck_langbot_vectors_embedding_dimension_enabled'
|
||||
_INDEX = 'ix_langbot_vectors_hnsw_cosine_3072'
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
if conn.dialect.name != 'postgresql' or _TABLE not in sa.inspect(conn).get_table_names():
|
||||
return
|
||||
op.drop_constraint(_CHECK, _TABLE, type_='check')
|
||||
op.create_check_constraint(_CHECK, _TABLE, 'embedding_dimension IN (384, 512, 768, 1024, 1536, 3072)')
|
||||
op.execute(
|
||||
sa.text(
|
||||
f'CREATE INDEX {_INDEX} ON {_TABLE} USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops) WHERE embedding_dimension = 3072'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
if conn.dialect.name != 'postgresql' or _TABLE not in sa.inspect(conn).get_table_names():
|
||||
return
|
||||
count = conn.scalar(sa.text(f'SELECT COUNT(*) FROM {_TABLE} WHERE embedding_dimension = 3072'))
|
||||
if count:
|
||||
raise RuntimeError('Cannot disable 3072-dimensional pgvector while matching embeddings exist')
|
||||
op.drop_index(_INDEX, table_name=_TABLE)
|
||||
op.drop_constraint(_CHECK, _TABLE, type_='check')
|
||||
op.create_check_constraint(_CHECK, _TABLE, 'embedding_dimension IN (384, 512, 768, 1024, 1536)')
|
||||
@@ -0,0 +1,49 @@
|
||||
"""add explicit Workspace membership source
|
||||
|
||||
Revision ID: 0020_membership_source
|
||||
Revises: 001a_pgvector_dimension_3072
|
||||
Create Date: 2026-08-06
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = '0020_membership_source'
|
||||
down_revision = '001a_pgvector_dimension_3072'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
_CONSTRAINT_NAME = 'ck_workspace_memberships_source'
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
inspector = sa.inspect(conn)
|
||||
if 'workspace_memberships' not in inspector.get_table_names():
|
||||
return
|
||||
if 'source' in {column['name'] for column in inspector.get_columns('workspace_memberships')}:
|
||||
return
|
||||
|
||||
# No durable historical field distinguishes Directory-created revision-zero
|
||||
# rows from Core invitations. Protect every existing row; production can
|
||||
# reclassify separately after UUIDs have been verified against Space.
|
||||
with op.batch_alter_table('workspace_memberships') as batch_op:
|
||||
batch_op.add_column(sa.Column('source', sa.String(length=32), nullable=False, server_default='local'))
|
||||
batch_op.create_check_constraint(
|
||||
_CONSTRAINT_NAME,
|
||||
"source IN ('local', 'cloud_projection')",
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
inspector = sa.inspect(conn)
|
||||
if 'workspace_memberships' not in inspector.get_table_names():
|
||||
return
|
||||
if 'source' not in {column['name'] for column in inspector.get_columns('workspace_memberships')}:
|
||||
return
|
||||
with op.batch_alter_table('workspace_memberships') as batch_op:
|
||||
batch_op.drop_constraint(_CONSTRAINT_NAME, type_='check')
|
||||
batch_op.drop_column('source')
|
||||
@@ -0,0 +1,21 @@
|
||||
"""merge reasoning config with the main migration branch
|
||||
|
||||
Revision ID: 0021_merge_reasoning_config
|
||||
Revises: 0020_membership_source, 0018_llm_reasoning_config
|
||||
Create Date: 2026-08-09
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
revision = '0021_merge_reasoning_config'
|
||||
down_revision = ('0020_membership_source', '0018_llm_reasoning_config')
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
pass
|
||||
@@ -0,0 +1,21 @@
|
||||
"""merge AgentRunner and model reasoning migration heads
|
||||
|
||||
Revision ID: 0022_merge_agent_reasoning_heads
|
||||
Revises: 0020_merge_agent_cloud_heads, 0021_merge_reasoning_config
|
||||
Create Date: 2026-08-14
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
revision = '0022_merge_agent_reasoning_heads'
|
||||
down_revision = ('0020_merge_agent_cloud_heads', '0021_merge_reasoning_config')
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
pass
|
||||
@@ -98,7 +98,7 @@ _WORKSPACE_ALEMBIC_REVISION = '0009_workspace_tenancy'
|
||||
_RESOURCE_SCOPE_ALEMBIC_REVISION = '0010_scope_resources'
|
||||
_OSS_WORKSPACE_METADATA_KEY = 'oss_workspace_uuid'
|
||||
_RELEASE_MIGRATION_ADVISORY_LOCK_ID = 0x4C414E47424F5432
|
||||
_PGVECTOR_ALLOWED_DIMENSIONS = (384, 512, 768, 1024, 1536)
|
||||
_PGVECTOR_ALLOWED_DIMENSIONS = (384, 512, 768, 1024, 1536, 3072)
|
||||
_RUNTIME_SCHEMA = 'public'
|
||||
_ALEMBIC_RUNTIME_TABLE = 'alembic_version'
|
||||
_RUNTIME_TABLE_PRIVILEGES = frozenset({'SELECT', 'INSERT', 'UPDATE', 'DELETE'})
|
||||
@@ -1311,14 +1311,16 @@ class PersistenceManager:
|
||||
index = by_index.get(index_name)
|
||||
index_definition = normalized(None if index is None else index['definition'])
|
||||
predicate = normalized(None if index is None else index['predicate'])
|
||||
vector_type = 'halfvec' if dimension > 2000 else 'vector'
|
||||
operator_class = f'{vector_type}_cosine_ops'
|
||||
if (
|
||||
index is None
|
||||
or index['access_method'] != 'hnsw'
|
||||
or index['is_valid'] is not True
|
||||
or index['is_ready'] is not True
|
||||
or f'vector({dimension})' not in index_definition
|
||||
or f'(embedding)::vector({dimension})' not in index_definition
|
||||
or 'vector_cosine_ops' not in index_definition
|
||||
or f'{vector_type}({dimension})' not in index_definition
|
||||
or f'(embedding)::{vector_type}({dimension})' not in index_definition
|
||||
or operator_class not in index_definition
|
||||
or predicate.strip('() ') != f'embedding_dimension = {dimension}'
|
||||
):
|
||||
raise RuntimeError(f'PostgreSQL pgvector ANN index {index_name!r} is invalid')
|
||||
|
||||
@@ -13,7 +13,7 @@ import typing
|
||||
import sqlalchemy
|
||||
import sqlalchemy.ext.asyncio as sqlalchemy_asyncio
|
||||
import sqlalchemy.orm as sqlalchemy_orm
|
||||
from pgvector.sqlalchemy import Vector
|
||||
from pgvector.sqlalchemy import HALFVEC, Vector
|
||||
from sqlalchemy.dialects.postgresql.dml import OnConflictDoNothing as PostgreSQLOnConflictDoNothing
|
||||
from sqlalchemy.dialects.postgresql.dml import OnConflictDoUpdate as PostgreSQLOnConflictDoUpdate
|
||||
from sqlalchemy.dialects.sqlite.dml import OnConflictDoNothing as SQLiteOnConflictDoNothing
|
||||
@@ -282,7 +282,7 @@ def _validate_scoped_sql_type(
|
||||
return
|
||||
seen.add(identity)
|
||||
|
||||
if type(sql_type) is Vector:
|
||||
if type(sql_type) in {Vector, HALFVEC}:
|
||||
return
|
||||
if not type(sql_type).__module__.startswith('sqlalchemy.'):
|
||||
raise ScopedSessionTransactionError('TenantUnitOfWork does not allow custom SQL types in public statements')
|
||||
@@ -463,7 +463,7 @@ def _validate_scoped_statement_call(args: tuple[typing.Any, ...], kwargs: dict[s
|
||||
if isinstance(element, sqlalchemy.sql.elements.BindParameter) and element.literal_execute:
|
||||
raise ScopedSessionTransactionError('TenantUnitOfWork does not allow literal-execute SQL parameters')
|
||||
|
||||
if isinstance(element, sqlalchemy.sql.elements.Cast) and type(element.type) is not Vector:
|
||||
if isinstance(element, sqlalchemy.sql.elements.Cast) and type(element.type) not in {Vector, HALFVEC}:
|
||||
raise ScopedSessionTransactionError(
|
||||
'TenantUnitOfWork only allows the trusted pgvector cast used by tenant vector search'
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user