Skip to content

Commit a7b65c5

Browse files
authored
Memory improvements (#1437)
* Scope query schema: bug fixes * Memory: settings flag to allow auto experimentation of natural language queries with enhanced scope ("where") detection * Code for fuzzier comparisons of test output
1 parent b0ca9c9 commit a7b65c5

File tree

9 files changed

+204
-56
lines changed

9 files changed

+204
-56
lines changed

ts/examples/chat/src/memory/knowproDoc.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export async function createKnowproDocMemoryCommands(
7070
),
7171
buildIndex: argBool("Index the imported podcast", true),
7272
v2: argBool("Use v2 knowledge extraction", false),
73+
scoped: argBool("Use scoped queries", true),
7374
},
7475
};
7576
}
@@ -84,7 +85,7 @@ export async function createKnowproDocMemoryCommands(
8485
namedArgs.filePath,
8586
namedArgs.maxCharsPerChunk,
8687
undefined,
87-
kpContext.createMemorySettings(),
88+
createDocMemorySettings(namedArgs.scoped),
8889
);
8990
kpContext.conversation = context.docMemory;
9091
writeDocInfo(context.docMemory);
@@ -101,6 +102,7 @@ export async function createKnowproDocMemoryCommands(
101102
description: "Load existing Doc memory",
102103
options: {
103104
filePath: argSourceFile(),
105+
scoped: argBool("Use scoped queries", true),
104106
},
105107
};
106108
}
@@ -118,7 +120,7 @@ export async function createKnowproDocMemoryCommands(
118120
const docMemory = await cm.DocMemory.readFromFile(
119121
path.dirname(memoryFilePath),
120122
getFileName(memoryFilePath),
121-
kpContext.createMemorySettings(),
123+
createDocMemorySettings(namedArgs.scoped),
122124
);
123125
clock.stop();
124126

@@ -234,5 +236,13 @@ export async function createKnowproDocMemoryCommands(
234236
}
235237
}
236238
}
239+
240+
function createDocMemorySettings(useScoped: boolean) {
241+
const settings = kpContext.createMemorySettings();
242+
if (useScoped !== undefined && useScoped === true) {
243+
settings.useScopedSearch = true;
244+
}
245+
return settings;
246+
}
237247
return;
238248
}

