test(agent): harden runner persistence coverage

This commit is contained in:
huanghuoguoguo
2026-06-08 11:50:12 +08:00
parent fa7b1b53a6
commit a1be41618c
8 changed files with 293 additions and 224 deletions

View File

@@ -16,23 +16,23 @@ from langbot.pkg.entity.persistence.base import Base
# Import all ORM models so they are registered with Base.metadata
# This is required for autogenerate to detect model changes
from langbot.pkg.entity.persistence import (
agent_runner_state,
apikey,
artifact,
bot,
bstorage,
event_log,
mcp,
metadata,
model,
monitoring,
pipeline,
plugin,
rag,
transcript,
user,
vector,
webhook,
agent_runner_state, # noqa: F401
apikey, # noqa: F401
artifact, # noqa: F401
bot, # noqa: F401
bstorage, # noqa: F401
event_log, # noqa: F401
mcp, # noqa: F401
metadata, # noqa: F401
model, # noqa: F401
monitoring, # noqa: F401
pipeline, # noqa: F401
plugin, # noqa: F401
rag, # noqa: F401
transcript, # noqa: F401
user, # noqa: F401
vector, # noqa: F401
webhook, # noqa: F401
)
target_metadata = Base.metadata

View File

@@ -14,89 +14,111 @@ branch_labels = None
depends_on = None
def _table_exists(table_name: str) -> bool:
return table_name in sa.inspect(op.get_bind()).get_table_names()
def _index_exists(table_name: str, index_name: str) -> bool:
return index_name in {index['name'] for index in sa.inspect(op.get_bind()).get_indexes(table_name)}
def _create_index_if_missing(table_name: str, index_name: str, columns: list[str], *, unique: bool = False) -> None:
if not _table_exists(table_name) or _index_exists(table_name, index_name):
return
with op.batch_alter_table(table_name, schema=None) as batch_op:
batch_op.create_index(index_name, columns, unique=unique)
def _drop_index_if_exists(table_name: str, index_name: str) -> None:
if not _table_exists(table_name) or not _index_exists(table_name, index_name):
return
with op.batch_alter_table(table_name, schema=None) as batch_op:
batch_op.drop_index(index_name)
def upgrade() -> None:
# Create event_log table
op.create_table(
'event_log',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True),
sa.Column('event_id', sa.String(255), nullable=False, unique=True),
sa.Column('event_type', sa.String(100), nullable=False),
sa.Column('event_time', sa.DateTime(), nullable=True),
sa.Column('source', sa.String(50), nullable=False),
sa.Column('bot_id', sa.String(255), nullable=True),
sa.Column('workspace_id', sa.String(255), nullable=True),
sa.Column('conversation_id', sa.String(255), nullable=True),
sa.Column('thread_id', sa.String(255), nullable=True),
sa.Column('actor_type', sa.String(50), nullable=True),
sa.Column('actor_id', sa.String(255), nullable=True),
sa.Column('actor_name', sa.String(255), nullable=True),
sa.Column('subject_type', sa.String(50), nullable=True),
sa.Column('subject_id', sa.String(255), nullable=True),
sa.Column('input_summary', sa.Text(), nullable=True),
sa.Column('input_json', sa.Text(), nullable=True),
sa.Column('raw_ref', sa.String(255), nullable=True),
sa.Column('run_id', sa.String(255), nullable=True),
sa.Column('runner_id', sa.String(255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('(CURRENT_TIMESTAMP)')),
sa.Column('metadata_json', sa.Text(), nullable=True),
)
if not _table_exists('event_log'):
op.create_table(
'event_log',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True),
sa.Column('event_id', sa.String(255), nullable=False, unique=True),
sa.Column('event_type', sa.String(100), nullable=False),
sa.Column('event_time', sa.DateTime(), nullable=True),
sa.Column('source', sa.String(50), nullable=False),
sa.Column('bot_id', sa.String(255), nullable=True),
sa.Column('workspace_id', sa.String(255), nullable=True),
sa.Column('conversation_id', sa.String(255), nullable=True),
sa.Column('thread_id', sa.String(255), nullable=True),
sa.Column('actor_type', sa.String(50), nullable=True),
sa.Column('actor_id', sa.String(255), nullable=True),
sa.Column('actor_name', sa.String(255), nullable=True),
sa.Column('subject_type', sa.String(50), nullable=True),
sa.Column('subject_id', sa.String(255), nullable=True),
sa.Column('input_summary', sa.Text(), nullable=True),
sa.Column('input_json', sa.Text(), nullable=True),
sa.Column('raw_ref', sa.String(255), nullable=True),
sa.Column('run_id', sa.String(255), nullable=True),
sa.Column('runner_id', sa.String(255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('(CURRENT_TIMESTAMP)')),
sa.Column('metadata_json', sa.Text(), nullable=True),
)
# Create indexes for event_log
with op.batch_alter_table('event_log', schema=None) as batch_op:
batch_op.create_index('ix_event_log_event_id', ['event_id'], unique=True)
batch_op.create_index('ix_event_log_event_type', ['event_type'], unique=False)
batch_op.create_index('ix_event_log_bot_id', ['bot_id'], unique=False)
batch_op.create_index('ix_event_log_conversation_id', ['conversation_id'], unique=False)
batch_op.create_index('ix_event_log_run_id', ['run_id'], unique=False)
_create_index_if_missing('event_log', 'ix_event_log_event_id', ['event_id'], unique=True)
_create_index_if_missing('event_log', 'ix_event_log_event_type', ['event_type'])
_create_index_if_missing('event_log', 'ix_event_log_bot_id', ['bot_id'])
_create_index_if_missing('event_log', 'ix_event_log_conversation_id', ['conversation_id'])
_create_index_if_missing('event_log', 'ix_event_log_run_id', ['run_id'])
# Create transcript table
op.create_table(
'transcript',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True),
sa.Column('transcript_id', sa.String(255), nullable=False, unique=True),
sa.Column('event_id', sa.String(255), nullable=False),
sa.Column('conversation_id', sa.String(255), nullable=False),
sa.Column('thread_id', sa.String(255), nullable=True),
sa.Column('role', sa.String(50), nullable=False),
sa.Column('item_type', sa.String(50), nullable=False, server_default='message'),
sa.Column('content', sa.Text(), nullable=True),
sa.Column('content_json', sa.Text(), nullable=True),
sa.Column('artifact_refs_json', sa.Text(), nullable=True),
sa.Column('seq', sa.Integer(), nullable=False),
sa.Column('run_id', sa.String(255), nullable=True),
sa.Column('runner_id', sa.String(255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('(CURRENT_TIMESTAMP)')),
sa.Column('metadata_json', sa.Text(), nullable=True),
)
if not _table_exists('transcript'):
op.create_table(
'transcript',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True),
sa.Column('transcript_id', sa.String(255), nullable=False, unique=True),
sa.Column('event_id', sa.String(255), nullable=False),
sa.Column('conversation_id', sa.String(255), nullable=False),
sa.Column('thread_id', sa.String(255), nullable=True),
sa.Column('role', sa.String(50), nullable=False),
sa.Column('item_type', sa.String(50), nullable=False, server_default='message'),
sa.Column('content', sa.Text(), nullable=True),
sa.Column('content_json', sa.Text(), nullable=True),
sa.Column('artifact_refs_json', sa.Text(), nullable=True),
sa.Column('seq', sa.Integer(), nullable=False),
sa.Column('run_id', sa.String(255), nullable=True),
sa.Column('runner_id', sa.String(255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('(CURRENT_TIMESTAMP)')),
sa.Column('metadata_json', sa.Text(), nullable=True),
)
# Create indexes for transcript
with op.batch_alter_table('transcript', schema=None) as batch_op:
batch_op.create_index('ix_transcript_transcript_id', ['transcript_id'], unique=True)
batch_op.create_index('ix_transcript_event_id', ['event_id'], unique=False)
batch_op.create_index('ix_transcript_conversation_id', ['conversation_id'], unique=False)
batch_op.create_index('ix_transcript_conversation_seq', ['conversation_id', 'seq'], unique=False)
batch_op.create_index('ix_transcript_conversation_created', ['conversation_id', 'created_at'], unique=False)
batch_op.create_index('ix_transcript_run_id', ['run_id'], unique=False)
_create_index_if_missing('transcript', 'ix_transcript_transcript_id', ['transcript_id'], unique=True)
_create_index_if_missing('transcript', 'ix_transcript_event_id', ['event_id'])
_create_index_if_missing('transcript', 'ix_transcript_conversation_id', ['conversation_id'])
_create_index_if_missing('transcript', 'ix_transcript_conversation_seq', ['conversation_id', 'seq'])
_create_index_if_missing('transcript', 'ix_transcript_conversation_created', ['conversation_id', 'created_at'])
_create_index_if_missing('transcript', 'ix_transcript_run_id', ['run_id'])
def downgrade() -> None:
# Drop transcript table
with op.batch_alter_table('transcript', schema=None) as batch_op:
batch_op.drop_index('ix_transcript_run_id')
batch_op.drop_index('ix_transcript_conversation_created')
batch_op.drop_index('ix_transcript_conversation_seq')
batch_op.drop_index('ix_transcript_conversation_id')
batch_op.drop_index('ix_transcript_event_id')
batch_op.drop_index('ix_transcript_transcript_id')
_drop_index_if_exists('transcript', 'ix_transcript_run_id')
_drop_index_if_exists('transcript', 'ix_transcript_conversation_created')
_drop_index_if_exists('transcript', 'ix_transcript_conversation_seq')
_drop_index_if_exists('transcript', 'ix_transcript_conversation_id')
_drop_index_if_exists('transcript', 'ix_transcript_event_id')
_drop_index_if_exists('transcript', 'ix_transcript_transcript_id')
op.drop_table('transcript')
if _table_exists('transcript'):
op.drop_table('transcript')
# Drop event_log table
with op.batch_alter_table('event_log', schema=None) as batch_op:
batch_op.drop_index('ix_event_log_run_id')
batch_op.drop_index('ix_event_log_conversation_id')
batch_op.drop_index('ix_event_log_bot_id')
batch_op.drop_index('ix_event_log_event_type')
batch_op.drop_index('ix_event_log_event_id')
_drop_index_if_exists('event_log', 'ix_event_log_run_id')
_drop_index_if_exists('event_log', 'ix_event_log_conversation_id')
_drop_index_if_exists('event_log', 'ix_event_log_bot_id')
_drop_index_if_exists('event_log', 'ix_event_log_event_type')
_drop_index_if_exists('event_log', 'ix_event_log_event_id')
op.drop_table('event_log')
if _table_exists('event_log'):
op.drop_table('event_log')

