[MISC] Optimize prompt studio list endpoint with lightweight serializer#1901
Conversation
Reduce list endpoint queries from O(tools × (4 + prompts)) to ~1 by:
- New CustomToolListSerializer skips profile lookups, prompt fetching, coverage calculation
- Added get_serializer_class() to route list action to lightweight serializer
- Optimized get_queryset() with select_related("created_by") + Subquery annotation for prompt_count
Tested: 4.6x faster (0.057s vs 0.263s with 9 tools). No regressions on detail/retrieve/create/update/delete.
Consumers verified: ListOfTools.jsx (OSS), LinkProjectModal.jsx (Cloud lookups).
Summary by CodeRabbit
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Filename | Overview |
|---|---|
| backend/prompt_studio/prompt_studio_core_v2/serializers.py | Adds CustomToolListSerializer: lightweight, 11-field serializer with safe annotation-aware prompt_count and select_related-backed email lookup; fallback now logs a warning. |
| backend/prompt_studio/prompt_studio_core_v2/views.py | Routes list action to CustomToolListSerializer and annotates _prompt_count via Subquery to avoid DISTINCT ON conflict; select_related('created_by') eliminates N+1 for email field. |
Sequence Diagram
sequenceDiagram
participant Client
participant PromptStudioCoreView
participant get_queryset
participant DB
participant CustomToolListSerializer
Client->>PromptStudioCoreView: GET /api/v2/prompt-studio/ (list)
PromptStudioCoreView->>PromptStudioCoreView: get_serializer_class() → CustomToolListSerializer
PromptStudioCoreView->>get_queryset: get_queryset()
get_queryset->>DB: CustomTool.objects.for_user(user)<br/>(DISTINCT ON tool_id)
get_queryset->>DB: .select_related("created_by")<br/>.annotate(_prompt_count=Subquery(...))
Note over DB: Single SQL: JOIN user + correlated<br/>COUNT subquery per tool
DB-->>get_queryset: Annotated QuerySet
get_queryset-->>PromptStudioCoreView: qs with _prompt_count + created_by prefetched
PromptStudioCoreView->>CustomToolListSerializer: serialize(qs, many=True)
CustomToolListSerializer->>CustomToolListSerializer: get_created_by_email()<br/>→ instance.created_by.email (no extra query)
CustomToolListSerializer->>CustomToolListSerializer: get_prompt_count()<br/>→ instance._prompt_count or 0 (no extra query)
CustomToolListSerializer-->>Client: [{tool_id, tool_name, description, author,<br/>icon, created_by_email, shared_to_org, prompt_count, ...}]
Prompt To Fix All With AI
This is a comment left during a code review.
Path: backend/prompt_studio/prompt_studio_core_v2/serializers.py
Line: 68
Comment:
**`0 or 0` evaluates to `0`, but `False or 0` also evaluates to `0`**
The expression `instance._prompt_count or 0` is correct for the `None` case (no prompts → subquery returns `NULL` → Python `None`). However, it will also silently coerce any falsy integer (only `0` is realistic here, which is fine). If the annotation ever returns a non-integer truthy value by mistake, the raw value would be passed through unchecked. Using an explicit `None`-guard is slightly clearer and more defensive:
```suggestion
return instance._prompt_count if instance._prompt_count is not None else 0
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (3): Last reviewed commit: "Merge branch 'main' into fix/prompt-stud..." | Re-trigger Greptile
There was a problem hiding this comment.
🧹 Nitpick comments (1)
backend/prompt_studio/prompt_studio_core_v2/views.py (1)
114-120: UseCount("id")to match codebase convention.
Count("*")is valid in Django and explicitly supported, but the codebase consistently usesCount("id")throughout. Align with the established pattern for consistency.♻️ Suggested change
prompt_count_sq = ( ToolStudioPrompt.objects.filter(tool_id=OuterRef("pk")) .order_by() .values("tool_id") - .annotate(cnt=Count("*")) + .annotate(cnt=Count("id")) .values("cnt") )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/prompt_studio/prompt_studio_core_v2/views.py` around lines 114 - 120, Replace the Count("*") usage in the subquery assigned to prompt_count_sq with Count("id") to follow the codebase convention; locate the ToolStudioPrompt subquery where prompt_count_sq is defined (the .annotate(cnt=Count("*")) call) and change the aggregation to .annotate(cnt=Count("id")) so the result and naming remain identical but match project style.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@backend/prompt_studio/prompt_studio_core_v2/views.py`:
- Around line 114-120: Replace the Count("*") usage in the subquery assigned to
prompt_count_sq with Count("id") to follow the codebase convention; locate the
ToolStudioPrompt subquery where prompt_count_sq is defined (the
.annotate(cnt=Count("*")) call) and change the aggregation to
.annotate(cnt=Count("id")) so the result and naming remain identical but match
project style.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0c791da9-4730-4447-9092-73f931035930
📒 Files selected for processing (2)
backend/prompt_studio/prompt_studio_core_v2/serializers.pybackend/prompt_studio/prompt_studio_core_v2/views.py
- Add logger.warning when _prompt_count annotation is missing to surface
misuse instead of silently degrading to per-instance queries
- Use Count("id") instead of Count("*") to match codebase convention
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
backend/prompt_studio/prompt_studio_core_v2/serializers.py (1)
49-61: Use an immutable tuple forMeta.fieldsto satisfy lint and avoid mutable class attributes.Ruff RUF012 is being triggered here. Switching to a tuple keeps behavior identical and removes mutable class attribute risk.
Suggested diff
class Meta: model = CustomTool - fields = [ + fields = ( "tool_id", "tool_name", "description", "author", "created_by", "created_at", "modified_at", "shared_to_org", "icon", "created_by_email", "prompt_count", - ] + )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/prompt_studio/prompt_studio_core_v2/serializers.py` around lines 49 - 61, Meta.fields is currently defined as a mutable list which triggers lint RUF012; change it to an immutable tuple by replacing the list literal with a tuple literal in the serializer's Meta class (the symbol to update is Meta.fields in prompt_studio_core_v2/serializers.py) so the field order is preserved but the class attribute is no longer mutable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@backend/prompt_studio/prompt_studio_core_v2/serializers.py`:
- Around line 49-61: Meta.fields is currently defined as a mutable list which
triggers lint RUF012; change it to an immutable tuple by replacing the list
literal with a tuple literal in the serializer's Meta class (the symbol to
update is Meta.fields in prompt_studio_core_v2/serializers.py) so the field
order is preserved but the class attribute is no longer mutable.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 46033dc8-a70e-4aa2-92c3-31c074bfcd8a
📒 Files selected for processing (2)
backend/prompt_studio/prompt_studio_core_v2/serializers.pybackend/prompt_studio/prompt_studio_core_v2/views.py
🚧 Files skipped from review as they are similar to previous changes (1)
- backend/prompt_studio/prompt_studio_core_v2/views.py
|
Test ResultsSummary
Runner Tests - Full Report
SDK1 Tests - Full Report
|



What
CustomToolListSerializer— lightweight serializer for list endpoint returning only frontend-needed fields (tool_id, tool_name, description, author, icon, created_by_email, shared_to_org, prompt_count)get_serializer_class()routing list action to lightweight serializerget_queryset()withselect_related("created_by")and Subquery annotation for prompt_countWhy
List endpoint currently uses
CustomToolSerializer.to_representation()which runs O(tools × (4 + prompts)) DB queries: 2 ProfileManager queries + prompt fetching + coverage calculation per tool. Skipping unnecessary work reduces API response time 4.6x.How
ListOfTools.jsx(OSS) andLinkProjectModal.jsx(Cloud)CustomToolSerializerCan this PR break any existing features
No. List endpoint returns same top-level fields; only omits nested prompts/profiles/coverage. Consumers verified:
ListOfTools.jsx,LinkProjectModal.jsx. Detail endpoints unaffected.Database Migrations
Env Config
Relevant Docs
Related Issues or PRs
Dependencies Versions
Notes on Testing
Tested locally with 9 tools: list endpoint 0.057s (new) vs 0.263s (old) = 4.6x faster. All fields returned match consumer requirements. No regressions.
Screenshots