11import json
2- import numpy as np
32from typing import List , Dict , Any , Optional
43from agentpress .tool import Tool , ToolResult , ToolSchema , SchemaType , XMLTagSchema , XMLNodeMapping
54from agentpress .thread_manager import ThreadManager
65from services .supabase import DBConnection
76from utils .logger import logger
87from utils .config import config
9- # Google Generative AI (Gemini) SDK
10- from google import genai
11- from google .genai import types
128
139class 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 []
0 commit comments