ts/examples/chat/src/memory/knowproTest.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -557,15 +557,7 @@ export async function createKnowproTestCommands(
557557
(result, index, total) => {
558558
context.printer.writeProgress(index + 1, total);
559559
if (result.success) {
560-
const cmp = result.data;
561-
if (cmp.error) {
562-
writeSearchComparison(cmp);
563-
} else {
564-
context.printer.writeLineInColor(
565-
chalk.green,
566-
cmp.query,
567-
);
568-
}
560+
writeSearchComparison(result.data);
569561
} else {
570562
context.printer.writeError(result.message);
571563
}
@@ -690,11 +682,16 @@ export async function createKnowproTestCommands(
690682
}
691683

692684
function writeSearchComparison(cmp: kpTest.ScopeQueryComparison): void {
693-
context.printer.writeJsonInColor(chalk.gray, cmp.expected);
694-
context.printer.writeHeading("Scope");
695-
context.printer.writeJson(cmp.actual);
696685
if (cmp.error) {
697-
context.printer.writeError(cmp.error);
686+
context.printer.writeLineInColor(chalk.redBright, cmp.query);
687+
context.printer.writeJsonInColor(chalk.green, cmp.expected);
688+
context.printer.writeHeading("Scope");
689+
context.printer.writeJsonInColor(chalk.red, cmp.actual);
690+
if (cmp.error) {
691+
context.printer.writeError(cmp.error);
692+
}
693+
} else {
694+
context.printer.writeLineInColor(chalk.greenBright, cmp.query);
698695
}
699696
}
700697

ts/examples/examplesLib/src/chalkWriter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export class ChalkWriter extends ConsoleWriter {
182182

183183
public writePromptSection(prompt: PromptSection) {
184184
if (prompt.content) {
185-
this.writeLine(prompt.role);
185+
this.writeLine(`[Role: ${prompt.role}]`);
186186
if (typeof prompt.content === "string") {
187187
this.writeLine(prompt.content);
188188
} else {

ts/packages/knowPro/src/searchLang.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,9 @@ export async function searchQueryExprFromLanguage2(
10891089

10901090
/**
10911091
* Experimental...
1092-
* Allows NLP to specify scoping.
1092+
* Uses an improved schema to better capture "scope" clauses from natural language queries
1093+
* Scopes can now be explicitly specified as 'sub-queries'... scope filters. This leads to a more
1094+
* robust "where" operator
10931095
*
10941096
* Search a conversation using natural language. Returns {@link ConversationSearchResult} containing
10951097
* relevant knowledge and messages.

ts/packages/knowPro/src/searchQuerySchema_v2.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ export type EntityTerm = {
2727
facets?: FacetTerm[];
2828
};
2929

30+
// Use for action verbs only. Ensure nouns are not misinterpreted as verbs
3031
export type VerbsTerm = {
31-
words: string[]; // individual words in single or compound verb
32+
words: string[]; // individual words in single verb or compound verb
3233
tense: "Past" | "Present" | "Future";
3334
};
3435

@@ -46,7 +47,7 @@ export type ActionTerm = {
4647
additionalEntities?: EntityTerm[];
4748
// Is the intent of the phrase translated to this ActionTerm to actually get information about a specific entities?
4849
// Examples:
49-
// true: if asking for specific information about an entity, such as "What is Mia's phone number?" or "Where did Jane study?"
50+
// true: if asking for specific information about an entity, such as "What did Mia say her phone number was?"
5051
// false if involves actions and interactions between entities, such as "What phone number did Mia mention in her note to Jane?"
5152
isInformational: boolean;
5253
};
@@ -62,10 +63,13 @@ export type ScopeFilter = {
6263
// Search a search engine using filters:
6364
// entitySearchTerms cannot contain entities already in actionSearchTerms
6465
export type SearchFilter = {
66+
// Use actionSearchTerm for queries involving actions and interactions between entities.
6567
actionSearchTerm?: ActionTerm;
68+
// Use entitySearchTerms for queries asking for specific information about entities and their attributes.
69+
// E.g. "What is Mia's phone number?" or "Where did Jane study?"
6670
entitySearchTerms?: EntityTerm[];
6771
// searchTerms:
68-
// Concepts, topics or other terms that don't fit ActionTerms or EntityTerms
72+
// Concepts, topics or other terms that don't fit (or are not already handled by) ActionTerms or EntityTerms.
6973
// - Do not use noisy searchTerms like "topic", "topics", "subject", "discussion" etc. even if they are mentioned in the user request
7074
// - Phrases like 'email address' or 'first name' are a single term
7175
// - use empty searchTerms array when use asks for summaries

ts/packages/knowProTest/src/common.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ export function compareStringArray(
253253
y: string[] | undefined,
254254
label: string,
255255
sort = true,
256+
cmpFuzzy?: (x: string, y: string) => boolean,
256257
): string | undefined {
257258
if (isUndefinedOrEmpty(x) && isUndefinedOrEmpty(y)) {
258259
return undefined;
@@ -269,7 +270,9 @@ export function compareStringArray(
269270
}
270271
for (let i = 0; i < x.length; ++i) {
271272
if (collections.stringCompare(x[i], y[i], false) !== 0) {
272-
return `${label}: [${i}] ${x[i]} !== ${y[i]}`;
273+
if (!cmpFuzzy || !cmpFuzzy(x[i], y[i])) {
274+
return `${label}: [${i}] ${x[i]} !== ${y[i]}`;
275+
}
273276
}
274277
}
275278
return undefined;
@@ -345,3 +348,7 @@ export function queryError(query: string, result: Error): Error {
345348
export function stringifyReadable(value: any): string {
346349
return JSON.stringify(value, undefined, 2);
347350
}
351+
352+
export function isStem(x: string, y: string, stemLength = 2) {
353+
return x.startsWith(y) && x.length - y.length <= stemLength;
354+
}

ts/packages/knowProTest/src/searchTest.ts

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ import {
1111
searchRequestDef,
1212
} from "./types.js";
1313
import {
14+
compareArray,
1415
compareNumberArray,
1516
compareObject,
1617
compareStringArray,
1718
dateRangeToTimeRange,
1819
getCommandArgs,
20+
isStem,
21+
isUndefinedOrEmpty,
1922
queryError,
2023
runTestBatch,
2124
} from "./common.js";
@@ -354,6 +357,7 @@ export function compareSearchExpr(
354357
function compareActionTerm(
355358
x?: kp.querySchema.ActionTerm,
356359
y?: kp.querySchema.ActionTerm,
360+
strict: boolean = true,
357361
): string | undefined {
358362
if (x === undefined && y === undefined) {
359363
return undefined;
@@ -362,26 +366,35 @@ function compareActionTerm(
362366
x?.actionVerbs?.words,
363367
y?.actionVerbs?.words,
364368
"verbs",
369+
true,
370+
strict ? undefined : (xv, yv) => isStem(xv, yv) || isStem(yv, xv),
365371
);
366372
if (error !== undefined) {
367373
return error;
368374
}
369-
error = compareObject(x?.actorEntities, y?.actorEntities, "actorEntities");
375+
error = compareEntityTerms(
376+
x?.actorEntities,
377+
y?.actorEntities,
378+
"actorEntities",
379+
strict,
380+
);
370381
if (error !== undefined) {
371382
return error;
372383
}
373-
error = compareObject(
384+
error = compareEntityTerms(
374385
x?.additionalEntities,
375386
y?.additionalEntities,
376387
"additionalEntities",
388+
strict,
377389
);
378390
if (error !== undefined) {
379391
return error;
380392
}
381-
error = compareObject(
393+
error = compareEntityTerms(
382394
x?.targetEntities,
383395
y?.targetEntities,
384396
"targetEntities",
397+
strict,
385398
);
386399
if (error !== undefined) {
387400
return error;
@@ -392,6 +405,81 @@ function compareActionTerm(
392405
return undefined;
393406
}
394407

408+
function compareEntityTerms(
409+
x: kp.querySchema.EntityTerm[] | "*" | undefined,
410+
y: kp.querySchema.EntityTerm[] | "*" | undefined,
411+
label: string,
412+
strict: boolean = true,
413+
): string | undefined {
414+
if (isUndefinedOrEmpty(x) && isUndefinedOrEmpty(y)) {
415+
return undefined;
416+
}
417+
if (!Array.isArray(x) || !Array.isArray(y)) {
418+
return compareObject(x, y, label);
419+
}
420+
return compareArray(x, y, label, (xt, yt) =>
421+
compareEntityTerm(xt, yt, label, strict),
422+
);
423+
}
424+
425+
function compareEntityTerm(
426+
x: kp.querySchema.EntityTerm,
427+
y: kp.querySchema.EntityTerm,
428+
label: string,
429+
strict: boolean,
430+
): string | undefined {
431+
if (strict) {
432+
return compareObject(x, y, label);
433+
}
434+
435+
if (x.name !== y.name) {
436+
return `${label}.name: ${x.name} !== ${y.name}`;
437+
}
438+
439+
let error = compareTypes(x.type, y.type, label);
440+
if (error !== undefined) {
441+
return error;
442+
}
443+
444+
if (x.isNamePronoun !== y.isNamePronoun) {
445+
return `${label}.isNamePronoun: ${x.isNamePronoun} !== ${y.isNamePronoun}`;
446+
}
447+
448+
return compareObject(x.facets, y.facets, label + ".facets");
449+
450+
function compareTypes(
451+
xType: string[] | undefined,
452+
yType: string[] | undefined,
453+
label: string,
454+
): string | undefined {
455+
let error = compareStringArray(xType, yType, label + ".type");
456+
if (error === undefined) {
457+
return undefined;
458+
}
459+
if (xType !== undefined && isTypeVariation(xType, yType)) {
460+
return undefined;
461+
}
462+
if (yType !== undefined && isTypeVariation(yType, xType)) {
463+
return undefined;
464+
}
465+
return error;
466+
}
467+
}
468+
469+
function isTypeVariation(xType: string[], yType?: string[]): boolean {
470+
if (xType.length === 1 && isUndefinedOrEmpty(yType)) {
471+
const xValue = xType[0];
472+
switch (xValue.toLowerCase()) {
473+
default:
474+
break;
475+
476+
case "person":
477+
return true;
478+
}
479+
}
480+
return false;
481+
}
482+
395483
export function compareSearchQueryScope(
396484
s1: kp.querySchema.SearchQuery,
397485
s2: kp.querySchema2.SearchQuery,
@@ -414,6 +502,7 @@ export function compareSearchQueryScope(
414502
export function compareSearchExprScope(
415503
s1: kp.querySchema.SearchExpr,
416504
s2: kp.querySchema2.SearchExpr,
505+
strict: boolean = false,
417506
): string | undefined {
418507
if (s1.filters.length !== s2.filters.length) {
419508
return `SearchExpr.filters.length: ${s1.filters.length} !== ${s2.filters.length}`;
@@ -423,15 +512,20 @@ export function compareSearchExprScope(
423512
const f1 = s1.filters[i];
424513
const f2 = s2.filters[i];
425514

426-
let error = compareObject(
515+
let error = compareEntityTerms(
427516
f1.entitySearchTerms,
428517
f2.entitySearchTerms,
429518
"entitySearchTerms",
519+
strict,
430520
);
431521
if (error !== undefined) {
432522
return error;
433523
}
434-
error = compareActionTerm(f1.actionSearchTerm, f2.actionSearchTerm);
524+
error = compareActionTerm(
525+
f1.actionSearchTerm,
526+
f2.actionSearchTerm,
527+
strict,
528+
);
435529
if (error !== undefined) {
436530
return error;
437531
}

ts/packages/memory/conversation/src/docSearchQuerySchema.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type EntityTerm = {
2929
facets?: FacetTerm[];
3030
};
3131

32+
// Use for action verbs only. Ensure nouns are not misinterpreted as verbs
3233
export type VerbsTerm = {
3334
words: string[]; // individual words in single or compound verb
3435
tense: "Past" | "Present" | "Future";
@@ -48,7 +49,7 @@ export type ActionTerm = {
4849
additionalEntities?: EntityTerm[];
4950
// Is the intent of the phrase translated to this ActionTerm to actually get information about a specific entities?
5051
// Examples:
51-
// true: if asking for specific information about an entity, such as "What is Mia's phone number?" or "Where did Jane study?"
52+
// true: if asking for specific information about an entity, such as "What did Mia say her phone number was?"
5253
// false if involves actions and interactions between entities, such as "What phone number did Mia mention in her note to Jane?"
5354
isInformational: boolean;
5455
};
@@ -65,10 +66,13 @@ export type ScopeFilter = {
6566
// Search a search engine using filters:
6667
// entitySearchTerms cannot contain entities already in actionSearchTerms
6768
export type SearchFilter = {
69+
// Use actionSearchTerm for queries involving actions and interactions between entities.
6870
actionSearchTerm?: ActionTerm;
71+
// Use entitySearchTerms for queries asking for specific information about entities and their attributes.
72+
// E.g. "What is Mia's phone number?" or "Where did Jane study?"
6973
entitySearchTerms?: EntityTerm[];
7074
// searchTerms:
71-
// Use for all concepts, topics, or other search terms that don't fit ActionTerms or EntityTerms
75+
// Use for all concepts, topics, or other search terms that don't fit (or are not already handled by) ActionTerms or EntityTerms
7276
// - Do not use noisy searchTerms like "topic", "topics", "subject", "discussion" etc. even if they are mentioned in the user request
7377
// - Phrases like 'email address' or 'first name' are a single term
7478
// - use empty searchTerms array when user asks for summaries

0 commit comments

Comments
 (0)