diff --git a/autogpt_platform/backend/.gitignore b/autogpt_platform/backend/.gitignore index f707d272b7..95b59cf676 100644 --- a/autogpt_platform/backend/.gitignore +++ b/autogpt_platform/backend/.gitignore @@ -17,4 +17,4 @@ load-tests/*_REPORT.md load-tests/results/ load-tests/*.json load-tests/*.log -load-tests/node_modules/* \ No newline at end of file +load-tests/node_modules/* diff --git a/autogpt_platform/backend/backend/server/v2/store/routes.py b/autogpt_platform/backend/backend/server/v2/store/routes.py index 770ebad6e6..f39482122e 100644 --- a/autogpt_platform/backend/backend/server/v2/store/routes.py +++ b/autogpt_platform/backend/backend/server/v2/store/routes.py @@ -6,6 +6,7 @@ import urllib.parse import autogpt_libs.auth import fastapi import fastapi.responses +from autogpt_libs.utils.cache import cached import backend.data.graph import backend.server.v2.store.db @@ -20,6 +21,117 @@ logger = logging.getLogger(__name__) router = fastapi.APIRouter() +############################################## +############### Caches ####################### +############################################## + + +# Cache user profiles for 1 hour per user +@cached(maxsize=1000, ttl_seconds=3600) +async def _get_cached_user_profile(user_id: str): + """Cached helper to get user profile.""" + return await backend.server.v2.store.db.get_user_profile(user_id) + + +# Cache store agents list for 15 minutes +# Different cache entries for different query combinations +@cached(maxsize=5000, ttl_seconds=900) +async def _get_cached_store_agents( + featured: bool, + creator: str | None, + sorted_by: str | None, + search_query: str | None, + category: str | None, + page: int, + page_size: int, +): + """Cached helper to get store agents.""" + return await backend.server.v2.store.db.get_store_agents( + featured=featured, + creators=[creator] if creator else None, + sorted_by=sorted_by, + search_query=search_query, + category=category, + page=page, + page_size=page_size, + ) + + +# Cache individual agent details for 15 minutes +@cached(maxsize=200, ttl_seconds=900) +async def _get_cached_agent_details(username: str, agent_name: str): + """Cached helper to get agent details.""" + return await backend.server.v2.store.db.get_store_agent_details( + username=username, agent_name=agent_name + ) + + +# Cache agent graphs for 1 hour +@cached(maxsize=200, ttl_seconds=3600) +async def _get_cached_agent_graph(store_listing_version_id: str): + """Cached helper to get agent graph.""" + return await backend.server.v2.store.db.get_available_graph( + store_listing_version_id + ) + + +# Cache agent by version for 1 hour +@cached(maxsize=200, ttl_seconds=3600) +async def _get_cached_store_agent_by_version(store_listing_version_id: str): + """Cached helper to get store agent by version ID.""" + return await backend.server.v2.store.db.get_store_agent_by_version_id( + store_listing_version_id + ) + + +# Cache creators list for 1 hour +@cached(maxsize=200, ttl_seconds=3600) +async def _get_cached_store_creators( + featured: bool, + search_query: str | None, + sorted_by: str | None, + page: int, + page_size: int, +): + """Cached helper to get store creators.""" + return await backend.server.v2.store.db.get_store_creators( + featured=featured, + search_query=search_query, + sorted_by=sorted_by, + page=page, + page_size=page_size, + ) + + +# Cache individual creator details for 1 hour +@cached(maxsize=100, ttl_seconds=3600) +async def _get_cached_creator_details(username: str): + """Cached helper to get creator details.""" + return await backend.server.v2.store.db.get_store_creator_details( + username=username.lower() + ) + + +# Cache user's own agents for 5 mins (shorter TTL as this changes more frequently) +@cached(maxsize=500, ttl_seconds=300) +async def _get_cached_my_agents(user_id: str, page: int, page_size: int): + """Cached helper to get user's agents.""" + return await backend.server.v2.store.db.get_my_agents( + user_id, page=page, page_size=page_size + ) + + +# Cache user's submissions for 1 hour (shorter TTL as this changes frequently) +@cached(maxsize=500, ttl_seconds=3600) +async def _get_cached_submissions(user_id: str, page: int, page_size: int): + """Cached helper to get user's submissions.""" + return await backend.server.v2.store.db.get_store_submissions( + user_id=user_id, + page=page, + page_size=page_size, + ) + + ############################################## ############### Profile Endpoints ############ ############################################## @@ -37,9 +149,10 @@ async def get_profile( ): """ Get the profile details for the authenticated user. + Cached for 1 hour per user. """ try: - profile = await backend.server.v2.store.db.get_user_profile(user_id) + profile = await _get_cached_user_profile(user_id) if profile is None: return fastapi.responses.JSONResponse( status_code=404, @@ -85,6 +198,8 @@ async def update_or_create_profile( updated_profile = await backend.server.v2.store.db.update_profile( user_id=user_id, profile=profile ) + # Clear the cache for this user after profile update + _get_cached_user_profile.cache_delete(user_id) return updated_profile except Exception as e: logger.exception("Failed to update profile for user %s: %s", user_id, e) @@ -119,6 +234,7 @@ async def get_agents( ): """ Get a paginated list of agents from the store with optional filtering and sorting. + Results are cached for 15 minutes. Args: featured (bool, optional): Filter to only show featured agents. Defaults to False. @@ -154,9 +270,9 @@ async def get_agents( ) try: - agents = await backend.server.v2.store.db.get_store_agents( + agents = await _get_cached_store_agents( featured=featured, - creators=[creator] if creator else None, + creator=creator, sorted_by=sorted_by, search_query=search_query, category=category, @@ -183,7 +299,8 @@ async def get_agents( ) async def get_agent(username: str, agent_name: str): """ - This is only used on the AgentDetails Page + This is only used on the AgentDetails Page. + Results are cached for 15 minutes. It returns the store listing agents details. """ @@ -191,7 +308,7 @@ async def get_agent(username: str, agent_name: str): username = urllib.parse.unquote(username).lower() # URL decode the agent name since it comes from the URL path agent_name = urllib.parse.unquote(agent_name).lower() - agent = await backend.server.v2.store.db.get_store_agent_details( + agent = await _get_cached_agent_details( username=username, agent_name=agent_name ) return agent @@ -214,11 +331,10 @@ async def get_agent(username: str, agent_name: str): async def get_graph_meta_by_store_listing_version_id(store_listing_version_id: str): """ Get Agent Graph from Store Listing Version ID. + Results are cached for 1 hour. """ try: - graph = await backend.server.v2.store.db.get_available_graph( - store_listing_version_id - ) + graph = await _get_cached_agent_graph(store_listing_version_id) return graph except Exception: logger.exception("Exception occurred whilst getting agent graph") @@ -238,11 +354,10 @@ async def get_graph_meta_by_store_listing_version_id(store_listing_version_id: s async def get_store_agent(store_listing_version_id: str): """ Get Store Agent Details from Store Listing Version ID. + Results are cached for 1 hour. """ try: - agent = await backend.server.v2.store.db.get_store_agent_by_version_id( - store_listing_version_id - ) + agent = await _get_cached_store_agent_by_version(store_listing_version_id) return agent except Exception: logger.exception("Exception occurred whilst getting store agent") @@ -279,7 +394,7 @@ async def create_review( """ try: username = urllib.parse.unquote(username).lower() - agent_name = urllib.parse.unquote(agent_name) + agent_name = urllib.parse.unquote(agent_name).lower() # Create the review created_review = await backend.server.v2.store.db.create_store_review( user_id=user_id, @@ -320,6 +435,8 @@ async def get_creators( - Home Page Featured Creators - Search Results Page + Results are cached for 1 hour. + --- To support this functionality we need: @@ -338,7 +455,7 @@ async def get_creators( ) try: - creators = await backend.server.v2.store.db.get_store_creators( + creators = await _get_cached_store_creators( featured=featured, search_query=search_query, sorted_by=sorted_by, @@ -364,14 +481,13 @@ async def get_creator( username: str, ): """ - Get the details of a creator + Get the details of a creator. + Results are cached for 1 hour. - Creator Details Page """ try: username = urllib.parse.unquote(username).lower() - creator = await backend.server.v2.store.db.get_store_creator_details( - username=username.lower() - ) + creator = await _get_cached_creator_details(username=username) return creator except Exception: logger.exception("Exception occurred whilst getting creator details") @@ -386,6 +502,8 @@ async def get_creator( ############################################ ############# Store Submissions ############### ############################################ + + @router.get( "/myagents", summary="Get my agents", @@ -398,10 +516,12 @@ async def get_my_agents( page: typing.Annotated[int, fastapi.Query(ge=1)] = 1, page_size: typing.Annotated[int, fastapi.Query(ge=1)] = 20, ): + """ + Get user's own agents. + Results are cached for 5 minutes per user. + """ try: - agents = await backend.server.v2.store.db.get_my_agents( - user_id, page=page, page_size=page_size - ) + agents = await _get_cached_my_agents(user_id, page=page, page_size=page_size) return agents except Exception: logger.exception("Exception occurred whilst getting my agents") @@ -437,6 +557,14 @@ async def delete_submission( user_id=user_id, submission_id=submission_id, ) + + # Clear submissions cache for this specific user after deletion + if result: + # Clear user's own agents cache - we don't know all page/size combinations + for page in range(1, 20): + # Clear user's submissions cache for common defaults + _get_cached_submissions.cache_delete(user_id, page=page, page_size=20) + return result except Exception: logger.exception("Exception occurred whilst deleting store submission") @@ -460,6 +588,7 @@ async def get_submissions( ): """ Get a paginated list of store submissions for the authenticated user. + Results are cached for 1 hour per user. Args: user_id (str): ID of the authenticated user @@ -482,10 +611,8 @@ async def get_submissions( status_code=422, detail="Page size must be greater than 0" ) try: - listings = await backend.server.v2.store.db.get_store_submissions( - user_id=user_id, - page=page, - page_size=page_size, + listings = await _get_cached_submissions( + user_id, page=page, page_size=page_size ) return listings except Exception: @@ -523,7 +650,7 @@ async def create_submission( HTTPException: If there is an error creating the submission """ try: - return await backend.server.v2.store.db.create_store_submission( + result = await backend.server.v2.store.db.create_store_submission( user_id=user_id, agent_id=submission_request.agent_id, agent_version=submission_request.agent_version, @@ -538,6 +665,13 @@ async def create_submission( changes_summary=submission_request.changes_summary or "Initial Submission", recommended_schedule_cron=submission_request.recommended_schedule_cron, ) + + # Clear user's own agents cache - we don't know all page/size combinations + for page in range(1, 20): + # Clear user's submissions cache for common defaults + _get_cached_submissions.cache_delete(user_id, page=page, page_size=20) + + return result except Exception: logger.exception("Exception occurred whilst creating store submission") return fastapi.responses.JSONResponse( @@ -572,7 +706,7 @@ async def edit_submission( Raises: HTTPException: If there is an error editing the submission """ - return await backend.server.v2.store.db.edit_store_submission( + result = await backend.server.v2.store.db.edit_store_submission( user_id=user_id, store_listing_version_id=store_listing_version_id, name=submission_request.name, @@ -586,6 +720,13 @@ async def edit_submission( recommended_schedule_cron=submission_request.recommended_schedule_cron, ) + # Clear user's own agents cache - we don't know all page/size combinations + for page in range(1, 20): + # Clear user's submissions cache for common defaults + _get_cached_submissions.cache_delete(user_id, page=page, page_size=20) + + return result + @router.post( "/submissions/media", @@ -737,3 +878,63 @@ async def download_agent_file( return fastapi.responses.FileResponse( tmp_file.name, filename=file_name, media_type="application/json" ) + + +############################################## +############### Cache Management ############# +############################################## + + +@router.get( + "/metrics/cache", + summary="Get cache metrics in Prometheus format", + tags=["store", "metrics"], + response_class=fastapi.responses.PlainTextResponse, +) +async def get_cache_metrics(): + """ + Get cache metrics in Prometheus text format. + + Returns Prometheus-compatible metrics for monitoring cache performance. + Metrics include size, maxsize, TTL, and hit rate for each cache. + + Returns: + str: Prometheus-formatted metrics text + """ + metrics = [] + + # Helper to add metrics for a cache + def add_cache_metrics(cache_name: str, cache_func): + info = cache_func.cache_info() + # Cache size metric (dynamic - changes as items are cached/expired) + metrics.append(f'store_cache_entries{{cache="{cache_name}"}} {info["size"]}') + # Cache utilization percentage (dynamic - useful for monitoring) + utilization = ( + (info["size"] / info["maxsize"] * 100) if info["maxsize"] > 0 else 0 + ) + metrics.append( + f'store_cache_utilization_percent{{cache="{cache_name}"}} {utilization:.2f}' + ) + + # Add metrics for each cache + add_cache_metrics("user_profile", _get_cached_user_profile) + add_cache_metrics("store_agents", _get_cached_store_agents) + add_cache_metrics("agent_details", _get_cached_agent_details) + add_cache_metrics("agent_graph", _get_cached_agent_graph) + add_cache_metrics("agent_by_version", _get_cached_store_agent_by_version) + add_cache_metrics("store_creators", _get_cached_store_creators) + add_cache_metrics("creator_details", _get_cached_creator_details) + add_cache_metrics("my_agents", _get_cached_my_agents) + add_cache_metrics("submissions", _get_cached_submissions) + + # Add metadata/help text at the beginning + prometheus_output = [ + "# HELP store_cache_entries Number of entries currently in cache", + "# TYPE store_cache_entries gauge", + "# HELP store_cache_utilization_percent Cache utilization as percentage (0-100)", + "# TYPE store_cache_utilization_percent gauge", + "", # Empty line before metrics + ] + prometheus_output.extend(metrics) + + return "\n".join(prometheus_output) diff --git a/autogpt_platform/backend/backend/server/v2/store/test_cache_delete.py b/autogpt_platform/backend/backend/server/v2/store/test_cache_delete.py new file mode 100644 index 0000000000..12d76c76e4 --- /dev/null +++ b/autogpt_platform/backend/backend/server/v2/store/test_cache_delete.py @@ -0,0 +1,351 @@ +#!/usr/bin/env python3 +""" +Test suite for verifying cache_delete functionality in store routes. +Tests that specific cache entries can be deleted while preserving others. +""" + +import datetime +from unittest.mock import AsyncMock, patch + +import pytest + +from backend.server.v2.store import routes +from backend.server.v2.store.model import ( + ProfileDetails, + StoreAgent, + StoreAgentDetails, + StoreAgentsResponse, +) +from backend.util.models import Pagination + + +class TestCacheDeletion: + """Test cache deletion functionality for store routes.""" + + @pytest.mark.asyncio + async def test_store_agents_cache_delete(self): + """Test that specific agent list cache entries can be deleted.""" + # Mock the database function + mock_response = StoreAgentsResponse( + agents=[ + StoreAgent( + slug="test-agent", + agent_name="Test Agent", + agent_image="https://example.com/image.jpg", + creator="testuser", + creator_avatar="https://example.com/avatar.jpg", + sub_heading="Test subheading", + description="Test description", + runs=100, + rating=4.5, + ) + ], + pagination=Pagination( + total_items=1, + total_pages=1, + current_page=1, + page_size=20, + ), + ) + + with patch( + "backend.server.v2.store.db.get_store_agents", + new_callable=AsyncMock, + return_value=mock_response, + ) as mock_db: + # Clear cache first + routes._get_cached_store_agents.cache_clear() + + # First call - should hit database + result1 = await routes._get_cached_store_agents( + featured=False, + creator=None, + sorted_by=None, + search_query="test", + category=None, + page=1, + page_size=20, + ) + assert mock_db.call_count == 1 + assert result1.agents[0].agent_name == "Test Agent" + + # Second call with same params - should use cache + await routes._get_cached_store_agents( + featured=False, + creator=None, + sorted_by=None, + search_query="test", + category=None, + page=1, + page_size=20, + ) + assert mock_db.call_count == 1 # No additional DB call + + # Third call with different params - should hit database + await routes._get_cached_store_agents( + featured=True, # Different param + creator=None, + sorted_by=None, + search_query="test", + category=None, + page=1, + page_size=20, + ) + assert mock_db.call_count == 2 # New DB call + + # Delete specific cache entry + deleted = routes._get_cached_store_agents.cache_delete( + featured=False, + creator=None, + sorted_by=None, + search_query="test", + category=None, + page=1, + page_size=20, + ) + assert deleted is True # Entry was deleted + + # Try to delete non-existent entry + deleted = routes._get_cached_store_agents.cache_delete( + featured=False, + creator="nonexistent", + sorted_by=None, + search_query="test", + category=None, + page=1, + page_size=20, + ) + assert deleted is False # Entry didn't exist + + # Call with deleted params - should hit database again + await routes._get_cached_store_agents( + featured=False, + creator=None, + sorted_by=None, + search_query="test", + category=None, + page=1, + page_size=20, + ) + assert mock_db.call_count == 3 # New DB call after deletion + + # Call with featured=True - should still be cached + await routes._get_cached_store_agents( + featured=True, + creator=None, + sorted_by=None, + search_query="test", + category=None, + page=1, + page_size=20, + ) + assert mock_db.call_count == 3 # No additional DB call + + @pytest.mark.asyncio + async def test_agent_details_cache_delete(self): + """Test that specific agent details cache entries can be deleted.""" + mock_response = StoreAgentDetails( + store_listing_version_id="version1", + slug="test-agent", + agent_name="Test Agent", + agent_video="https://example.com/video.mp4", + agent_image=["https://example.com/image.jpg"], + creator="testuser", + creator_avatar="https://example.com/avatar.jpg", + sub_heading="Test subheading", + description="Test description", + categories=["productivity"], + runs=100, + rating=4.5, + versions=[], + last_updated=datetime.datetime(2024, 1, 1), + ) + + with patch( + "backend.server.v2.store.db.get_store_agent_details", + new_callable=AsyncMock, + return_value=mock_response, + ) as mock_db: + # Clear cache first + routes._get_cached_agent_details.cache_clear() + + # First call - should hit database + await routes._get_cached_agent_details( + username="testuser", agent_name="testagent" + ) + assert mock_db.call_count == 1 + + # Second call - should use cache + await routes._get_cached_agent_details( + username="testuser", agent_name="testagent" + ) + assert mock_db.call_count == 1 # No additional DB call + + # Delete specific entry + deleted = routes._get_cached_agent_details.cache_delete( + username="testuser", agent_name="testagent" + ) + assert deleted is True + + # Call again - should hit database + await routes._get_cached_agent_details( + username="testuser", agent_name="testagent" + ) + assert mock_db.call_count == 2 # New DB call after deletion + + @pytest.mark.asyncio + async def test_user_profile_cache_delete(self): + """Test that user profile cache entries can be deleted.""" + mock_response = ProfileDetails( + name="Test User", + username="testuser", + description="Test profile", + links=["https://example.com"], + ) + + with patch( + "backend.server.v2.store.db.get_user_profile", + new_callable=AsyncMock, + return_value=mock_response, + ) as mock_db: + # Clear cache first + routes._get_cached_user_profile.cache_clear() + + # First call - should hit database + await routes._get_cached_user_profile("user123") + assert mock_db.call_count == 1 + + # Second call - should use cache + await routes._get_cached_user_profile("user123") + assert mock_db.call_count == 1 + + # Different user - should hit database + await routes._get_cached_user_profile("user456") + assert mock_db.call_count == 2 + + # Delete specific user's cache + deleted = routes._get_cached_user_profile.cache_delete("user123") + assert deleted is True + + # user123 should hit database again + await routes._get_cached_user_profile("user123") + assert mock_db.call_count == 3 + + # user456 should still be cached + await routes._get_cached_user_profile("user456") + assert mock_db.call_count == 3 # No additional DB call + + @pytest.mark.asyncio + async def test_cache_info_after_deletions(self): + """Test that cache_info correctly reflects deletions.""" + # Clear all caches first + routes._get_cached_store_agents.cache_clear() + + mock_response = StoreAgentsResponse( + agents=[], + pagination=Pagination( + total_items=0, + total_pages=1, + current_page=1, + page_size=20, + ), + ) + + with patch( + "backend.server.v2.store.db.get_store_agents", + new_callable=AsyncMock, + return_value=mock_response, + ): + # Add multiple entries + for i in range(5): + await routes._get_cached_store_agents( + featured=False, + creator=f"creator{i}", + sorted_by=None, + search_query=None, + category=None, + page=1, + page_size=20, + ) + + # Check cache size + info = routes._get_cached_store_agents.cache_info() + assert info["size"] == 5 + + # Delete some entries + for i in range(2): + deleted = routes._get_cached_store_agents.cache_delete( + featured=False, + creator=f"creator{i}", + sorted_by=None, + search_query=None, + category=None, + page=1, + page_size=20, + ) + assert deleted is True + + # Check cache size after deletion + info = routes._get_cached_store_agents.cache_info() + assert info["size"] == 3 + + @pytest.mark.asyncio + async def test_cache_delete_with_complex_params(self): + """Test cache deletion with various parameter combinations.""" + mock_response = StoreAgentsResponse( + agents=[], + pagination=Pagination( + total_items=0, + total_pages=1, + current_page=1, + page_size=20, + ), + ) + + with patch( + "backend.server.v2.store.db.get_store_agents", + new_callable=AsyncMock, + return_value=mock_response, + ) as mock_db: + routes._get_cached_store_agents.cache_clear() + + # Test with all parameters + await routes._get_cached_store_agents( + featured=True, + creator="testuser", + sorted_by="rating", + search_query="AI assistant", + category="productivity", + page=2, + page_size=50, + ) + assert mock_db.call_count == 1 + + # Delete with exact same parameters + deleted = routes._get_cached_store_agents.cache_delete( + featured=True, + creator="testuser", + sorted_by="rating", + search_query="AI assistant", + category="productivity", + page=2, + page_size=50, + ) + assert deleted is True + + # Try to delete with slightly different parameters + deleted = routes._get_cached_store_agents.cache_delete( + featured=True, + creator="testuser", + sorted_by="rating", + search_query="AI assistant", + category="productivity", + page=2, + page_size=51, # Different page_size + ) + assert deleted is False # Different parameters, not in cache + + +if __name__ == "__main__": + # Run the tests + pytest.main([__file__, "-v"]) diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json index c55be2345d..8604d967e6 100644 --- a/autogpt_platform/frontend/src/app/api/openapi.json +++ b/autogpt_platform/frontend/src/app/api/openapi.json @@ -2493,7 +2493,7 @@ "get": { "tags": ["v2", "store", "private"], "summary": "Get user profile", - "description": "Get the profile details for the authenticated user.", + "description": "Get the profile details for the authenticated user.\nCached for 1 hour per user.", "operationId": "getV2Get user profile", "responses": { "200": { @@ -2551,7 +2551,7 @@ "get": { "tags": ["v2", "store", "public"], "summary": "List store agents", - "description": "Get a paginated list of agents from the store with optional filtering and sorting.\n\nArgs:\n featured (bool, optional): Filter to only show featured agents. Defaults to False.\n creator (str | None, optional): Filter agents by creator username. Defaults to None.\n sorted_by (str | None, optional): Sort agents by \"runs\" or \"rating\". Defaults to None.\n search_query (str | None, optional): Search agents by name, subheading and description. Defaults to None.\n category (str | None, optional): Filter agents by category. Defaults to None.\n page (int, optional): Page number for pagination. Defaults to 1.\n page_size (int, optional): Number of agents per page. Defaults to 20.\n\nReturns:\n StoreAgentsResponse: Paginated list of agents matching the filters\n\nRaises:\n HTTPException: If page or page_size are less than 1\n\nUsed for:\n- Home Page Featured Agents\n- Home Page Top Agents\n- Search Results\n- Agent Details - Other Agents By Creator\n- Agent Details - Similar Agents\n- Creator Details - Agents By Creator", + "description": "Get a paginated list of agents from the store with optional filtering and sorting.\nResults are cached for 15 minutes.\n\nArgs:\n featured (bool, optional): Filter to only show featured agents. Defaults to False.\n creator (str | None, optional): Filter agents by creator username. Defaults to None.\n sorted_by (str | None, optional): Sort agents by \"runs\" or \"rating\". Defaults to None.\n search_query (str | None, optional): Search agents by name, subheading and description. Defaults to None.\n category (str | None, optional): Filter agents by category. Defaults to None.\n page (int, optional): Page number for pagination. Defaults to 1.\n page_size (int, optional): Number of agents per page. Defaults to 20.\n\nReturns:\n StoreAgentsResponse: Paginated list of agents matching the filters\n\nRaises:\n HTTPException: If page or page_size are less than 1\n\nUsed for:\n- Home Page Featured Agents\n- Home Page Top Agents\n- Search Results\n- Agent Details - Other Agents By Creator\n- Agent Details - Similar Agents\n- Creator Details - Agents By Creator", "operationId": "getV2List store agents", "parameters": [ { @@ -2637,7 +2637,7 @@ "get": { "tags": ["v2", "store", "public"], "summary": "Get specific agent", - "description": "This is only used on the AgentDetails Page\n\nIt returns the store listing agents details.", + "description": "This is only used on the AgentDetails Page.\nResults are cached for 15 minutes.\n\nIt returns the store listing agents details.", "operationId": "getV2Get specific agent", "parameters": [ { @@ -2677,7 +2677,7 @@ "get": { "tags": ["v2", "store"], "summary": "Get agent graph", - "description": "Get Agent Graph from Store Listing Version ID.", + "description": "Get Agent Graph from Store Listing Version ID.\nResults are cached for 1 hour.", "operationId": "getV2Get agent graph", "security": [{ "HTTPBearerJWT": [] }], "parameters": [ @@ -2711,7 +2711,7 @@ "get": { "tags": ["v2", "store"], "summary": "Get agent by version", - "description": "Get Store Agent Details from Store Listing Version ID.", + "description": "Get Store Agent Details from Store Listing Version ID.\nResults are cached for 1 hour.", "operationId": "getV2Get agent by version", "security": [{ "HTTPBearerJWT": [] }], "parameters": [ @@ -2801,7 +2801,7 @@ "get": { "tags": ["v2", "store", "public"], "summary": "List store creators", - "description": "This is needed for:\n- Home Page Featured Creators\n- Search Results Page\n\n---\n\nTo support this functionality we need:\n- featured: bool - to limit the list to just featured agents\n- search_query: str - vector search based on the creators profile description.\n- sorted_by: [agent_rating, agent_runs] -", + "description": "This is needed for:\n- Home Page Featured Creators\n- Search Results Page\n\nResults are cached for 1 hour.\n\n---\n\nTo support this functionality we need:\n- featured: bool - to limit the list to just featured agents\n- search_query: str - vector search based on the creators profile description.\n- sorted_by: [agent_rating, agent_runs] -", "operationId": "getV2List store creators", "parameters": [ { @@ -2869,7 +2869,7 @@ "get": { "tags": ["v2", "store", "public"], "summary": "Get creator details", - "description": "Get the details of a creator\n- Creator Details Page", + "description": "Get the details of a creator.\nResults are cached for 1 hour.\n- Creator Details Page", "operationId": "getV2Get creator details", "parameters": [ { @@ -2903,6 +2903,7 @@ "get": { "tags": ["v2", "store", "private"], "summary": "Get my agents", + "description": "Get user's own agents.\nResults are cached for 5 minutes per user.", "operationId": "getV2Get my agents", "security": [{ "HTTPBearerJWT": [] }], "parameters": [ @@ -2997,7 +2998,7 @@ "get": { "tags": ["v2", "store", "private"], "summary": "List my submissions", - "description": "Get a paginated list of store submissions for the authenticated user.\n\nArgs:\n user_id (str): ID of the authenticated user\n page (int, optional): Page number for pagination. Defaults to 1.\n page_size (int, optional): Number of submissions per page. Defaults to 20.\n\nReturns:\n StoreListingsResponse: Paginated list of store submissions\n\nRaises:\n HTTPException: If page or page_size are less than 1", + "description": "Get a paginated list of store submissions for the authenticated user.\nResults are cached for 1 hour per user.\n\nArgs:\n user_id (str): ID of the authenticated user\n page (int, optional): Page number for pagination. Defaults to 1.\n page_size (int, optional): Number of submissions per page. Defaults to 20.\n\nReturns:\n StoreListingsResponse: Paginated list of store submissions\n\nRaises:\n HTTPException: If page or page_size are less than 1", "operationId": "getV2List my submissions", "security": [{ "HTTPBearerJWT": [] }], "parameters": [ @@ -3230,6 +3231,20 @@ } } }, + "/api/store/metrics/cache": { + "get": { + "tags": ["v2", "store", "metrics"], + "summary": "Get cache metrics in Prometheus format", + "description": "Get cache metrics in Prometheus text format.\n\nReturns Prometheus-compatible metrics for monitoring cache performance.\nMetrics include size, maxsize, TTL, and hit rate for each cache.\n\nReturns:\n str: Prometheus-formatted metrics text", + "operationId": "getV2Get cache metrics in prometheus format", + "responses": { + "200": { + "description": "Successful Response", + "content": { "text/plain": { "schema": { "type": "string" } } } + } + } + } + }, "/api/builder/suggestions": { "get": { "tags": ["v2"],