WIP - resolving related look ups for search filtering
This commit is contained in:
@@ -707,11 +707,21 @@ export class AiAssistantService {
|
|||||||
|
|
||||||
console.log('AI search plan (query):', plan);
|
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(
|
const filtered = await this.objectService.searchRecordsWithFilters(
|
||||||
resolvedTenantId,
|
resolvedTenantId,
|
||||||
payload.objectApiName,
|
payload.objectApiName,
|
||||||
userId,
|
userId,
|
||||||
plan.filters || [],
|
resolvedFilters,
|
||||||
{ page, pageSize },
|
{ page, pageSize },
|
||||||
plan.sort || undefined,
|
plan.sort || undefined,
|
||||||
);
|
);
|
||||||
@@ -2207,6 +2217,59 @@ export class AiAssistantService {
|
|||||||
return extracted;
|
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(
|
private async buildSearchPlan(
|
||||||
tenantId: string,
|
tenantId: string,
|
||||||
message: string,
|
message: string,
|
||||||
@@ -2250,6 +2313,7 @@ export class AiAssistantService {
|
|||||||
apiName: field.apiName,
|
apiName: field.apiName,
|
||||||
label: field.label,
|
label: field.label,
|
||||||
type: field.type,
|
type: field.type,
|
||||||
|
...(field.referenceObject ? { referenceObject: field.referenceObject } : {}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const formatInstructions = parser.getFormatInstructions();
|
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`,
|
`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.`,
|
`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"`,
|
` "list all cocker spaniels" → strategy=query, filter: race contains "cocker spaniel"`,
|
||||||
` "show golden retrievers" → strategy=query, filter: race contains "golden retriever"`,
|
` "show golden retrievers" → strategy=query, filter: race contains "golden retriever"`,
|
||||||
` "large dogs" → strategy=query, filter: size contains "large"`,
|
` "large dogs" → strategy=query, filter: size contains "large"`,
|
||||||
` "dogs added this week" → strategy=query, filter: createdAt between <monday> <today>`,
|
` "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"`,
|
` "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 ===`,
|
`=== OUTPUT FORMAT ===`,
|
||||||
`Return a JSON object with these keys:`,
|
`Return a JSON object with these keys:`,
|
||||||
` strategy : "keyword" or "query"`,
|
` strategy : "keyword" or "query"`,
|
||||||
|
|||||||
Reference in New Issue
Block a user