feat: make agent_id optional in conversations list endpoint [LET-7612] (#9585)

* feat: make agent_id optional in conversations list endpoint [LET-7612]

Allow listing all conversations without filtering by agent_id.

**Router changes (conversations.py):**
- Changed agent_id from required (`...`) to optional (`None`)
- Updated description to clarify behavior
- Updated docstring to reflect optional filtering

**Manager changes (conversation_manager.py):**
- Updated list_conversations signature: agent_id: str → Optional[str]
- Updated docstring to clarify optional behavior
- Summary search query: conditionally adds agent_id filter only if provided
- Default list logic: passes agent_id (can be None) to list_async

**How it works:**
- Without agent_id: returns all conversations for the user's organization
- With agent_id: returns conversations filtered by that agent
- list_async handles None gracefully via **kwargs pattern

**Use case:**
- Cloud UI can list all user conversations across agents
- Still supports filtering by agent_id when needed

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: update logs

* chore: update logs

---------

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Shubham Naik
2026-02-20 12:29:21 -08:00
committed by Caren Thomas
parent 257b99923b
commit 73c824f5d2
3 changed files with 29 additions and 18 deletions

View File

@@ -8718,19 +8718,26 @@
"get": {
"tags": ["conversations"],
"summary": "List Conversations",
"description": "List all conversations for an agent.",
"description": "List all conversations for an agent (or all conversations if agent_id not provided).",
"operationId": "list_conversations",
"parameters": [
{
"name": "agent_id",
"in": "query",
"required": true,
"required": false,
"schema": {
"type": "string",
"description": "The agent ID to list conversations for",
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The agent ID to list conversations for (optional - returns all conversations if not provided)",
"title": "Agent Id"
},
"description": "The agent ID to list conversations for"
"description": "The agent ID to list conversations for (optional - returns all conversations if not provided)"
},
{
"name": "limit",

View File

@@ -60,14 +60,14 @@ async def create_conversation(
@router.get("/", response_model=List[Conversation], operation_id="list_conversations")
async def list_conversations(
agent_id: str = Query(..., description="The agent ID to list conversations for"),
agent_id: Optional[str] = Query(None, description="The agent ID to list conversations for (optional - returns all conversations if not provided)"),
limit: int = Query(50, description="Maximum number of conversations to return"),
after: Optional[str] = Query(None, description="Cursor for pagination (conversation ID)"),
summary_search: Optional[str] = Query(None, description="Search for text within conversation summaries"),
server: SyncServer = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""List all conversations for an agent."""
"""List all conversations for an agent (or all conversations if agent_id not provided)."""
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
return await conversation_manager.list_conversations(
agent_id=agent_id,

View File

@@ -103,16 +103,16 @@ class ConversationManager:
@trace_method
async def list_conversations(
self,
agent_id: str,
agent_id: Optional[str],
actor: PydanticUser,
limit: int = 50,
after: Optional[str] = None,
summary_search: Optional[str] = None,
) -> List[PydanticConversation]:
"""List conversations for an agent with cursor-based pagination.
"""List conversations for an agent (or all conversations) with cursor-based pagination.
Args:
agent_id: The agent ID to list conversations for
agent_id: The agent ID to list conversations for (optional - returns all if not provided)
actor: The user performing the action
limit: Maximum number of conversations to return
after: Cursor for pagination (conversation ID)
@@ -126,16 +126,20 @@ class ConversationManager:
if summary_search:
from sqlalchemy import and_
# Build where conditions
conditions = [
ConversationModel.organization_id == actor.organization_id,
ConversationModel.summary.isnot(None),
ConversationModel.summary.contains(summary_search),
]
# Add agent_id filter if provided
if agent_id is not None:
conditions.append(ConversationModel.agent_id == agent_id)
stmt = (
select(ConversationModel)
.where(
and_(
ConversationModel.agent_id == agent_id,
ConversationModel.organization_id == actor.organization_id,
ConversationModel.summary.isnot(None),
ConversationModel.summary.contains(summary_search),
)
)
.where(and_(*conditions))
.order_by(ConversationModel.created_at.desc())
.limit(limit)
)