Skip to content

Commit 5c5d41b

Browse files
iamjr15claude
andcommitted
perf: lazy load heavy libraries to reduce memory usage
- Remove unused litellm and openai imports from sb_files_tool.py - Lazy load PIL in sb_vision_tool.py (only when processing images) - Lazy load numpy and google.genai in component_search_tool.py - Lazy load Polar SDK in polar_service.py (only when payments used) - Lazy load polar_sdk.webhooks in webhook handler Estimated savings: ~48-75 MB per worker (~240-375 MB total with 5 workers) Also: Update deploy tooltip text in thread-site-header.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent f8d5059 commit 5c5d41b

File tree

6 files changed

+48
-27
lines changed

6 files changed

+48
-27
lines changed

backend/agent/tools/component_search_tool.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import json
2-
import numpy as np
32
from typing import List, Dict, Any, Optional
43
from agentpress.tool import Tool, ToolResult, ToolSchema, SchemaType, XMLTagSchema, XMLNodeMapping
54
from agentpress.thread_manager import ThreadManager
65
from services.supabase import DBConnection
76
from utils.logger import logger
87
from utils.config import config
9-
# Google Generative AI (Gemini) SDK
10-
from google import genai
11-
from google.genai import types
128

139
class ComponentSearchTool(Tool):
1410
"""Tool for searching components using embedding-based semantic search.
@@ -22,19 +18,29 @@ def __init__(self, thread_manager: ThreadManager, app_type: str = 'web'):
2218
self.thread_manager = thread_manager
2319
self.db = DBConnection()
2420
self.app_type = app_type
21+
self._client = None
22+
self._client_initialized = False
2523
logger.info(f"🔍 ComponentSearchTool initialized with app_type: {app_type}")
2624

27-
# Initialize Gemini API client using centralized config
25+
def _get_client(self):
26+
"""Lazy initialize the Gemini API client."""
27+
if self._client_initialized:
28+
return self._client
29+
30+
from google import genai # Lazy load - only needed when search is used
31+
2832
api_key = config.GOOGLE_API_KEY
2933
try:
3034
if api_key:
31-
self.client = genai.Client(api_key=api_key)
35+
self._client = genai.Client(api_key=api_key)
3236
else:
3337
logger.debug("GOOGLE_API_KEY not set – skipping Gemini client init")
34-
self.client = None
35-
except Exception as api_err: # pragma: no cover
38+
self._client = None
39+
except Exception as api_err:
3640
logger.warning(f"Failed to initialize Google GenAI client: {api_err}")
37-
self.client = None
41+
self._client = None
42+
self._client_initialized = True
43+
return self._client
3844

3945
def get_schemas(self) -> Dict[str, List[ToolSchema]]:
4046
"""Override base class to provide dynamic schemas based on app_type."""
@@ -174,35 +180,39 @@ def get_tool_schemas(self) -> Dict[str, List[ToolSchema]]:
174180
async def _generate_embedding(self, text: str) -> List[float]:
175181
"""Generate embedding for the given text using Gemini."""
176182
try:
177-
if not self.client:
183+
from google.genai import types # Lazy load - only needed for embedding
184+
import numpy as np # Lazy load - only needed for normalization
185+
186+
client = self._get_client()
187+
if not client:
178188
logger.error("Gemini client not initialized")
179189
return []
180-
190+
181191
# Use the latest Gemini embedding API
182-
response = self.client.models.embed_content(
192+
response = client.models.embed_content(
183193
model='gemini-embedding-001',
184194
contents=text,
185195
config=types.EmbedContentConfig(
186196
task_type="CODE_RETRIEVAL_QUERY", # Optimized for code queries
187197
output_dimensionality=1536
188198
)
189199
)
190-
200+
191201
if response and response.embeddings:
192202
# Get the first (and only) embedding from the response
193203
embedding_obj = response.embeddings[0]
194204
embedding_values = embedding_obj.values
195-
205+
196206
# Normalize the embedding for 1536 dimensions (as per documentation)
197207
# The 3072 dimension embedding is auto-normalized, but smaller ones need manual normalization
198208
embedding_array = np.array(embedding_values)
199209
normalized_embedding = embedding_array / np.linalg.norm(embedding_array)
200-
210+
201211
return normalized_embedding.tolist()
202212
else:
203213
logger.error("No embeddings returned from Gemini API")
204214
return []
205-
215+
206216
except Exception as e:
207217
logger.error(f"Error generating embedding: {e}")
208218
return []

backend/agent/tools/sb_files_tool.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
from utils.config import config
88
import json
99
import re
10-
import litellm
11-
import openai
1210

1311
class SandboxFilesTool(SandboxToolsBase):
1412
"""Tool for executing file system operations in a Daytona sandbox. All operations are performed relative to the workspace directory."""

backend/agent/tools/sb_vision_tool.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import mimetypes
44
from typing import Optional, Tuple, Dict, List
55
from io import BytesIO
6-
from PIL import Image
76
from urllib.parse import urlparse
87
from agentpress.tool import ToolResult, ToolSchema, SchemaType, XMLTagSchema, XMLNodeMapping
98
from sandbox.tool_base import SandboxToolsBase
@@ -115,16 +114,18 @@ def get_tool_schemas(self) -> Dict[str, List[ToolSchema]]:
115114

116115
def compress_image(self, image_bytes: bytes, mime_type: str, file_path: str) -> Tuple[bytes, str]:
117116
"""Compress an image to reduce its size while maintaining reasonable quality.
118-
117+
119118
Args:
120119
image_bytes: Original image bytes
121120
mime_type: MIME type of the image
122121
file_path: Path to the image file (for logging)
123-
122+
124123
Returns:
125124
Tuple of (compressed_bytes, new_mime_type)
126125
"""
127126
try:
127+
from PIL import Image # Lazy load - only needed when processing images
128+
128129
# Open image from bytes
129130
img = Image.open(BytesIO(image_bytes))
130131

backend/api/webhooks/polar.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
from datetime import datetime, timedelta
77
from typing import Dict, Any
88

9-
from polar_sdk.webhooks import validate_event, WebhookVerificationError
10-
119
from services.supabase import DBConnection
1210
from services.billing import invalidate_billing_cache
1311
from utils.logger import logger
@@ -237,6 +235,9 @@ async def handle_subscription_active(data: Dict[str, Any]):
237235
async def handle_polar_webhook(request: Request):
238236
"""Handle Polar webhook events."""
239237
try:
238+
# Lazy load - only needed when webhook is called
239+
from polar_sdk.webhooks import validate_event, WebhookVerificationError
240+
240241
# Get raw payload
241242
payload = await request.body()
242243

backend/services/polar_service.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"""
44

