Skip to content

Commit 9e34db6

Browse files
committed
Fix several errors in search matching algorithm
1 parent 4705e6a commit 9e34db6

File tree

4 files changed

+180
-111
lines changed

4 files changed

+180
-111
lines changed

typescript/packages/toolshed/lib/schema-match.ts

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,65 @@ export function checkSchemaMatch(
44
data: Record<string, unknown>,
55
schema: Schema,
66
): boolean {
7-
const validator = new Validator();
8-
9-
const jsonSchema: unknown = {
10-
type: "object",
11-
properties: Object.keys(schema).reduce(
12-
(acc: Record<string, unknown>, key) => {
13-
const schemaValue = schema[key as keyof Schema];
14-
acc[key] = { type: (schemaValue as any)?.type || typeof schemaValue };
15-
return acc;
16-
},
17-
{},
18-
),
19-
required: Object.keys(schema),
20-
additionalProperties: true,
21-
};
22-
23-
const rootResult = validator.validate(data, jsonSchema as Schema);
24-
if (rootResult.valid) {
25-
return true;
26-
}
7+
try {
8+
const validator = new Validator();
9+
10+
const jsonSchema: unknown = {
11+
type: "object",
12+
properties: Object.keys(schema).reduce(
13+
(acc: Record<string, unknown>, key) => {
14+
try {
15+
const schemaValue = schema[key as keyof Schema];
16+
acc[key] = {
17+
type: (schemaValue as any)?.type || typeof schemaValue,
18+
};
19+
return acc;
20+
} catch (err) {
21+
console.error(`Error reducing schema key ${key}:`, err);
22+
return acc;
23+
}
24+
},
25+
{},
26+
),
27+
required: Object.keys(schema),
28+
additionalProperties: true,
29+
};
2730

28-
function checkSubtrees(obj: unknown): boolean {
29-
if (typeof obj !== "object" || obj === null) {
31+
try {
32+
const rootResult = validator.validate(data, jsonSchema as Schema);
33+
if (rootResult.valid) {
34+
return true;
35+
}
36+
} catch (err) {
37+
console.error("Error validating root schema:", err);
3038
return false;
3139
}
3240

33-
if (Array.isArray(obj)) {
34-
return obj.some((item) => checkSubtrees(item));
35-
}
41+
function checkSubtrees(obj: unknown): boolean {
42+
try {
43+
if (typeof obj !== "object" || obj === null) {
44+
return false;
45+
}
3646

37-
const result = validator.validate(obj, jsonSchema as Schema);
38-
if (result.valid) {
39-
return true;
47+
if (Array.isArray(obj)) {
48+
return obj.some((item) => checkSubtrees(item));
49+
}
50+
51+
const result = validator.validate(obj, jsonSchema as Schema);
52+
if (result.valid) {
53+
return true;
54+
}
55+
56+
return Object.values(obj).some((value) => checkSubtrees(value));
57+
} catch (err) {
58+
console.error("Error checking subtrees:", err);
59+
return false;
60+
}
4061
}
4162

42-
return Object.values(obj).some((value) => checkSubtrees(value));
63+
return checkSubtrees(data);
64+
} catch (err) {
65+
console.error("Top level error in checkSchemaMatch:", err);
66+
return false;
4367
}
44-
45-
return checkSubtrees(data);
4668
}

typescript/packages/toolshed/routes/ai/spell/behavior/spell-search.ts

Lines changed: 106 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { checkSchemaMatch } from "@/lib/schema-match.ts";
2+
import { Spell, SpellSchema } from "../spell.ts";
3+
import { Schema } from "jsonschema";
4+
15
interface SpellSearchResult {
26
spells: Array<{
37
key: string;
@@ -26,7 +30,7 @@ interface SpellSearchParams {
2630
query: string;
2731
tags?: string[];
2832
referencedKeys: string[];
29-
spells: Record<string, Record<string, unknown>>;
33+
spells: Record<string, Spell>;
3034
blobs: Record<string, Record<string, unknown>>;
3135
options?: {
3236
limit?: number;
@@ -45,7 +49,6 @@ function calculateRank(itemStr: string, tags: string[] = []): number {
4549

4650
return rank;
4751
}
48-
4952
export function processSpellSearch({
5053
query,
5154
tags = [],
@@ -54,75 +57,107 @@ export function processSpellSearch({
5457
blobs,
5558
options = {},
5659
}: SpellSearchParams): SpellSearchResult {
57-
const spellMatches: SpellSearchResult["spells"] = [];
58-
const blobMatches: SpellSearchResult["blobs"] = [];
59-
60-
const limit = options.limit || 10;
61-
const searchTerms = query.toLowerCase().replace(/@\w+/g, "").trim();
62-
63-
// Handle @references first
64-
for (const key of referencedKeys) {
65-
const spellKey = `spell-${key}`;
66-
const blobKey = key;
67-
68-
// Check for referenced spells
69-
if (spells[spellKey]) {
70-
spellMatches.push({
71-
key: spellKey,
72-
name: spells[spellKey].name as string || spellKey,
73-
description: spells[spellKey].description as string || "No description",
74-
matchType: "reference",
75-
compatibleBlobs: findCompatibleBlobs(spells[spellKey], blobs),
76-
});
77-
}
60+
try {
61+
const spellMatches: SpellSearchResult["spells"] = [];
62+
const blobMatches: SpellSearchResult["blobs"] = [];
7863

79-
// Check for referenced blobs
80-
if (blobs[blobKey] && !blobKey.startsWith("spell-")) {
81-
blobMatches.push({
82-
key: blobKey,
83-
snippet: getRelevantSnippet(JSON.stringify(blobs[blobKey])),
84-
matchType: "reference",
85-
compatibleSpells: findCompatibleSpells(blobs[blobKey], spells),
86-
});
64+
console.log(spells);
65+
66+
// Parse spells using schema
67+
const validSpells: Record<string, Spell> = {};
68+
for (const [key, spell] of Object.entries(spells)) {
69+
try {
70+
const parsed = SpellSchema.parse(spell);
71+
validSpells[key] = parsed;
72+
console.log("checked spell", parsed);
73+
} catch (error: any) {
74+
console.log(`Invalid spell ${key}:`, error.message);
75+
continue;
76+
}
8777
}
88-
}
8978

90-
// Perform text search if we haven't hit the limit
91-
if (searchTerms) {
92-
// Search spells with tags
93-
if (spellMatches.length < limit) {
94-
const textSpellMatches = searchSpells(
95-
searchTerms,
96-
spells,
97-
blobs,
98-
limit - spellMatches.length,
99-
tags,
100-
);
101-
spellMatches.push(...textSpellMatches);
79+
console.log("Valid spells:", Object.keys(validSpells));
80+
81+
const limit = options.limit || 10;
82+
const searchTerms = query.toLowerCase().replace(/@\w+/g, "").trim();
83+
84+
// Handle @references first
85+
for (const key of referencedKeys) {
86+
const spellKey = `spell-${key}`;
87+
const blobKey = key;
88+
89+
// Check for referenced spells
90+
if (validSpells[spellKey]) {
91+
spellMatches.push({
92+
key: spellKey,
93+
name: validSpells[spellKey].spellbookTitle as string || spellKey,
94+
description:
95+
validSpells[spellKey].recipe.argumentSchema.description as string ||
96+
"No description",
97+
matchType: "reference",
98+
compatibleBlobs: findCompatibleBlobs(validSpells[spellKey], blobs),
99+
});
100+
}
101+
102+
// Check for referenced blobs
103+
if (blobs[blobKey] && !blobKey.startsWith("spell-")) {
104+
blobMatches.push({
105+
key: blobKey,
106+
snippet: getRelevantSnippet(JSON.stringify(blobs[blobKey])),
107+
matchType: "reference",
108+
compatibleSpells: findCompatibleSpells(blobs[blobKey], validSpells),
109+
});
110+
}
102111
}
103112

104-
// Search blobs with tags
105-
if (blobMatches.length < limit) {
106-
const textBlobMatches = searchBlobs(
107-
searchTerms,
108-
blobs,
109-
spells,
110-
limit - blobMatches.length,
111-
tags,
112-
);
113-
blobMatches.push(...textBlobMatches);
113+
// Perform text search if we haven't hit the limit
114+
if (searchTerms) {
115+
// Search spells with tags
116+
if (spellMatches.length < limit) {
117+
const textSpellMatches = searchSpells(
118+
searchTerms,
119+
validSpells,
120+
blobs,
121+
limit - spellMatches.length,
122+
tags,
123+
);
124+
spellMatches.push(...textSpellMatches);
125+
}
126+
127+
// Search blobs with tags
128+
if (blobMatches.length < limit) {
129+
const textBlobMatches = searchBlobs(
130+
searchTerms,
131+
blobs,
132+
validSpells,
133+
limit - blobMatches.length,
134+
tags,
135+
);
136+
blobMatches.push(...textBlobMatches);
137+
}
114138
}
115-
}
116139

117-
return {
118-
spells: spellMatches.slice(0, limit),
119-
blobs: blobMatches.slice(0, limit),
120-
};
140+
return {
141+
spells: spellMatches.slice(0, limit),
142+
blobs: blobMatches.slice(0, limit),
143+
};
144+
} catch (error) {
145+
console.error("Error in processSpellSearch:", {
146+
error,
147+
errorMessage: error instanceof Error ? error.message : String(error),
148+
errorStack: error instanceof Error ? error.stack : undefined,
149+
query,
150+
referencedKeys,
151+
spellKeys: Object.keys(spells),
152+
blobKeys: Object.keys(blobs),
153+
});
154+
return { spells: [], blobs: [] };
155+
}
121156
}
122157

123158
function searchSpells(
124159
searchTerms: string,
125-
spells: Record<string, Record<string, unknown>>,
160+
spells: Record<string, Spell>,
126161
blobs: Record<string, Record<string, unknown>>,
127162
limit: number,
128163
tags: string[] = [],
@@ -135,8 +170,9 @@ function searchSpells(
135170
const rank = calculateRank(spellStr, tags);
136171
matches.push({
137172
key,
138-
name: spell.name as string || key,
139-
description: spell.description as string || "No description",
173+
name: spell.spellbookTitle as string || key,
174+
description: spell.recipe.argumentSchema?.description as string ||
175+
"No description",
140176
matchType: "text-match" as const,
141177
compatibleBlobs: findCompatibleBlobs(spell, blobs),
142178
rank,
@@ -153,7 +189,7 @@ function searchSpells(
153189
function searchBlobs(
154190
searchTerms: string,
155191
blobs: Record<string, Record<string, unknown>>,
156-
spells: Record<string, Record<string, unknown>>,
192+
spells: Record<string, Spell>,
157193
limit: number,
158194
tags: string[] = [],
159195
): SpellSearchResult["blobs"] {
@@ -183,7 +219,7 @@ function searchBlobs(
183219

184220
function findCompatibleSpells(
185221
blob: Record<string, unknown>,
186-
spells: Record<string, Record<string, unknown>>,
222+
spells: Record<string, Spell>,
187223
): Array<{ key: string; name: string; description: string }> {
188224
const compatible: Array<{ key: string; name: string; description: string }> =
189225
[];
@@ -192,13 +228,14 @@ function findCompatibleSpells(
192228
for (const [key, spell] of Object.entries(spells)) {
193229
const spellStr = JSON.stringify(spell).toLowerCase();
194230
if (
195-
hasMatchingSchema(spell, blob) ||
231+
checkSchemaMatch(blob, spell.recipe.argumentSchema.properties) ||
196232
spellStr.includes(blob.key?.toString().toLowerCase() || "")
197233
) {
198234
compatible.push({
199235
key,
200-
name: spell.name as string || key,
201-
description: spell.description as string || "No description",
236+
name: spell.spellbookTitle as string || key,
237+
description: spell.recipe.argumentSchema.description as string ||
238+
"No description",
202239
});
203240
}
204241
}
@@ -207,7 +244,7 @@ function findCompatibleSpells(
207244
}
208245

209246
function findCompatibleBlobs(
210-
spell: Record<string, unknown>,
247+
spell: Spell,
211248
blobs: Record<string, Record<string, unknown>>,
212249
): Array<{ key: string; snippet: string; data: unknown }> {
213250
const compatible: Array<{ key: string; snippet: string; data: unknown }> = [];
@@ -218,7 +255,7 @@ function findCompatibleBlobs(
218255

219256
const blobStr = JSON.stringify(blob).toLowerCase();
220257
if (
221-
hasMatchingSchema(spell, blob) ||
258+
checkSchemaMatch(blob, spell.recipe.argumentSchema.properties) ||
222259
spellStr.includes(key.toLowerCase())
223260
) {
224261
compatible.push({

typescript/packages/toolshed/routes/ai/spell/caster.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,13 @@ import { checkSchemaMatch } from "@/lib/schema-match.ts";
22
import { isObject } from "@/routes/ai/spell/schema.ts";
33
import { Schema } from "jsonschema";
44
import { z } from "zod";
5+
import { Recipe, RecipeSchema } from "./spell.ts";
56

67
export interface SchemaCandidate {
78
key: string;
89
similarity: number;
910
}
1011

11-
export const SpellSchema = z.object({
12-
src: z.string(),
13-
recipe: z.object({
14-
argumentSchema: z.record(z.unknown()),
15-
resultSchema: z.record(z.unknown()),
16-
}),
17-
});
18-
19-
export type Spell = z.infer<typeof SpellSchema>;
2012
export interface SchemaAnalysis {
2113
data: string[];
2214
consumes: string[];
@@ -49,10 +41,10 @@ export function candidates(
4941
const produces: SchemaCandidate[] = [];
5042

5143
// Parse spells using schema
52-
const validSpells: Record<string, Spell> = {};
44+
const validSpells: Record<string, Recipe> = {};
5345
for (const [key, spell] of Object.entries(spells)) {
5446
try {
55-
const parsed = SpellSchema.parse(spell);
47+
const parsed = RecipeSchema.parse(spell);
5648
validSpells[key] = parsed;
5749
} catch (error: any) {
5850
console.log(`Invalid spell ${key}:`, error.message);

0 commit comments

Comments
 (0)