Unified search via Vertex AI Search (preferred) or Google CSE (legacy).
| 317 | # --------------------------------------------------------------------------- |
| 318 | |
| 319 | class WebSearcher: |
| 320 | """Unified search via Vertex AI Search (preferred) or Google CSE (legacy).""" |
| 321 | |
| 322 | VERTEX_URL = ( |
| 323 | "https://discoveryengine.googleapis.com/v1/projects/{project_id}" |
| 324 | "/locations/global/collections/default_collection" |
| 325 | "/engines/{app_id}/servingConfigs/default_search:searchLite" |
| 326 | ) |
| 327 | GOOGLE_CSE_URL = "https://www.googleapis.com/customsearch/v1" |
| 328 | |
| 329 | def __init__(self): |
| 330 | self.use_vertex = bool( |
| 331 | VERTEX_AI_PROJECT_ID and VERTEX_AI_SEARCH_APP_ID |
| 332 | and VERTEX_AI_SEARCH_API_KEY |
| 333 | ) |
| 334 | self.use_google = bool(GOOGLE_SEARCH_API_KEY and GOOGLE_SEARCH_ENGINE_ID) |
| 335 | if self.use_vertex: |
| 336 | logger.info("Using Vertex AI Search (searchLite)") |
| 337 | elif self.use_google: |
| 338 | logger.info("Using Google CSE (legacy)") |
| 339 | else: |
| 340 | logger.warning( |
| 341 | "No search API configured. Set VERTEX_AI_* or GOOGLE_SEARCH_* env vars." |
| 342 | ) |
| 343 | |
| 344 | @property |
| 345 | def available(self) -> bool: |
| 346 | return self.use_vertex or self.use_google |
| 347 | |
| 348 | def search(self, query: str, num: int = 10) -> list[dict]: |
| 349 | if self.use_vertex: |
| 350 | return self._vertex_search(query, num) |
| 351 | if self.use_google: |
| 352 | return self._google_search(query, num) |
| 353 | return [] |
| 354 | |
| 355 | def _vertex_search(self, query: str, num: int) -> list[dict]: |
| 356 | query = re.sub(r"site:\S+\s*", "", query).strip() |
| 357 | query = re.sub(r"filetype:\S+\s*", "", query).strip() |
| 358 | url = self.VERTEX_URL.format( |
| 359 | project_id=VERTEX_AI_PROJECT_ID, |
| 360 | app_id=VERTEX_AI_SEARCH_APP_ID, |
| 361 | ) |
| 362 | try: |
| 363 | resp = requests.post( |
| 364 | url, |
| 365 | params={"key": VERTEX_AI_SEARCH_API_KEY}, |
| 366 | json={"query": query, "pageSize": min(num, 25)}, |
| 367 | headers={"Content-Type": "application/json"}, |
| 368 | timeout=20, |
| 369 | ) |
| 370 | if resp.status_code != 200: |
| 371 | logger.warning(f"Vertex AI Search HTTP {resp.status_code}: " |
| 372 | f"{resp.text[:200]}") |
| 373 | return [] |
| 374 | results = [] |
| 375 | for r in resp.json().get("results", []): |
| 376 | doc = r.get("document", {}) |