View File

@@ -16,53 +16,79 @@ branch_labels = None
depends_on = None
def _table_exists(table_name: str) -> bool:
return table_name in sa.inspect(op.get_bind()).get_table_names()
def _index_exists(table_name: str, index_name: str) -> bool:
return index_name in {index['name'] for index in sa.inspect(op.get_bind()).get_indexes(table_name)}
def _create_index_if_missing(table_name: str, index_name: str, columns: list[str], *, unique: bool = False) -> None:
if not _table_exists(table_name) or _index_exists(table_name, index_name):
return
with op.batch_alter_table(table_name, schema=None) as batch_op:
batch_op.create_index(index_name, columns, unique=unique)
def _drop_index_if_exists(table_name: str, index_name: str) -> None:
if not _table_exists(table_name) or not _index_exists(table_name, index_name):
return
with op.batch_alter_table(table_name, schema=None) as batch_op:
batch_op.drop_index(index_name)
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('agent_runner_state',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('runner_id', sa.String(length=255), nullable=False),
sa.Column('binding_identity', sa.String(length=255), nullable=False),
sa.Column('scope', sa.String(length=50), nullable=False),
sa.Column('scope_key', sa.String(length=512), nullable=False),
sa.Column('state_key', sa.String(length=255), nullable=False),
sa.Column('value_json', sa.Text(), nullable=True),
sa.Column('bot_id', sa.String(length=255), nullable=True),
sa.Column('workspace_id', sa.String(length=255), nullable=True),
sa.Column('conversation_id', sa.String(length=255), nullable=True),
sa.Column('thread_id', sa.String(length=255), nullable=True),
sa.Column('actor_type', sa.String(length=50), nullable=True),
sa.Column('actor_id', sa.String(length=255), nullable=True),
sa.Column('subject_type', sa.String(length=50), nullable=True),
sa.Column('subject_id', sa.String(length=255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('scope_key', 'state_key', name='uq_agent_runner_state_scope_key_state_key')
if not _table_exists('agent_runner_state'):
op.create_table('agent_runner_state',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('runner_id', sa.String(length=255), nullable=False),
sa.Column('binding_identity', sa.String(length=255), nullable=False),
sa.Column('scope', sa.String(length=50), nullable=False),
sa.Column('scope_key', sa.String(length=512), nullable=False),
sa.Column('state_key', sa.String(length=255), nullable=False),
sa.Column('value_json', sa.Text(), nullable=True),
sa.Column('bot_id', sa.String(length=255), nullable=True),
sa.Column('workspace_id', sa.String(length=255), nullable=True),
sa.Column('conversation_id', sa.String(length=255), nullable=True),
sa.Column('thread_id', sa.String(length=255), nullable=True),
sa.Column('actor_type', sa.String(length=50), nullable=True),
sa.Column('actor_id', sa.String(length=255), nullable=True),
sa.Column('subject_type', sa.String(length=50), nullable=True),
sa.Column('subject_id', sa.String(length=255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('scope_key', 'state_key', name='uq_agent_runner_state_scope_key_state_key')
)
_create_index_if_missing('agent_runner_state', 'ix_agent_runner_state_actor_id', ['actor_id'])
_create_index_if_missing('agent_runner_state', 'ix_agent_runner_state_binding_identity', ['binding_identity'])
_create_index_if_missing('agent_runner_state', 'ix_agent_runner_state_bot_id', ['bot_id'])
_create_index_if_missing('agent_runner_state', 'ix_agent_runner_state_conversation_id', ['conversation_id'])
_create_index_if_missing(
'agent_runner_state',
'ix_agent_runner_state_runner_binding',
['runner_id', 'binding_identity'],
)
with op.batch_alter_table('agent_runner_state', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_agent_runner_state_actor_id'), ['actor_id'], unique=False)
batch_op.create_index(batch_op.f('ix_agent_runner_state_binding_identity'), ['binding_identity'], unique=False)
batch_op.create_index(batch_op.f('ix_agent_runner_state_bot_id'), ['bot_id'], unique=False)
batch_op.create_index(batch_op.f('ix_agent_runner_state_conversation_id'), ['conversation_id'], unique=False)
batch_op.create_index('ix_agent_runner_state_runner_binding', ['runner_id', 'binding_identity'], unique=False)
batch_op.create_index(batch_op.f('ix_agent_runner_state_runner_id'), ['runner_id'], unique=False)
batch_op.create_index(batch_op.f('ix_agent_runner_state_scope'), ['scope'], unique=False)
batch_op.create_index(batch_op.f('ix_agent_runner_state_scope_key'), ['scope_key'], unique=False)
_create_index_if_missing('agent_runner_state', 'ix_agent_runner_state_runner_id', ['runner_id'])
_create_index_if_missing('agent_runner_state', 'ix_agent_runner_state_scope', ['scope'])
_create_index_if_missing('agent_runner_state', 'ix_agent_runner_state_scope_key', ['scope_key'])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('agent_runner_state', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_agent_runner_state_scope_key'))
batch_op.drop_index(batch_op.f('ix_agent_runner_state_scope'))
batch_op.drop_index(batch_op.f('ix_agent_runner_state_runner_id'))
batch_op.drop_index('ix_agent_runner_state_runner_binding')
batch_op.drop_index(batch_op.f('ix_agent_runner_state_conversation_id'))
batch_op.drop_index(batch_op.f('ix_agent_runner_state_bot_id'))
batch_op.drop_index(batch_op.f('ix_agent_runner_state_binding_identity'))
batch_op.drop_index(batch_op.f('ix_agent_runner_state_actor_id'))
_drop_index_if_exists('agent_runner_state', 'ix_agent_runner_state_scope_key')
_drop_index_if_exists('agent_runner_state', 'ix_agent_runner_state_scope')
_drop_index_if_exists('agent_runner_state', 'ix_agent_runner_state_runner_id')
_drop_index_if_exists('agent_runner_state', 'ix_agent_runner_state_runner_binding')
_drop_index_if_exists('agent_runner_state', 'ix_agent_runner_state_conversation_id')
_drop_index_if_exists('agent_runner_state', 'ix_agent_runner_state_bot_id')
_drop_index_if_exists('agent_runner_state', 'ix_agent_runner_state_binding_identity')
_drop_index_if_exists('agent_runner_state', 'ix_agent_runner_state_actor_id')
op.drop_table('agent_runner_state')
if _table_exists('agent_runner_state'):
op.drop_table('agent_runner_state')
# ### end Alembic commands ###

View File

@@ -14,42 +14,64 @@ branch_labels = None
depends_on = None
def _table_exists(table_name: str) -> bool:
return table_name in sa.inspect(op.get_bind()).get_table_names()
def _index_exists(table_name: str, index_name: str) -> bool:
return index_name in {index['name'] for index in sa.inspect(op.get_bind()).get_indexes(table_name)}
def _create_index_if_missing(table_name: str, index_name: str, columns: list[str], *, unique: bool = False) -> None:
if not _table_exists(table_name) or _index_exists(table_name, index_name):
return
with op.batch_alter_table(table_name, schema=None) as batch_op:
batch_op.create_index(index_name, columns, unique=unique)
def _drop_index_if_exists(table_name: str, index_name: str) -> None:
if not _table_exists(table_name) or not _index_exists(table_name, index_name):
return
with op.batch_alter_table(table_name, schema=None) as batch_op:
batch_op.drop_index(index_name)
def upgrade() -> None:
# Create agent_artifact table
op.create_table(
'agent_artifact',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True),
sa.Column('artifact_id', sa.String(255), nullable=False, unique=True),
sa.Column('artifact_type', sa.String(50), nullable=False),
sa.Column('mime_type', sa.String(255), nullable=True),
sa.Column('name', sa.String(255), nullable=True),
sa.Column('size_bytes', sa.BigInteger(), nullable=True),
sa.Column('sha256', sa.String(64), nullable=True),
sa.Column('source', sa.String(50), nullable=False),
sa.Column('storage_key', sa.String(255), nullable=True),
sa.Column('storage_type', sa.String(50), nullable=False, server_default='binary_storage'),
sa.Column('conversation_id', sa.String(255), nullable=True),
sa.Column('run_id', sa.String(255), nullable=True),
sa.Column('runner_id', sa.String(255), nullable=True),
sa.Column('bot_id', sa.String(255), nullable=True),
sa.Column('workspace_id', sa.String(255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('(CURRENT_TIMESTAMP)')),
sa.Column('expires_at', sa.DateTime(), nullable=True),
sa.Column('metadata_json', sa.Text(), nullable=True),
)
if not _table_exists('agent_artifact'):
op.create_table(
'agent_artifact',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True),
sa.Column('artifact_id', sa.String(255), nullable=False, unique=True),
sa.Column('artifact_type', sa.String(50), nullable=False),
sa.Column('mime_type', sa.String(255), nullable=True),
sa.Column('name', sa.String(255), nullable=True),
sa.Column('size_bytes', sa.BigInteger(), nullable=True),
sa.Column('sha256', sa.String(64), nullable=True),
sa.Column('source', sa.String(50), nullable=False),
sa.Column('storage_key', sa.String(255), nullable=True),
sa.Column('storage_type', sa.String(50), nullable=False, server_default='binary_storage'),
sa.Column('conversation_id', sa.String(255), nullable=True),
sa.Column('run_id', sa.String(255), nullable=True),
sa.Column('runner_id', sa.String(255), nullable=True),
sa.Column('bot_id', sa.String(255), nullable=True),
sa.Column('workspace_id', sa.String(255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('(CURRENT_TIMESTAMP)')),
sa.Column('expires_at', sa.DateTime(), nullable=True),
sa.Column('metadata_json', sa.Text(), nullable=True),
)
# Create indexes for agent_artifact
with op.batch_alter_table('agent_artifact', schema=None) as batch_op:
batch_op.create_index('ix_agent_artifact_artifact_id', ['artifact_id'], unique=True)
batch_op.create_index('ix_agent_artifact_conversation_id', ['conversation_id'], unique=False)
batch_op.create_index('ix_agent_artifact_run_id', ['run_id'], unique=False)
_create_index_if_missing('agent_artifact', 'ix_agent_artifact_artifact_id', ['artifact_id'], unique=True)
_create_index_if_missing('agent_artifact', 'ix_agent_artifact_conversation_id', ['conversation_id'])
_create_index_if_missing('agent_artifact', 'ix_agent_artifact_run_id', ['run_id'])
def downgrade() -> None:
# Drop agent_artifact table
with op.batch_alter_table('agent_artifact', schema=None) as batch_op:
batch_op.drop_index('ix_agent_artifact_run_id')
batch_op.drop_index('ix_agent_artifact_conversation_id')
batch_op.drop_index('ix_agent_artifact_artifact_id')
_drop_index_if_exists('agent_artifact', 'ix_agent_artifact_run_id')
_drop_index_if_exists('agent_artifact', 'ix_agent_artifact_conversation_id')
_drop_index_if_exists('agent_artifact', 'ix_agent_artifact_artifact_id')
op.drop_table('agent_artifact')
if _table_exists('agent_artifact'):
op.drop_table('agent_artifact')