Skip to content

Commit 35a8ea4

Browse files
author
Umesh Madan
authored
knowpro: structured querying (#642)
Ongoing experimentation: * Flexible property matching for query trees... * Simplified property predicates * Filter by arbitrary properties; auto-lower property terms * Simple query builder * Bug fixes
1 parent 9dd2d10 commit 35a8ea4

6 files changed

Lines changed: 359 additions & 207 deletions

File tree

‎ts/examples/chat/src/memory/knowproMemory.ts‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,7 @@ export async function createKnowproCommands(
165165
description: "Search current knowPro conversation by terms",
166166
options: {
167167
maxToDisplay: argNum("Maximum matches to display", 25),
168-
type: arg("Knowledge type"),
169-
speaker: arg("Speaker"),
168+
ktype: arg("Knowledge type"),
170169
},
171170
};
172171
}
@@ -190,10 +189,11 @@ export async function createKnowproCommands(
190189
`Searching ${conversation.nameTag}...`,
191190
);
192191

193-
const matches = await kp.searchConversation(conversation, terms, {
194-
type: namedArgs.type,
195-
speaker: namedArgs.speaker,
196-
});
192+
const matches = await kp.searchConversation(
193+
conversation,
194+
terms,
195+
filterFromArgs(namedArgs),
196+
);
197197
if (matches === undefined || matches.size === 0) {
198198
context.printer.writeLine("No matches");
199199
return;
@@ -210,6 +210,26 @@ export async function createKnowproCommands(
210210
}
211211
}
212212

