feat: query param parity for conversation messages (#8730)

* base

* add tests

* generate
This commit is contained in:
jnjpng
2026-01-14 17:44:48 -08:00
committed by Sarah Wooders
parent 9aac2abdfe
commit 037c20ae1b
4 changed files with 490 additions and 9 deletions

View File

@@ -396,3 +396,172 @@ class TestConversationsSDK:
)
)
assert len(messages) > 0, "Should be able to send message after concurrent requests complete"
def test_list_conversation_messages_order_asc(self, client: Letta, agent):
"""Test listing messages in ascending order (oldest first)."""
conversation = client.conversations.create(agent_id=agent.id)
# Send messages to create history
list(
client.conversations.messages.create(
conversation_id=conversation.id,
messages=[{"role": "user", "content": "First message"}],
)
)
list(
client.conversations.messages.create(
conversation_id=conversation.id,
messages=[{"role": "user", "content": "Second message"}],
)
)
# List messages in ascending order (oldest first)
messages_asc = client.conversations.messages.list(
conversation_id=conversation.id,
order="asc",
)
# First message should be system message (oldest)
assert messages_asc[0].message_type == "system_message"
# Get user messages and verify order
user_messages = [m for m in messages_asc if m.message_type == "user_message"]
assert len(user_messages) >= 2
# First user message should contain "First message"
assert "First" in user_messages[0].content
def test_list_conversation_messages_order_desc(self, client: Letta, agent):
"""Test listing messages in descending order (newest first)."""
conversation = client.conversations.create(agent_id=agent.id)
# Send messages to create history
list(
client.conversations.messages.create(
conversation_id=conversation.id,
messages=[{"role": "user", "content": "First message"}],
)
)
list(
client.conversations.messages.create(
conversation_id=conversation.id,
messages=[{"role": "user", "content": "Second message"}],
)
)
# List messages in descending order (newest first) - this is the default
messages_desc = client.conversations.messages.list(
conversation_id=conversation.id,
order="desc",
)
# Get user messages and verify order
user_messages = [m for m in messages_desc if m.message_type == "user_message"]
assert len(user_messages) >= 2
# First user message in desc order should contain "Second message" (newest)
assert "Second" in user_messages[0].content
def test_list_conversation_messages_order_affects_pagination(self, client: Letta, agent):
"""Test that order parameter affects pagination correctly."""
conversation = client.conversations.create(agent_id=agent.id)
# Send multiple messages
for i in range(3):
list(
client.conversations.messages.create(
conversation_id=conversation.id,
messages=[{"role": "user", "content": f"Message {i}"}],
)
)
# Get all messages in descending order with limit
messages_desc = client.conversations.messages.list(
conversation_id=conversation.id,
order="desc",
limit=5,
)
# Get all messages in ascending order with limit
messages_asc = client.conversations.messages.list(
conversation_id=conversation.id,
order="asc",
limit=5,
)
# The first messages should be different based on order
assert messages_desc[0].id != messages_asc[0].id
def test_list_conversation_messages_with_before_cursor(self, client: Letta, agent):
"""Test pagination with before cursor."""
conversation = client.conversations.create(agent_id=agent.id)
# Send messages to create history
list(
client.conversations.messages.create(
conversation_id=conversation.id,
messages=[{"role": "user", "content": "First message"}],
)
)
list(
client.conversations.messages.create(
conversation_id=conversation.id,
messages=[{"role": "user", "content": "Second message"}],
)
)
# Get all messages first
all_messages = client.conversations.messages.list(
conversation_id=conversation.id,
order="asc",
)
assert len(all_messages) >= 4 # system + user + assistant + user + assistant
# Use the last message ID as cursor
last_message_id = all_messages[-1].id
messages_before = client.conversations.messages.list(
conversation_id=conversation.id,
order="asc",
before=last_message_id,
)
# Should have fewer messages (all except the last one)
assert len(messages_before) < len(all_messages)
# Should not contain the cursor message
assert last_message_id not in [m.id for m in messages_before]
def test_list_conversation_messages_with_after_cursor(self, client: Letta, agent):
"""Test pagination with after cursor."""
conversation = client.conversations.create(agent_id=agent.id)
# Send messages to create history
list(
client.conversations.messages.create(
conversation_id=conversation.id,
messages=[{"role": "user", "content": "First message"}],
)
)
list(
client.conversations.messages.create(
conversation_id=conversation.id,
messages=[{"role": "user", "content": "Second message"}],
)
)
# Get all messages first
all_messages = client.conversations.messages.list(
conversation_id=conversation.id,
order="asc",
)
assert len(all_messages) >= 4
# Use the first message ID as cursor
first_message_id = all_messages[0].id
messages_after = client.conversations.messages.list(
conversation_id=conversation.id,
order="asc",
after=first_message_id,
)
# Should have fewer messages (all except the first one)
assert len(messages_after) < len(all_messages)
# Should not contain the cursor message
assert first_message_id not in [m.id for m in messages_after]

