feat: add pagination to agent tools endpoint (#5084)

* feat: add pagination to agent tools endpoint

* chore: regenerate API client with pagination parameters

* feat: implement pagination logic in list_agent_tools endpoint

- Update list_attached_tools_async method to support pagination parameters (before, after, limit, ascending)
- Update list_agent_tools route to pass pagination parameters to underlying service method
- Add proper cursor-based pagination and sorting in SQL query

* feat: update default limit for list_agent_tools to 10

* feat: regenerate API client after limit parameter change
This commit is contained in:
cthomas
2025-10-02 11:06:20 -07:00
committed by Caren Thomas
parent cb5e89cfa2
commit 874ac0dd5c
3 changed files with 129 additions and 2 deletions

View File

@@ -4197,6 +4197,87 @@
"type": "string",
"title": "Agent Id"
}
},
{
"name": "before",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Tool ID cursor for pagination. Returns tools that come before this tool ID in the specified sort order",
"title": "Before"
},
"description": "Tool ID cursor for pagination. Returns tools that come before this tool ID in the specified sort order"
},
{
"name": "after",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Tool ID cursor for pagination. Returns tools that come after this tool ID in the specified sort order",
"title": "After"
},
"description": "Tool ID cursor for pagination. Returns tools that come after this tool ID in the specified sort order"
},
{
"name": "limit",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"description": "Maximum number of tools to return",
"default": 10,
"title": "Limit"
},
"description": "Maximum number of tools to return"
},
{
"name": "order",
"in": "query",
"required": false,
"schema": {
"enum": ["asc", "desc"],
"type": "string",
"description": "Sort order for tools by creation time. 'asc' for oldest first, 'desc' for newest first",
"default": "desc",
"title": "Order"
},
"description": "Sort order for tools by creation time. 'asc' for oldest first, 'desc' for newest first"
},
{
"name": "order_by",
"in": "query",
"required": false,
"schema": {
"const": "created_at",
"type": "string",
"description": "Field to sort by",
"default": "created_at",
"title": "Order By"
},
"description": "Field to sort by"
}
],
"responses": {

View File

@@ -460,10 +460,28 @@ async def list_agent_tools(
agent_id: str,
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
before: Optional[str] = Query(
None, description="Tool ID cursor for pagination. Returns tools that come before this tool ID in the specified sort order"
),
after: Optional[str] = Query(
None, description="Tool ID cursor for pagination. Returns tools that come after this tool ID in the specified sort order"
),
limit: Optional[int] = Query(10, description="Maximum number of tools to return"),
order: Literal["asc", "desc"] = Query(
"desc", description="Sort order for tools by creation time. 'asc' for oldest first, 'desc' for newest first"
),
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
):
"""Get tools from an existing agent"""
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
return await server.agent_manager.list_attached_tools_async(agent_id=agent_id, actor=actor)
return await server.agent_manager.list_attached_tools_async(
agent_id=agent_id,
actor=actor,
before=before,
after=after,
limit=limit,
ascending=(order == "asc"),
)
@router.patch("/{agent_id}/tools/attach/{tool_id}", response_model=AgentState, operation_id="attach_tool")

View File

@@ -2579,7 +2579,15 @@ class AgentManager:
@enforce_types
@trace_method
async def list_attached_tools_async(self, agent_id: str, actor: PydanticUser) -> List[PydanticTool]:
async def list_attached_tools_async(
self,
agent_id: str,
actor: PydanticUser,
before: Optional[str] = None,
after: Optional[str] = None,
limit: Optional[int] = None,
ascending: bool = False,
) -> List[PydanticTool]:
"""
List all tools attached to an agent (async version with optimized performance).
Uses direct SQL queries to avoid SqlAlchemyBase overhead.
@@ -2587,6 +2595,10 @@ class AgentManager:
Args:
agent_id: ID of the agent to list tools for.
actor: User performing the action.
before: Tool ID cursor for pagination. Returns tools that come before this tool ID.
after: Tool ID cursor for pagination. Returns tools that come after this tool ID.
limit: Maximum number of tools to return.
ascending: Sort order by creation time.
Returns:
List[PydanticTool]: List of tools attached to the agent.
@@ -2602,6 +2614,22 @@ class AgentManager:
.where(ToolsAgents.agent_id == agent_id, ToolModel.organization_id == actor.organization_id)
)
# Apply cursor-based pagination
if before:
query = query.where(ToolModel.id < before)
if after:
query = query.where(ToolModel.id > after)
# Apply sorting
if ascending:
query = query.order_by(ToolModel.created_at.asc())
else:
query = query.order_by(ToolModel.created_at.desc())
# Apply limit
if limit:
query = query.limit(limit)
result = await session.execute(query)
tools = result.scalars().all()
return [tool.to_pydantic() for tool in tools]