WIP - resolving related look ups for search filtering

This commit is contained in:
Francisco Gaona
2026-04-10 09:07:06 +02:00
parent c89dc04d4c
commit a0bdb09c03

View File

@@ -707,11 +707,21 @@ export class AiAssistantService {
console.log('AI search plan (query):', plan);
// Resolve any LOOKUP filter values (user typed a name, we find the related record ID)
const resolvedFilters = await this.resolveRelatedFilters(
resolvedTenantId,
userId,
objectDefinition,
plan.filters || [],
);
console.log('Resolved filters for query strategy:', resolvedFilters);
const filtered = await this.objectService.searchRecordsWithFilters(
resolvedTenantId,
payload.objectApiName,
userId,
plan.filters || [],
resolvedFilters,
{ page, pageSize },
plan.sort || undefined,
);
@@ -2207,6 +2217,59 @@ export class AiAssistantService {
return extracted;
}
/**
* Resolves LOOKUP filter values from human-readable names to actual record IDs.
* When the AI produces a filter like { field: "familyId", operator: "eq", value: "Gaona Family" },
* this method looks up "Gaona Family" in the referenced object and replaces the value with its ID.
*/
private async resolveRelatedFilters(
tenantId: string,
userId: string,
objectDefinition: any,
filters: AiSearchFilter[],
): Promise<AiSearchFilter[]> {
if (!filters || filters.length === 0) return filters;
const lookupFieldMap = new Map<string, string>(); // apiName → referenceObject
for (const field of objectDefinition.fields || []) {
if (field.type === 'LOOKUP' && field.referenceObject) {
lookupFieldMap.set(field.apiName, field.referenceObject);
}
}
const resolved: AiSearchFilter[] = [];
for (const filter of filters) {
const referenceObject = lookupFieldMap.get(filter.field);
if (
referenceObject &&
filter.value &&
typeof filter.value === 'string' &&
!this.isUuid(filter.value)
) {
// Try to resolve the name to an ID in the referenced object
try {
console.log(`Resolving LOOKUP filter: ${filter.field} → searching "${filter.value}" in ${referenceObject}`);
const relatedRecord = await this.searchForExistingRecord(tenantId, userId, referenceObject, filter.value);
if (relatedRecord?.id) {
console.log(`Resolved "${filter.value}" → ID: ${relatedRecord.id}`);
resolved.push({ ...filter, operator: 'eq', value: relatedRecord.id });
} else {
// Could not resolve; keep as-is so we get 0 results rather than wrong ones
console.warn(`Could not resolve related record "${filter.value}" in ${referenceObject}; keeping original filter`);
resolved.push(filter);
}
} catch (err) {
this.logger.warn(`Failed to resolve lookup filter for ${filter.field}: ${err.message}`);
resolved.push(filter);
}
} else {
resolved.push(filter);
}
}
return resolved;
}
private async buildSearchPlan(
tenantId: string,
message: string,
@@ -2250,6 +2313,7 @@ export class AiAssistantService {
apiName: field.apiName,
label: field.label,
type: field.type,
...(field.referenceObject ? { referenceObject: field.referenceObject } : {}),
}));
const formatInstructions = parser.getFormatInstructions();
@@ -2276,13 +2340,20 @@ export class AiAssistantService {
`and apply a "contains" filter (or "eq" for exact values). Do NOT fall back to keyword just because the`,
`field name is not explicitly mentioned — infer the intent from context.`,
``,
`EXAMPLES (Object = Dog, fields include: name, race, size, color, createdAt):`,
`EXAMPLES (Object = Dog, fields include: name, race, size, color, createdAt, familyId[LOOKUP→Family]):`,
` "list all cocker spaniels" → strategy=query, filter: race contains "cocker spaniel"`,
` "show golden retrievers" → strategy=query, filter: race contains "golden retriever"`,
` "large dogs" → strategy=query, filter: size contains "large"`,
` "dogs added this week" → strategy=query, filter: createdAt between <monday> <today>`,
` "dogs belonging to Gaona Family" → strategy=query, filter: familyId eq "Gaona Family"`,
` "rex" → strategy=keyword, keyword="rex"`,
``,
`=== LOOKUP FIELDS ===`,
`Fields with type LOOKUP store a reference (ID) to another object (referenceObject).`,
`When the user refers to a related record by its name (e.g. "belonging to Gaona Family"),`,
`output the human-readable name as the filter value — the system will resolve it to the correct ID.`,
`Use the LOOKUP field apiName (e.g. familyId) as the filter field.`,
``,
`=== OUTPUT FORMAT ===`,
`Return a JSON object with these keys:`,
` strategy : "keyword" or "query"`,