55
from typing import Dict, Optional
6-
from polar_sdk import Polar
76

87
from utils.config import config
98
from utils.logger import logger
@@ -18,6 +17,8 @@ class PolarService:
1817
def __init__(self):
1918
self.access_token = config.POLAR_ACCESS_TOKEN
2019
self.organization_id = config.POLAR_ORGANIZATION_ID
20+
self._client = None
21+
self._client_initialized = False
2122

2223
# Build product mapping from config
2324
self.PRODUCT_MAPPING = {
@@ -26,13 +27,23 @@ def __init__(self):
2627
'byok': config.POLAR_PRODUCT_ID_BYOK
2728
}
2829

30+
@property
31+
def client(self):
32+
"""Lazy initialize the Polar client."""
33+
if self._client_initialized:
34+
return self._client
35+
2936
if not self.access_token:
3037
logger.warning("POLAR_ACCESS_TOKEN not configured - payment processing will be unavailable")
31-
self.client = None
38+
self._client = None
3239
else:
33-
self.client = Polar(access_token=self.access_token)
40+
from polar_sdk import Polar # Lazy load - only needed when payments are used
41+
self._client = Polar(access_token=self.access_token)
3442
logger.info("Polar SDK initialized successfully")
3543

44+
self._client_initialized = True
45+
return self._client
46+
3647
def is_configured(self) -> bool:
3748
"""Check if Polar is properly configured."""
3849
return self.client is not None

frontend/src/components/thread/thread-site-header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ export function SiteHeader() {
445445
<div className="p-4 space-y-3">
446446
<h4 className="text-sm font-semibold text-white">Deploy your site</h4>
447447
<p className="text-sm text-muted-foreground">
448-
Your app will be deployed to a .style.dev domain based on your project name.
448+
Your app will be deployed and you&apos;ll receive a live URL.
449449
</p>
450450

451451
{/* Progress bar during deployment */}

0 commit comments

Comments
 (0)