Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(platform) Implement redesigned agent list #9133

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 125 additions & 2 deletions autogpt_platform/backend/backend/server/v2/library/db.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from typing import List
import typing

import prisma.errors
import prisma.models
Expand All @@ -15,17 +16,21 @@

async def get_library_agents(
user_id: str,
limit: int = 20,
offset: int = 0,
) -> List[backend.server.v2.library.model.LibraryAgent]:
"""
Returns all agents (AgentGraph) that belong to the user and all agents in their library (UserAgent table)
Returns paginated agents (AgentGraph) that belong to the user and agents in their library (UserAgent table)
"""
logger.debug(f"Getting library agents for user {user_id}")
logger.debug(f"Getting library agents for user {user_id} with limit {limit} offset {offset}")

try:
# Get agents created by user with nodes and links
user_created = await prisma.models.AgentGraph.prisma().find_many(
where=prisma.types.AgentGraphWhereInput(userId=user_id, isActive=True),
include=backend.data.includes.AGENT_GRAPH_INCLUDE,
skip=offset,
take=limit,
)

# Get agents in user's library with nodes and links
Expand All @@ -47,6 +52,8 @@ async def get_library_agents(
}
}
},
skip=offset,
take=limit,
)

# Convert to Graph models first
Expand Down Expand Up @@ -94,6 +101,122 @@ async def get_library_agents(
"Failed to fetch library agents"
) from e

async def search_library_agents(
user_id: str,
search_term: str,
sort_by: typing.Literal["most_recent", "highest_runtime", "most_runs", "alphabetical", "last_modified"],
limit: int = 20,
offset: int = 0,
) -> List[backend.server.v2.library.model.LibraryAgent]:
"""
Searches paginated agents (AgentGraph) that belong to the user and agents in their library (UserAgent table)
based on name or description containing the search term
"""
logger.debug(f"Searching library agents for user {user_id} with term '{search_term}', limit {limit} offset {offset}")

try:
# Get sort field
sort_order = "desc" if sort_by in ["most_recent", "highest_runtime", "most_runs", "last_modified"] else "asc"

sort_field = {
"most_recent": "createdAt",
"last_modified": "updatedAt",
"highest_runtime": "totalRuntime",
"most_runs": "runCount",
"alphabetical": "name"
}.get(sort_by, "updatedAt")

# Get user created agents matching search
user_created = await prisma.models.AgentGraph.prisma().find_many(
where=prisma.types.AgentGraphWhereInput(
userId=user_id,
isActive=True,
OR=[
{"name": {"contains": search_term, "mode": "insensitive"}},
{"description": {"contains": search_term, "mode": "insensitive"}}
]
),
include=backend.data.includes.AGENT_GRAPH_INCLUDE,
order={sort_field: sort_order},
skip=offset,
take=limit,
)

# Get library agents matching search
library_agents = await prisma.models.UserAgent.prisma().find_many(
where=prisma.types.UserAgentWhereInput(
userId=user_id,
isDeleted=False,
isArchived=False,
Agent={
"is": {
"OR": [
{"name": {"contains": search_term, "mode": "insensitive"}},
{"description": {"contains": search_term, "mode": "insensitive"}}
]
}
}
),
include={
"Agent": {
"include": {
"AgentNodes": {
"include": {
"Input": True,
"Output": True,
"Webhook": True,
"AgentBlock": True,
}
}
}
}
},
skip=offset,
take=limit,
)

# Convert to Graph models
graphs = []

for agent in user_created:
try:
graphs.append(backend.data.graph.GraphModel.from_db(agent))
except Exception as e:
logger.error(f"Error processing searched user agent {agent.id}: {e}")
continue

for agent in library_agents:
if agent.Agent:
try:
graphs.append(backend.data.graph.GraphModel.from_db(agent.Agent))
except Exception as e:
logger.error(f"Error processing searched library agent {agent.agentId}: {e}")
continue

