{"record":{"id":"74e426408861bb54","repo":"alibaba/nacos","slug":"ai-resource-channel-recall-exceeded-configured-c","errorCode":null,"errorMessage":"AI resource {channel} recall exceeded configured candidate limit {limit}","messagePattern":"AI resource (.+?) recall exceeded configured candidate limit (.+?)","errorType":"exception","errorClass":"NacosException","httpStatus":500,"severity":"critical","filePath":"ai/src/main/java/com/alibaba/nacos/ai/service/search/AiResourceSearchService.java","lineNumber":344,"sourceCode":"                maxCandidates + 1));\n            ensureWithinRecallLimit(vectorHits, maxCandidates, \"vector\");\n            for (AiResourceSearchHit hit : vectorHits) {\n                recordMaxScore(scores, hit);\n            }\n        }\n        List<AiResourceSearchHit> keywordHits = repository.searchChunks(query.getNamespaceId(),\n            query.getText(), query.getResourceTypes(), maxCandidates + 1);\n        ensureWithinRecallLimit(keywordHits, maxCandidates, \"keyword\");\n        for (AiResourceSearchHit hit : keywordHits) {\n            recordMaxScore(scores, hit);\n        }\n        return scores;\n    }\n    \n    private void ensureWithinRecallLimit(List<AiResourceSearchHit> hits, int limit, String channel)\n        throws NacosException {\n        if (hits != null && hits.size() > limit) {\n            throw new NacosException(NacosException.SERVER_ERROR,\n                \"AI resource \" + channel + \" recall exceeded configured candidate limit \"\n                    + limit);\n        }\n    }\n    \n    private List<AiResourceSearchDocument> findDocumentsByIds(Collection<Long> documentIds) {\n        if (documentIds == null || documentIds.isEmpty()) {\n            return Collections.emptyList();\n        }\n        List<Long> ids = new ArrayList<>(documentIds);\n        List<AiResourceSearchDocument> result = new ArrayList<>();\n        for (int offset = 0; offset < ids.size(); offset += DOCUMENT_LOOKUP_BATCH_SIZE) {\n            int toIndex = Math.min(offset + DOCUMENT_LOOKUP_BATCH_SIZE, ids.size());\n            result.addAll(repository.findEntriesByIds(ids.subList(offset, toIndex)));\n        }\n        return result;\n    }\n    ","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/ai/src/main/java/com/alibaba/nacos/ai/service/search/AiResourceSearchService.java#L326-L362","documentation":"A SERVER_ERROR (HTTP 500) from ensureWithinRecallLimit: a search recall channel (vector or keyword) returned more hits than the configured candidate ceiling. The service deliberately requests limit+1 from the repository so that exceeding the limit is detectable; breaching it means the storage/vector index did not honor the LIMIT clause. The ceiling defaults to 10000 and is tunable via nacos.ai.resource.search.max-recall-candidates.","triggerScenarios":"Any search query while the active AiResourceSearchRepository or AiResourceVectorIndex returns an unbounded/larger-than-requested result set, or while max-recall-candidates is set below the real recall volume. Raised for the 'vector' channel when the vector index over-returns and for 'keyword' when the chunk repository over-returns.","commonSituations":"A new or updated storage-dialect plugin whose LIMIT handling is broken; a vector index implementation changed to return the full top-k set; someone set max-recall-candidates to an absurdly small number; very large corpus plus a broken SQL LIMIT for the dialect.","solutions":["Check the configured value of nacos.ai.resource.search.max-recall-candidates and raise it if it was set too low.","Inspect the active AiResourceSearchRepository / AiResourceVectorIndex implementation for the configured storage dialect and confirm it applies LIMIT correctly.","If the value is sane and recall still exceeds it, treat it as a defect in the storage/vector plugin and file a bug.","As a stopgap, narrow the query (resourceTypes filter) to reduce recall volume."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    Page result = searchService.search(query);\n} catch (NacosException e) {\n    if (e.getErrCode() == NacosException.SERVER_ERROR\n            && e.getErrMsg().contains(\"recall exceeded configured candidate limit\")) {\n        // degrade: narrow resourceTypes filter or fall back to keyword-only/empty result\n        return degradedSearch(query);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Keep nacos.ai.resource.search.max-recall-candidates at or above expected recall volume.","Verify any new storage-dialect/vector-index plugin honors LIMIT before enabling.","Re-test search after dialect or vector-index upgrades.","Do not set max-recall-candidates to an artificially tiny value."],"tags":["search","recall","server-error","configuration","vector","storage-dialect"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}