{"record":{"id":"0f51253f8ac2ca1f","repo":"infiniflow/ragflow","slug":"serpapi-returned-no-organic-results","errorCode":null,"errorMessage":"SerpApi returned no organic_results.","messagePattern":"SerpApi returned no organic_results\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"agent/tools/google.py","lineNumber":505,"sourceCode":"            return \"\"\n\n        params = {\"api_key\": self._param.api_key, \"engine\": \"google\", \"q\": kwargs[\"q\"], \"google_domain\": \"google.com\", \"gl\": self._param.country, \"hl\": self._param.language}\n        last_e = \"\"\n        for _ in range(self._param.max_retries + 1):\n            if self.check_if_canceled(\"Google processing\"):\n                return\n\n            try:\n                search = GoogleSearch(params).get_dict()\n\n                if self.check_if_canceled(\"Google processing\"):\n                    return\n\n                # serpapi reports an invalid key, exhausted quota or an empty result\n                # set through an \"error\" field and omits \"organic_results\"; surface that\n                # message instead of raising a cryptic KeyError on the missing key.\n                if \"organic_results\" not in search:\n                    raise RuntimeError(search.get(\"error\", \"SerpApi returned no organic_results.\"))\n\n                organic_results = search[\"organic_results\"]\n                # a result may omit any of these; note the fallback of the \"description\"\n                # lookup is evaluated eagerly, so it has to be a .get() too or a result\n                # carrying a description but no snippet raises KeyError.\n                self._retrieve_chunks(\n                    organic_results,\n                    get_title=lambda r: r.get(\"title\", \"\"),\n                    get_url=lambda r: r.get(\"link\", \"\"),\n                    get_content=lambda r: r.get(\"about_this_result\", {}).get(\"source\", {}).get(\"description\", r.get(\"snippet\", \"\")),\n                )\n                self.set_output(\"json\", organic_results)\n                return self.output(\"formalized_content\")\n            except Exception as e:\n                if self.check_if_canceled(\"Google processing\"):\n                    return\n\n                last_e = e","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/agent/tools/google.py#L487-L523","documentation":"Raised as RuntimeError by the Google (SerpApi) search tool when the SerpApi response dict lacks 'organic_results'. SerpApi signals an invalid API key, exhausted quota, or an empty result set through an 'error' field while omitting 'organic_results'; the tool surfaces that error message, and only falls back to the literal 'SerpApi returned no organic_results.' when neither key exists.","triggerScenarios":"GoogleSearch(params).get_dict() with an invalid/expired SERPAPI_API_KEY ('Your account is suspended' / 'Invalid API key'), a plan whose searches/month quota is exhausted, or a legitimate query that simply yields zero organic results (obscure query, num=0, heavy site: filtering).","commonSituations":"Agent canvas Google-search nodes after the SerpApi free trial (100 searches) runs out, rotated keys not updated in the component parameters, geo/gl parameters producing empty result pages, or a typo'd key configured per-node instead of globally.","solutions":["Read the RuntimeError text — 'Invalid API key' or 'Your searches are exhausted' tells you to fix the key or upgrade/top-up the plan at serpapi.com.","Verify the key with a curl 'https://serpapi.com/search?q=test&api_key=KEY' and confirm the response contains organic_results.","If the key is valid and quota remains, loosen the query (remove restrictive site:/filetype: filters, check gl/hl params) so organic results exist.","Cache search responses to avoid burning quota on repeated agent runs."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"def serpapi_key_ok(key: str) -> bool:\n    import requests\n    r = requests.get(\"https://serpapi.com/account\", params={\"api_key\": key}, timeout=10)\n    return r.ok and r.json().get(\"account_rate_limit_per_hour\", 0) >= 0","typeGuard":null,"tryCatchPattern":"try:\n    return google_tool._invoke(query=q)\nexcept RuntimeError as e:\n    if \"organic_results\" in str(e):\n        return \"\"  # zero results: benign\n    log.error(\"serpapi: %s\", e)  # quota/key problem\n    return fallback_search(q)","preventionTips":["Monitor remaining SerpApi searches monthly quota via the account endpoint and alert before exhaustion.","Treat 'no organic_results' with empty error text as empty-result, not failure.","Cache queries so re-runs of the same agent don't burn quota."],"tags":["api","serpapi","search","quota","agent-tool"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}