# Convert to LibraryAgent models
result = []
for graph in graphs:
result.append(
backend.server.v2.library.model.LibraryAgent(
id=graph.id,
version=graph.version,
is_active=graph.is_active,
name=graph.name,
description=graph.description,
isCreatedByUser=any(a.id == graph.id for a in user_created),
input_schema=graph.input_schema,
output_schema=graph.output_schema,
)
)

logger.debug(f"Found {len(result)} library agents matching search")
return result

except prisma.errors.PrismaError as e:
logger.error(f"Database error searching library agents: {str(e)}")
raise backend.server.v2.store.exceptions.DatabaseError(
"Failed to search library agents"
) from e

async def add_agent_to_library(store_listing_version_id: str, user_id: str) -> None:
"""
Expand Down
174 changes: 125 additions & 49 deletions autogpt_platform/backend/backend/server/v2/library/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import autogpt_libs.auth.depends
import autogpt_libs.auth.middleware
import fastapi
import prisma

import backend.data.graph
import backend.integrations.creds_manager
Expand All @@ -28,21 +27,96 @@
async def get_library_agents(
user_id: typing.Annotated[
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
]
) -> typing.Sequence[backend.server.v2.library.model.LibraryAgent]:
],
pagination_token: str | None = fastapi.Query(None)
) -> dict[str, typing.Any]:
"""
Get all agents in the user's library, including both created and saved agents.
Get agents in the user's library with pagination (20 agents per page).

Args:
user_id: ID of the authenticated user
pagination_token: Token to get next page of results

Returns:
Dictionary containing:
- agents: List of agents for current page
- next_token: Token to get next page (None if no more pages)
"""
try:
agents = await backend.server.v2.library.db.get_library_agents(user_id)
return agents
page_size = 20
agents = await backend.server.v2.library.db.get_library_agents(
user_id,
limit=page_size + 1,
offset=int(pagination_token) if pagination_token else 0
)

has_more = len(agents) > page_size
agents = agents[:page_size]
next_token = str(int(pagination_token or 0) + page_size) if has_more else None

return {
"agents": agents,
"next_token": next_token
}
except Exception:
logger.exception("Exception occurred whilst getting library agents")
raise fastapi.HTTPException(
status_code=500, detail="Failed to get library agents"
)


# For searching and filtering the library agents
@router.get(
"/agents/search",
tags=["library", "private"],
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
)
async def search_library_agents(
user_id: typing.Annotated[
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
],
search_term: str = fastapi.Query(..., description="Search term to filter agents"),
sort_by: typing.Literal["most_recent", "highest_runtime", "most_runs", "alphabetical", "last_modified"] = fastapi.Query("most_recent", description="Sort results by criteria"),
pagination_token: str | None = fastapi.Query(None)
) -> dict[str, typing.Any]:
"""
Search for agents in the user's library with pagination (20 agents per page).

Args:
user_id: ID of the authenticated user
search_term: Term to search for in agent names/descriptions
sort_by: How to sort results (most_recent, highest_runtime, most_runs, alphabetical, last_modified)
pagination_token: Token to get next page of results

Returns:
Dictionary containing:
- agents: List of matching agents for current page
- next_token: Token to get next page (None if no more pages)
"""
try:
page_size = 20
agents = await backend.server.v2.library.db.search_library_agents(
user_id,
search_term,
sort_by=sort_by,
limit=page_size + 1,
offset=int(pagination_token) if pagination_token else 0
)

has_more = len(agents) > page_size
agents = agents[:page_size]
next_token = str(int(pagination_token or 0) + page_size) if has_more else None

return {
"agents": agents,
"next_token": next_token
}
except Exception:
logger.exception("Exception occurred whilst searching library agents")
raise fastapi.HTTPException(
status_code=500, detail="Failed to search library agents"
)

@router.post(
"/agents/{store_listing_version_id}",
tags=["library", "private"],
Expand Down Expand Up @@ -70,49 +144,51 @@ async def add_agent_to_library(
"""
try:
# Get the graph from the store listing
store_listing_version = (
await prisma.models.StoreListingVersion.prisma().find_unique(
where={"id": store_listing_version_id}, include={"Agent": True}
)
)