213+
function filterFromArgs(namedArgs: NamedArgs) {
214+
let filter: kp.SearchFilter = { type: namedArgs.ktype };
215+
let argCopy = { ...namedArgs };
216+
delete argCopy.maxToDisplay;
217+
delete argCopy.ktype;
218+
let keys = Object.keys(argCopy);
219+
if (keys.length > 0) {
220+
for (const key of keys) {
221+
const value = argCopy[key];
222+
if (typeof value === "function") {
223+
delete argCopy[key];
224+
}
225+
}
226+
if (Object.keys(argCopy).length > 0) {
227+
filter.propertiesToMatch = argCopy;
228+
}
229+
}
230+
return filter;
231+
}
232+
213233
function entitiesDef(): CommandMetadata {
214234
return {
215235
description: "Display entities in current conversation",

‎ts/packages/knowPro/src/accumulators.ts‎

Lines changed: 22 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { collections, createTopNList } from "typeagent";
4+
import { createTopNList } from "typeagent";
55
import {
66
IMessage,
77
KnowledgeType,
@@ -167,7 +167,7 @@ export class MatchAccumulator<T = any> {
167167
}
168168

169169
export class SemanticRefAccumulator extends MatchAccumulator<SemanticRefIndex> {
170-
constructor(public queryTermMatches = new QueryTermAccumulator()) {
170+
constructor(public queryTermMatches = new TermMatchAccumulator()) {
171171
super();
172172
}
173173

@@ -289,77 +289,41 @@ export class SemanticRefAccumulator extends MatchAccumulator<SemanticRefIndex> {
289289
}
290290
}
291291

292-
export class QueryTermAccumulator {
292+
export class TermMatchAccumulator {
293293
constructor(
294294
public termMatches: Set<string> = new Set<string>(),
295-
public relatedTermToTerms: Map<string, Set<string>> = new Map<
295+
// Related terms work 'on behalf' of a primary term
296+
// For each related term, we track the primary terms it matched on behalf of
297+
public relatedTermMatchedFor: Map<string, Set<string>> = new Map<
296298
string,
297299
Set<string>
298300
>(),
299301
) {}
300302

301-
public add(term: Term, relatedTerm?: Term) {
302-
this.termMatches.add(term.text);
303+
public add(primaryTerm: Term, relatedTerm?: Term) {
304+
this.termMatches.add(primaryTerm.text);
303305
if (relatedTerm !== undefined) {
304-
let relatedTermToTerms = this.relatedTermToTerms.get(
305-
relatedTerm.text,
306-
);
307-
if (relatedTermToTerms === undefined) {
308-
relatedTermToTerms = new Set<string>();
309-
this.relatedTermToTerms.set(
310-
relatedTerm.text,
311-
relatedTermToTerms,
312-
);
306+
// Related term matched on behalf of term
307+
let primaryTerms = this.relatedTermMatchedFor.get(relatedTerm.text);
308+
if (primaryTerms === undefined) {
309+
primaryTerms = new Set<string>();
310+
this.relatedTermMatchedFor.set(relatedTerm.text, primaryTerms);
313311
}
314-
relatedTermToTerms.add(term.text);
312+
// Track that this related term matched on behalf of term
313+
primaryTerms.add(primaryTerm.text);
315314
}
316315
}
317316

318-
public matched(testText: string | string[], expectedText: string): boolean {
319-
if (Array.isArray(testText)) {
320-
if (testText.length > 0) {
321-
for (const text of testText) {
322-
if (this.matched(text, expectedText)) {
323-
return true;
324-
}
325-
}
326-
}
327-
return false;
328-
}
329-
330-
if (collections.stringEquals(testText, expectedText, false)) {
317+
public has(text: string, includeRelated: boolean = true): boolean {
318+
if (this.termMatches.has(text)) {
331319
return true;
332320
}
333-
334-
// Maybe the test text matched a related term.
335-
// If so, the matching related term should have matched *on behalf* of
336-
// of expectedTerm
337-
const relatedTermToTerms = this.relatedTermToTerms.get(testText);
338-
return relatedTermToTerms !== undefined
339-
? relatedTermToTerms.has(expectedText)
340-
: false;
321+
return includeRelated ? this.relatedTermMatchedFor.has(text) : false;
341322
}
342323

343-
public didValueMatch(
344-
obj: Record<string, any>,
345-
key: string,
346-
expectedValue: string,
347-
): boolean {
348-
const value = obj[key];
349-
if (value === undefined) {
350-
return false;
351-
}
352-
if (Array.isArray(value)) {
353-
for (const item of value) {
354-
if (this.didValueMatch(item, key, expectedValue)) {
355-
return true;
356-
}
357-
}
358-
return false;
359-
} else {
360-
const stringValue = value.toString().toLowerCase();
361-
return this.matched(stringValue, expectedValue);
362-
}
324+
public hasRelatedMatch(primaryTerm: string, relatedTerm: string): boolean {
325+
let primaryTerms = this.relatedTermMatchedFor.get(relatedTerm);
326+
return primaryTerms?.has(primaryTerm) ?? false;
363327
}
364328
}
365329

@@ -378,6 +342,7 @@ export class TextRangeAccumulator {
378342
if (textRanges === undefined) {
379343
textRanges = [textRange];
380344
}
345+
// Future: Merge ranges
381346
textRanges.push(textRange);
382347
}
383348

‎ts/packages/knowPro/src/conversationIndex.ts‎

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,17 @@ import { openai } from "aiclient";
1919
import { Result } from "typechat";
2020
import { async } from "typeagent";
2121

22-
function addFacet(
23-
facet: conversation.Facet | undefined,
24-
refIndex: number,
25-
semanticRefIndex: ITermToSemanticRefIndex,
26-
) {
27-
if (facet !== undefined) {
28-
semanticRefIndex.addTerm(facet.name, refIndex);
29-
if (facet.value !== undefined) {
30-
semanticRefIndex.addTerm(
31-
conversation.knowledgeValueToString(facet.value),
32-
refIndex,
33-
);
34-
}
35-
}
22+
function createKnowledgeModel() {
23+
const chatModelSettings = openai.apiSettingsFromEnv(
24+
openai.ModelType.Chat,
25+
undefined,
26+
"GPT_4_O",
27+
);
28+
chatModelSettings.retryPauseMs = 10000;
29+
const chatModel = openai.createJsonChatModel(chatModelSettings, [
30+
"chatExtractor",
31+
]);
32+
return chatModel;
3633
}
3734

3835
function textLocationFromLocation(
@@ -52,17 +49,20 @@ function textRangeFromLocation(
5249
};
5350
}
5451

55-
function createKnowledgeModel() {
56-
const chatModelSettings = openai.apiSettingsFromEnv(
57-
openai.ModelType.Chat,
58-
undefined,
59-
"GPT_4_O",
60-
);
61-
chatModelSettings.retryPauseMs = 10000;
62-
const chatModel = openai.createJsonChatModel(chatModelSettings, [
63-
"chatExtractor",
64-
]);
65-
return chatModel;
52+
function addFacet(
53+
facet: conversation.Facet | undefined,
54+
refIndex: number,
55+
semanticRefIndex: ITermToSemanticRefIndex,
56+
) {
57+
if (facet !== undefined) {
58+
semanticRefIndex.addTerm(facet.name, refIndex);
59+
if (facet.value !== undefined) {
60+
semanticRefIndex.addTerm(
61+
conversation.knowledgeValueToString(facet.value),
62+
refIndex,
63+
);
64+
}
65+
}
6666
}
6767

6868
export function addEntityToIndex(

‎ts/packages/knowPro/src/dataFormat.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export interface ITopic {
6767
text: string;
6868
}
6969

70-
type ITag = ITopic;
70+
export type ITag = ITopic;
7171

7272
export interface IConversation<TMeta extends IKnowledgeSource = any> {
7373
nameTag: string;

0 commit comments

Comments
 (0)