diff --git a/src/agents/extensions/memory/sqlalchemy_session.py b/src/agents/extensions/memory/sqlalchemy_session.py index 81f7dcdae8..f15a26662d 100644 --- a/src/agents/extensions/memory/sqlalchemy_session.py +++ b/src/agents/extensions/memory/sqlalchemy_session.py @@ -48,6 +48,7 @@ text as sql_text, update, ) +from sqlalchemy.dialects.mysql import LONGTEXT from sqlalchemy.exc import IntegrityError, OperationalError from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine @@ -64,7 +65,12 @@ class SQLAlchemySession(SessionABC): - """SQLAlchemy implementation of [`Session`][agents.memory.session.Session].""" + """SQLAlchemy implementation of [`Session`][agents.memory.session.Session]. + + Newly created MySQL and MariaDB message tables use LONGTEXT for serialized items. + Existing tables are not migrated; applications must widen their message_data column + to store items larger than the existing column's limit. + """ _table_init_locks: ClassVar[dict[tuple[str, str, str], threading.Lock]] = {} _table_init_locks_guard: ClassVar[threading.Lock] = threading.Lock() @@ -215,7 +221,11 @@ def __init__( ForeignKey(f"{sessions_table}.session_id", ondelete="CASCADE"), nullable=False, ), - Column("message_data", Text, nullable=False), + Column( + "message_data", + Text().with_variant(LONGTEXT(), "mysql", "mariadb"), + nullable=False, + ), Column( "created_at", TIMESTAMP(timezone=False), diff --git a/tests/extensions/memory/test_sqlalchemy_session.py b/tests/extensions/memory/test_sqlalchemy_session.py index b985d0a7e9..7587184e91 100644 --- a/tests/extensions/memory/test_sqlalchemy_session.py +++ b/tests/extensions/memory/test_sqlalchemy_session.py @@ -18,7 +18,11 @@ Summary, ) from sqlalchemy import event, insert, select, text, update +from sqlalchemy.dialects import mysql, postgresql, sqlite +from sqlalchemy.dialects.mysql.mariadb import MariaDBDialect +from sqlalchemy.engine import Dialect from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine +from sqlalchemy.schema import CreateColumn from sqlalchemy.sql import Select pytest.importorskip("sqlalchemy") # Skip tests if SQLAlchemy is not installed @@ -108,6 +112,27 @@ async def test_sqlalchemy_session_direct_ops(agent: Agent): assert len(retrieved_after_clear) == 0 +@pytest.mark.parametrize( + ("dialect", "expected_type"), + [ + pytest.param(mysql.dialect(), "LONGTEXT", id="mysql"), + pytest.param(MariaDBDialect(), "LONGTEXT", id="mariadb"), + pytest.param(postgresql.dialect(), "TEXT", id="postgresql"), + pytest.param(sqlite.dialect(), "TEXT", id="sqlite"), + ], +) +async def test_message_data_column_type(dialect: Dialect, expected_type: str): + """MySQL needs large text storage; other dialects retain the existing TEXT column.""" + session = SQLAlchemySession.from_url("message_column", url=DB_URL) + try: + column = session._messages.c.message_data + assert str(CreateColumn(column).compile(dialect=dialect)) == ( + f"message_data {expected_type} NOT NULL" + ) + finally: + await session.engine.dispose() + + async def test_sqlalchemy_session_defaults_to_escaped_non_ascii_storage(): """Default storage keeps the historical escaped non-ASCII JSON representation.""" session = SQLAlchemySession.from_url("default_ascii_storage", url=DB_URL, create_tables=True)