mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
fix(search): bound rolling usage query batches (#3456)
This commit is contained in:
@@ -5,5 +5,5 @@ export const CANONICAL_SKILL_SEARCH_BOUNDS = {
|
||||
externalCandidateLimitPerIndex: 50,
|
||||
externalIndexedReadCount: 6,
|
||||
rollingAdoptionDays: 60,
|
||||
rollingUsageBatchSize: 40,
|
||||
rollingUsageBatchSize: 20,
|
||||
} as const;
|
||||
|
||||
+52
-2
@@ -1295,12 +1295,12 @@ describe("search helpers", () => {
|
||||
getRollingSkillSearchUsageHandler(
|
||||
{ db: { query: vi.fn() } },
|
||||
{
|
||||
skillIds: Array.from({ length: 41 }, (_, index) => `skills:${index}`),
|
||||
skillIds: Array.from({ length: 21 }, (_, index) => `skills:${index}`),
|
||||
startDay: 100,
|
||||
endDay: 159,
|
||||
},
|
||||
),
|
||||
).rejects.toThrow("skillIds exceeds 40");
|
||||
).rejects.toThrow("skillIds exceeds 20");
|
||||
});
|
||||
|
||||
it("returns one ordered native and external contract with canonical routes and install refs", async () => {
|
||||
@@ -1370,6 +1370,56 @@ describe("search helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves rolling adoption ranking when usage reads require smaller transactions", async () => {
|
||||
generateEmbeddingMock.mockRejectedValueOnce(new Error("embedding unavailable"));
|
||||
const native = Array.from({ length: 100 }, (_, index) => ({
|
||||
skill: makePublicSkill({
|
||||
id: `skills:calendar-${index}`,
|
||||
slug: `calendar-${index}`,
|
||||
displayName: `Calendar ${index}`,
|
||||
}),
|
||||
version: null,
|
||||
ownerHandle: "openclaw",
|
||||
owner: null,
|
||||
}));
|
||||
const runQuery = vi.fn(
|
||||
async (ref: Parameters<typeof getFunctionName>[0], args?: { skillIds?: string[] }) => {
|
||||
switch (getFunctionName(ref)) {
|
||||
case "search:getExactSkillSlugMatch":
|
||||
case "search:lexicalFallbackSkills":
|
||||
case "search:getExternalSkillSearchCandidates":
|
||||
return [];
|
||||
case "search:directPrefixSkillMatches":
|
||||
return native;
|
||||
case "search:getRollingSkillSearchUsage": {
|
||||
const skillIds = args?.skillIds ?? [];
|
||||
if (skillIds.length > 20) {
|
||||
throw new Error("Function execution timed out (maximum duration: 1s)");
|
||||
}
|
||||
return skillIds.map((skillId) => ({
|
||||
skillId,
|
||||
installs: skillId === "skills:calendar-99" ? 10_000 : 1,
|
||||
bookmarks: 0,
|
||||
}));
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unexpected query ${getFunctionName(ref)}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const result = await canonicalSearchSkillsHandler(
|
||||
{ runQuery, vectorSearch: vi.fn() },
|
||||
{ query: "calendar", limit: 100 },
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(100);
|
||||
expect(result[0]).toMatchObject({
|
||||
id: "clawhub:skills:calendar-99",
|
||||
metrics: { rolling60DayInstalls: 10_000 },
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps exact mode deterministic across canonical sources", async () => {
|
||||
const native = {
|
||||
skill: makePublicSkill({
|
||||
|
||||
+2
-2
@@ -664,8 +664,8 @@ type CanonicalSkillSearchResult = {
|
||||
const CANONICAL_NATIVE_CANDIDATE_LIMIT = CANONICAL_SKILL_SEARCH_BOUNDS.nativeCandidateLimit;
|
||||
const CANONICAL_RESULT_LIMIT_MAX = CANONICAL_SKILL_SEARCH_BOUNDS.resultLimit;
|
||||
const ROLLING_ADOPTION_DAYS = CANONICAL_SKILL_SEARCH_BOUNDS.rollingAdoptionDays;
|
||||
// Forty candidates can read at most 2,400 daily rows, leaving headroom below
|
||||
// Convex's per-transaction document/byte limits for imported production-shaped data.
|
||||
// Twenty candidates read at most 1,200 daily rows. Keep this well below the
|
||||
// query CPU ceiling: production-shaped 40-skill batches had intermittent 1s timeouts.
|
||||
const ROLLING_USAGE_QUERY_BATCH_SIZE = CANONICAL_SKILL_SEARCH_BOUNDS.rollingUsageBatchSize;
|
||||
|
||||
function chunkValues<T>(values: T[], size: number) {
|
||||
|
||||
@@ -342,8 +342,9 @@ describe("clawhub e2e", () => {
|
||||
const response = await fetchWithTimeout(url.toString(), {
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
expect(response.ok).toBe(true);
|
||||
const json = (await response.json()) as unknown;
|
||||
const body = await response.text();
|
||||
expect(response.ok, `search failed with ${response.status}: ${body}`).toBe(true);
|
||||
const json = JSON.parse(body) as unknown;
|
||||
const parsed = parseArk(ApiV1SearchResponseSchema, json, "API response");
|
||||
expect(Array.isArray(parsed.results)).toBe(true);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user