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);
|
||||
|
||||
// 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,12 +2340,19 @@ 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):`,
|
||||
` "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>`,
|
||||
` "rex" → strategy=keyword, keyword="rex"`,
|
||||
`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:`,
|
||||
|
||||
Reference in New Issue
Block a user