Skip to content

Commit c48a722

Browse files
authored
Memory updates (#1439)
* Additional wrapper APIs on base Memory class to simplify and demonstrate: * Searching for matching entities and topics * Searching _using_ entities and topics * These also show how to use the more powerful searchKnowledge and search APIs * Better documentation * Unit Tests
1 parent 7f1ed32 commit c48a722

File tree

8 files changed

+319
-12
lines changed

8 files changed

+319
-12
lines changed

ts/packages/knowPro/src/interfaces.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export interface IMessage extends IKnowledgeSource {
6767
export type ScoredMessageOrdinal = {
6868
/**
6969
* The ordinal number of the message.
70+
* Use this ordinal to get the message frm the conversation's message collection {@link IMessageCollection}
71+
* @see {@link IConversation.messages}
7072
*/
7173
messageOrdinal: MessageOrdinal;
7274
/**
@@ -187,7 +189,9 @@ export interface IConversation<TMessage extends IMessage = IMessage> {
187189
*/
188190
export type ScoredSemanticRefOrdinal = {
189191
/**
190-
* Ordinal number for the semantic reference.
192+
* Ordinal number for the SemanticRef {@link SemanticRef}.
193+
* Use this ordinal to resolve the SemanticRef from the conversation's semantic refs collection {@link ISemanticRefCollection}
194+
* @see {@link IConversation.semanticRefs}
191195
*/
192196
semanticRefOrdinal: SemanticRefOrdinal;
193197
/**
@@ -657,7 +661,13 @@ export type WhenFilter = {
657661
};
658662

659663
export type SemanticRefSearchResult = {
664+
/**
665+
* The search terms that caused these semantic refs to match
666+
*/
660667
termMatches: Set<string>;
668+
/**
669+
* Scored semantic ref matches
670+
*/
661671
semanticRefMatches: ScoredSemanticRefOrdinal[];
662672
};
663673

ts/packages/knowPro/src/knowledgeLib.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
IMessageCollection,
2121
} from "./interfaces.js";
2222
import { conversation as kpLib } from "knowledge-processor";
23+
import { getSemanticRefsFromScoredOrdinals } from "./searchLib.js";
2324

2425
export function facetValueToString(facet: kpLib.Facet): string {
2526
const value = facet.value;
@@ -67,14 +68,6 @@ export function* getScoredSemanticRefsFromOrdinals(
6768
}
6869
}
6970

70-
export function getSemanticRefsFromScoredOrdinals(
71-
semanticRefs: ISemanticRefCollection,
72-
scoredOrdinals: ScoredSemanticRefOrdinal[],
73-
): SemanticRef[] {
74-
const ordinals = scoredOrdinals.map((sr) => sr.semanticRefOrdinal);
75-
return semanticRefs.getMultiple(ordinals);
76-
}
77-
7871
export function* messageOrdinalsFromSemanticRefs(semanticRefs: SemanticRef[]) {
7972
for (const sr of semanticRefs) {
8073
yield sr.range.start.messageOrdinal;

ts/packages/knowPro/src/search.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,17 @@ export function createSearchOptionsTypical(): SearchOptions {
8282
};
8383
}
8484

85+
/**
86+
* Search matches returned by searchConversation {@link searchConversation}
87+
*/
8588
export type ConversationSearchResult = {
89+
/**
90+
* The ordinals of matching messages. You can use these ordinals to load messages
91+
*/
8692
messageMatches: ScoredMessageOrdinal[];
93+
/**
94+
* For each matching knowledge type, the ordinals of the matching knowledge.
95+
*/
8796
knowledgeMatches: Map<KnowledgeType, SemanticRefSearchResult>;
8897
rawSearchQuery?: string | undefined;
8998
};

ts/packages/knowPro/src/searchLib.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
*/
88

99
import {
10+
ISemanticRefCollection,
1011
KnowledgePropertyName,
1112
PropertySearchTerm,
13+
ScoredSemanticRefOrdinal,
1214
SearchTerm,
1315
SearchTermGroup,
1416
SearchTermGroupTypes,
17+
SemanticRef,
1518
} from "./interfaces.js";
1619
import * as kpLib from "knowledge-processor";
1720
import { PropertyNames } from "./propertyIndex.js";
@@ -171,6 +174,76 @@ export function createPropertySearchTerms(
171174
return propertySearchTerms;
172175
}
173176

177+
export function createTopicSearchTermGroup(
178+
topicTerms: string | string[],
179+
exactMatch: boolean = false,
180+
): SearchTermGroup {
181+
const termGroup = createOrMaxTermGroup();
182+
if (typeof topicTerms === "string") {
183+
termGroup.terms.push(
184+
createPropertySearchTerm(
185+
PropertyNames.Topic,
186+
topicTerms,
187+
exactMatch,
188+
),
189+
);
190+
} else {
191+
for (const term of topicTerms) {
192+
termGroup.terms.push(
193+
createPropertySearchTerm(PropertyNames.Topic, term, exactMatch),
194+
);
195+
}
196+
}
197+
return termGroup;
198+
}
199+
200+
export function createEntitySearchTermGroup(
201+
name: string | undefined,
202+
type?: string | undefined,
203+
faceName?: string | undefined,
204+
facetValue?: string | undefined,
205+
exactMatch: boolean = false,
206+
): SearchTermGroup {
207+
const termGroup = createOrMaxTermGroup();
208+
if (name) {
209+
termGroup.terms.push(
210+
createPropertySearchTerm(
211+
PropertyNames.EntityName,
212+
name,
213+
exactMatch,
214+
),
215+
);
216+
}
217+
if (type) {
218+
termGroup.terms.push(
219+
createPropertySearchTerm(
220+
PropertyNames.EntityType,
221+
type,
222+
exactMatch,
223+
),
224+
);
225+
}
226+
if (faceName) {
227+
termGroup.terms.push(
228+
createPropertySearchTerm(
229+
PropertyNames.FacetName,
230+
faceName,
231+
exactMatch,
232+
),
233+
);
234+
}
235+
if (facetValue) {
236+
termGroup.terms.push(
237+
createPropertySearchTerm(
238+
PropertyNames.FacetValue,
239+
facetValue,
240+
exactMatch,
241+
),
242+
);
243+
}
244+
return termGroup;
245+
}
246+
174247
export function createTagSearchTermGroup(
175248
tags: string[],
176249
exactMatch: boolean = true,
@@ -210,3 +283,11 @@ export function createMultipleChoiceQuestion(
210283
}
211284
return text;
212285
}
286+
287+
export function getSemanticRefsFromScoredOrdinals(
288+
semanticRefs: ISemanticRefCollection,
289+
scoredOrdinals: ScoredSemanticRefOrdinal[],
290+
): SemanticRef[] {
291+
const ordinals = scoredOrdinals.map((sr) => sr.semanticRefOrdinal);
292+
return semanticRefs.getMultiple(ordinals);
293+
}

ts/packages/knowPro/test/search.spec.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import {
55
IConversation,
66
KnowledgeType,
7+
PropertySearchTerm,
78
SearchTermGroup,
89
SemanticRefSearchResult,
910
} from "../src/interfaces.js";
@@ -14,6 +15,8 @@ import {
1415
createOrTermGroup,
1516
createPropertySearchTerm,
1617
createOrMaxTermGroup,
18+
createTopicSearchTermGroup,
19+
createEntitySearchTermGroup,
1720
} from "../src/searchLib.js";
1821
import {
1922
emptyConversation,
@@ -30,10 +33,12 @@ import {
3033
resolveAndVerifyKnowledgeMatches,
3134
resolveAndVerifySemanticRefs,
3235
verifyDidMatchSearchGroup,
36+
verifyPropertySearchTermName,
3337
verifySemanticRefResult,
3438
} from "./verify.js";
3539
import { hasTestKeys, describeIf } from "test-lib";
36-
import { validateSearchTermGroup } from "../src/compileLib.js";
40+
import { isPropertyTerm, validateSearchTermGroup } from "../src/compileLib.js";
41+
import { PropertyNames } from "../src/propertyIndex.js";
3742

3843
/**
3944
* These tests are designed to run offline.
@@ -226,6 +231,39 @@ describe("search.offline", () => {
226231
},
227232
testTimeout,
228233
);
234+
test(
235+
"propertySearchGroup",
236+
() => {
237+
let topicTerms = ["music", "dance"];
238+
let termGroup = createTopicSearchTermGroup(topicTerms);
239+
validateSearchTermGroup(termGroup);
240+
241+
expect(termGroup.terms.length).toEqual(topicTerms.length);
242+
for (let i = 0; i < termGroup.terms.length; ++i) {
243+
const term = termGroup.terms[i];
244+
expect(isPropertyTerm(term)).toBeTruthy();
245+
if (isPropertyTerm(term)) {
246+
verifyPropertySearchTermName(term, PropertyNames.Topic);
247+
expect(term.propertyValue.term.text).toEqual(topicTerms[i]);
248+
}
249+
}
250+
251+
termGroup = createEntitySearchTermGroup(
252+
"jane",
253+
"person",
254+
"job",
255+
"scientist",
256+
);
257+
validateSearchTermGroup(termGroup);
258+
expect(termGroup.terms.length).toEqual(4);
259+
let propertyTerm = termGroup.terms[2] as PropertySearchTerm;
260+
verifyPropertySearchTermName(propertyTerm, "facet.name");
261+
propertyTerm = termGroup.terms[3] as PropertySearchTerm;
262+
verifyPropertySearchTermName(propertyTerm, "facet.value");
263+
},
264+
testTimeout,
265+
);
266+
229267
async function runSearchKnowledge(
230268
termGroup: SearchTermGroup,
231269
knowledgeType: KnowledgeType,

ts/packages/knowPro/test/verify.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
SemanticRef,
1313
TextRange,
1414
SemanticRefSearchResult,
15+
PropertySearchTerm,
1516
} from "../src/interfaces.js";
1617
import { ConversationSearchResult } from "../src/search.js";
1718
import {
@@ -198,3 +199,15 @@ export function verifyAnswerResponse(response: AnswerResponse) {
198199
expect(response.whyNoAnswer).toBeDefined();
199200
}
200201
}
202+
203+
export function verifyPropertySearchTermName(
204+
property: PropertySearchTerm,
205+
name: string,
206+
) {
207+
expect(property.propertyName).toBeDefined();
208+
if (typeof property.propertyName === "string") {
209+
expect(property.propertyName).toEqual(name);
210+
} else {
211+
expect(property.propertyName.term.text).toEqual(name);
212+
}
213+
}

0 commit comments

Comments
 (0)