View File

@@ -749,3 +749,291 @@ async def test_delete_conversation_cleans_up_isolated_blocks(conversation_manage
# Verify the isolated block was hard-deleted
deleted_block = await server.block_manager.get_block_by_id_async(isolated_block_id, default_user)
assert deleted_block is None
# ======================================================================================================================
# list_conversation_messages with order/reverse Tests
# ======================================================================================================================
@pytest.mark.asyncio
async def test_list_conversation_messages_ascending_order(conversation_manager, server: SyncServer, sarah_agent, default_user):
"""Test listing messages in ascending order (oldest first)."""
from letta.schemas.letta_message_content import TextContent
from letta.schemas.message import Message as PydanticMessage
# Create a conversation
conversation = await conversation_manager.create_conversation(
agent_id=sarah_agent.id,
conversation_create=CreateConversation(summary="Test"),
actor=default_user,
)
# Create messages in a known order
pydantic_messages = [
PydanticMessage(
agent_id=sarah_agent.id,
role="user",
content=[TextContent(text=f"Message {i}")],
)
for i in range(3)
]
messages = await server.message_manager.create_many_messages_async(
pydantic_messages,
actor=default_user,
)
# Add messages to conversation
await conversation_manager.add_messages_to_conversation(
conversation_id=conversation.id,
agent_id=sarah_agent.id,
message_ids=[m.id for m in messages],
actor=default_user,
)
# List messages in ascending order (reverse=False)
letta_messages = await conversation_manager.list_conversation_messages(
conversation_id=conversation.id,
actor=default_user,
reverse=False,
)
# First message should be "Message 0" (oldest)
assert len(letta_messages) == 3
assert "Message 0" in letta_messages[0].content
@pytest.mark.asyncio
async def test_list_conversation_messages_descending_order(conversation_manager, server: SyncServer, sarah_agent, default_user):
"""Test listing messages in descending order (newest first)."""
from letta.schemas.letta_message_content import TextContent
from letta.schemas.message import Message as PydanticMessage
# Create a conversation
conversation = await conversation_manager.create_conversation(
agent_id=sarah_agent.id,
conversation_create=CreateConversation(summary="Test"),
actor=default_user,
)
# Create messages in a known order
pydantic_messages = [
PydanticMessage(
agent_id=sarah_agent.id,
role="user",
content=[TextContent(text=f"Message {i}")],
)
for i in range(3)
]
messages = await server.message_manager.create_many_messages_async(
pydantic_messages,
actor=default_user,
)
# Add messages to conversation
await conversation_manager.add_messages_to_conversation(
conversation_id=conversation.id,
agent_id=sarah_agent.id,
message_ids=[m.id for m in messages],
actor=default_user,
)
# List messages in descending order (reverse=True)
letta_messages = await conversation_manager.list_conversation_messages(
conversation_id=conversation.id,
actor=default_user,
reverse=True,
)
# First message should be "Message 2" (newest)
assert len(letta_messages) == 3
assert "Message 2" in letta_messages[0].content
@pytest.mark.asyncio
async def test_list_conversation_messages_with_group_id_filter(conversation_manager, server: SyncServer, sarah_agent, default_user):
"""Test filtering messages by group_id."""
from letta.schemas.letta_message_content import TextContent
from letta.schemas.message import Message as PydanticMessage
# Create a conversation
conversation = await conversation_manager.create_conversation(
agent_id=sarah_agent.id,
conversation_create=CreateConversation(summary="Test"),
actor=default_user,
)
# Create messages with different group_ids
group_a_id = "group-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
group_b_id = "group-bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
messages_group_a = [
PydanticMessage(
agent_id=sarah_agent.id,
role="user",
content=[TextContent(text="Group A message 1")],
group_id=group_a_id,
),
PydanticMessage(
agent_id=sarah_agent.id,
role="user",
content=[TextContent(text="Group A message 2")],
group_id=group_a_id,
),
]
messages_group_b = [
PydanticMessage(
agent_id=sarah_agent.id,
role="user",
content=[TextContent(text="Group B message 1")],
group_id=group_b_id,
),
]
created_a = await server.message_manager.create_many_messages_async(messages_group_a, actor=default_user)
created_b = await server.message_manager.create_many_messages_async(messages_group_b, actor=default_user)
# Add all messages to conversation
all_message_ids = [m.id for m in created_a] + [m.id for m in created_b]
await conversation_manager.add_messages_to_conversation(
conversation_id=conversation.id,
agent_id=sarah_agent.id,
message_ids=all_message_ids,
actor=default_user,
)
# List messages filtered by group A
messages_a = await conversation_manager.list_conversation_messages(
conversation_id=conversation.id,
actor=default_user,
group_id=group_a_id,
)
assert len(messages_a) == 2
for msg in messages_a:
assert "Group A" in msg.content
# List messages filtered by group B
messages_b = await conversation_manager.list_conversation_messages(
conversation_id=conversation.id,
actor=default_user,
group_id=group_b_id,
)
assert len(messages_b) == 1
assert "Group B" in messages_b[0].content
@pytest.mark.asyncio
async def test_list_conversation_messages_no_group_id_returns_all(conversation_manager, server: SyncServer, sarah_agent, default_user):
"""Test that not providing group_id returns all messages."""
from letta.schemas.letta_message_content import TextContent
from letta.schemas.message import Message as PydanticMessage
# Create a conversation
conversation = await conversation_manager.create_conversation(
agent_id=sarah_agent.id,
conversation_create=CreateConversation(summary="Test"),
actor=default_user,
)
# Create messages with different group_ids
group_a_id = "group-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
group_b_id = "group-bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
pydantic_messages = [
PydanticMessage(
agent_id=sarah_agent.id,
role="user",
content=[TextContent(text="Group A message")],
group_id=group_a_id,
),
PydanticMessage(
agent_id=sarah_agent.id,
role="user",
content=[TextContent(text="Group B message")],
group_id=group_b_id,
),
PydanticMessage(
agent_id=sarah_agent.id,
role="user",
content=[TextContent(text="No group message")],
group_id=None,
),
]
messages = await server.message_manager.create_many_messages_async(pydantic_messages, actor=default_user)
# Add all messages to conversation
await conversation_manager.add_messages_to_conversation(
conversation_id=conversation.id,
agent_id=sarah_agent.id,
message_ids=[m.id for m in messages],
actor=default_user,
)
# List all messages without group_id filter
all_messages = await conversation_manager.list_conversation_messages(
conversation_id=conversation.id,
actor=default_user,
)
assert len(all_messages) == 3
@pytest.mark.asyncio
async def test_list_conversation_messages_order_with_pagination(conversation_manager, server: SyncServer, sarah_agent, default_user):
"""Test that order affects pagination correctly."""
from letta.schemas.letta_message_content import TextContent
from letta.schemas.message import Message as PydanticMessage
# Create a conversation
conversation = await conversation_manager.create_conversation(
agent_id=sarah_agent.id,
conversation_create=CreateConversation(summary="Test"),
actor=default_user,
)
# Create messages
pydantic_messages = [
PydanticMessage(
agent_id=sarah_agent.id,
role="user",
content=[TextContent(text=f"Message {i}")],
)
for i in range(5)
]
messages = await server.message_manager.create_many_messages_async(
pydantic_messages,
actor=default_user,
)
# Add messages to conversation
await conversation_manager.add_messages_to_conversation(
conversation_id=conversation.id,
agent_id=sarah_agent.id,
message_ids=[m.id for m in messages],
actor=default_user,
)
# Get first page in ascending order with limit
page_asc = await conversation_manager.list_conversation_messages(
conversation_id=conversation.id,
actor=default_user,
reverse=False,
limit=2,
)
# Get first page in descending order with limit
page_desc = await conversation_manager.list_conversation_messages(
conversation_id=conversation.id,
actor=default_user,
reverse=True,
limit=2,
)
# The first messages should be different
assert page_asc[0].content != page_desc[0].content
# In ascending, first should be "Message 0"
assert "Message 0" in page_asc[0].content
# In descending, first should be "Message 4"
assert "Message 4" in page_desc[0].content