if not store_listing_version or not store_listing_version.Agent:
raise fastapi.HTTPException(
status_code=404,
detail=f"Store listing version {store_listing_version_id} not found",
)

agent = store_listing_version.Agent

if agent.userId == user_id:
raise fastapi.HTTPException(
status_code=400, detail="Cannot add own agent to library"
)

# Create a new graph from the template
graph = await backend.data.graph.get_graph(
agent.id, agent.version, template=True, user_id=user_id
)

if not graph:
raise fastapi.HTTPException(
status_code=404, detail=f"Agent {agent.id} not found"
)

# Create a deep copy with new IDs
graph.version = 1
graph.is_template = False
graph.is_active = True
graph.reassign_ids(user_id=user_id, reassign_graph_id=True)

# Save the new graph
graph = await backend.data.graph.create_graph(graph, user_id=user_id)
graph = (
await backend.integrations.webhooks.graph_lifecycle_hooks.on_graph_activate(
graph,
get_credentials=lambda id: integration_creds_manager.get(user_id, id),
)
)
# store_listing_version = (
# await prisma.models.StoreListingVersion.prisma().find_unique(
# where={"id": store_listing_version_id}, include={"Agent": True}
# )
# )

# if not store_listing_version or not store_listing_version.Agent:
# raise fastapi.HTTPException(
# status_code=404,
# detail=f"Store listing version {store_listing_version_id} not found",
# )

# agent = store_listing_version.Agent

# if agent.userId == user_id:
# raise fastapi.HTTPException(
# status_code=400, detail="Cannot add own agent to library"
# )

# # Create a new graph from the template
# graph = await backend.data.graph.get_graph(
# agent.id, agent.version, template=True, user_id=user_id
# )

# if not graph:
# raise fastapi.HTTPException(
# status_code=404, detail=f"Agent {agent.id} not found"
# )

# # Create a deep copy with new IDs
# graph.version = 1
# graph.is_template = False
# graph.is_active = True
# graph.reassign_ids(user_id=user_id, reassign_graph_id=True)

# # Save the new graph
# graph = await backend.data.graph.create_graph(graph, user_id=user_id)
# graph = (
# await backend.integrations.webhooks.graph_lifecycle_hooks.on_graph_activate(
# graph,
# get_credentials=lambda id: integration_creds_manager.get(user_id, id),
# )
# )

await backend.server.v2.library.db.add_agent_to_library(store_listing_version_id=store_listing_version_id,user_id=user_id)

return fastapi.Response(status_code=201)

Expand Down
1 change: 1 addition & 0 deletions autogpt_platform/frontend/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const nextConfig = {
typescript: {
ignoreBuildErrors: true,
},
transpilePackages: ["geist"],
};

export default withSentryConfig(nextConfig, {
Expand Down
3 changes: 3 additions & 0 deletions autogpt_platform/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@
"framer-motion": "^11.15.0",
"geist": "^1.3.1",
"launchdarkly-react-client-sdk": "^3.6.0",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.468.0",
"moment": "^2.30.1",
"next": "^14.2.13",
"next-themes": "^0.4.4",
"react": "^18",
"react-day-picker": "^9.4.4",
"react-dom": "^18",
"react-drag-drop-files": "^2.4.0",
"react-hook-form": "^7.54.0",
"react-icons": "^5.4.0",
"react-markdown": "^9.0.1",
Expand All @@ -93,6 +95,7 @@
"@storybook/react": "^8.3.5",
"@storybook/test": "^8.3.5",
"@storybook/test-runner": "^0.20.1",
"@types/lodash": "^4.17.13",
"@types/negotiator": "^0.6.3",
"@types/node": "^22.9.0",
"@types/react": "^18",
Expand Down
Loading
Loading