WIP - improve list filtering explanation

This commit is contained in:
Francisco Gaona
2026-04-09 21:46:29 +02:00
parent 7a175923b0
commit c89dc04d4c

View File

@@ -49,7 +49,7 @@ type AiSearchPayload = {
@Injectable()
export class AiAssistantService {
private readonly logger = new Logger(AiAssistantService.name);
private readonly defaultModel = process.env.OPENAI_MODEL || 'gpt-4o';
private readonly defaultModel = process.env.OPENAI_MODEL || 'gpt-5.4-mini';
private readonly conversationState = new Map<
string,
{ fields: Record<string, any>; updatedAt: number }
@@ -2255,20 +2255,52 @@ export class AiAssistantService {
const formatInstructions = parser.getFormatInstructions();
const today = new Date().toISOString();
const systemPromptLines = [
`You are a CRM search assistant. The user is browsing a list of "${objectDefinition.label || objectDefinition.apiName}" records.`,
``,
`Your task: decide whether the user input is a KEYWORD search or a STRUCTURED QUERY, then return the correct JSON plan.`,
``,
`=== STRATEGY DECISION RULES ===`,
`Use strategy="query" when the user:`,
` - Describes a record attribute or property value (e.g. "list all golden retrievers" → filter the race field)`,
` - Uses phrases like "list all X", "show all X", "find all X", "filter by X", "where X is Y"`,
` - Mentions a specific field value, status, category, type, breed, color, size, or any distinguishing characteristic`,
` - Requests records matching a condition (e.g. "created this week", "price > 100", "active only")`,
` - Asks for sorted or ordered results (e.g. "newest first", "alphabetically")`,
``,
`Use strategy="keyword" ONLY when the user types a bare search term with no clear field or attribute intent`,
` - e.g. a lone name or identifier: "rex", "john", "acme corp"`,
``,
`=== HOW TO MAP USER LANGUAGE TO FIELDS ===`,
`When the user describes a characteristic or property value, find the best-matching field from the Fields list`,
`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"`,
``,
`=== OUTPUT FORMAT ===`,
`Return a JSON object with these keys:`,
` strategy : "keyword" or "query"`,
` explanation : short plain-language description of what the search does`,
` keyword : the search term when strategy is "keyword", otherwise null`,
` filters : array of filter objects for strategy="query" (empty array otherwise)`,
` sort : {field, direction} when sorting is requested, otherwise null`,
``,
`Each filter object: { field: <apiName>, operator: <op>, value?, values?, from?, to? }`,
`Valid operators: eq | neq | gt | gte | lt | lte | contains | startsWith | endsWith | in | notIn | isNull | notNull | between`,
`Use "between" with {from, to} for date ranges.`,
`Only use field apiName values exactly as they appear in the Fields list provided.`,
``,
formatInstructions,
].join('\n');
const response = await model.invoke([
new SystemMessage(
`You are a CRM search assistant. Decide whether the user input is a keyword search or a structured query.` +
`\nReturn a JSON object with keys: strategy, explanation, keyword, filters, sort.` +
`\n- strategy must be "keyword" or "query".` +
`\n- explanation must be a short sentence explaining the approach.` +
`\n- keyword should be the search term when strategy is "keyword", otherwise null.` +
`\n- filters is an array of {field, operator, value, values, from, to}.` +
`\n- operators must be one of eq, neq, gt, gte, lt, lte, contains, startsWith, endsWith, in, notIn, isNull, notNull, between.` +
`\n- Use between with from/to when the user gives date ranges like "yesterday" or "last week".` +
`\n- sort should be {field, direction} when sorting is requested.` +
`\n- Only use field apiName values exactly as provided.` +
`\n${formatInstructions}`,
),
new SystemMessage(systemPromptLines),
new HumanMessage(
`Object: ${objectDefinition.label || objectDefinition.apiName}.\n` +
`Fields: ${JSON.stringify(fields)}.\n` +
@@ -2281,6 +2313,8 @@ export class AiAssistantService {
const parsed = await parser.parse(content);
const normalizedPlan = this.normalizeSearchPlan(parsed, message, objectDefinition);
console.log('AI search plan:', normalizedPlan);
if (normalizedPlan.strategy === 'query') {
const aiExplanation = await this.generateQueryExplanationWithAi(
model,
@@ -2462,6 +2496,9 @@ export class AiAssistantService {
.join(' ')
: String(response.content || '');
const cleaned = content.trim().replace(/\s+/g, ' ');
console.log('AI-generated query explanation:', cleaned);
return cleaned || null;
} catch (error) {
this.logger.warn(`AI query explanation refinement failed: ${error.message}`);