Skip to content

Commit

Permalink
knowpro: structured querying (#642)
Browse files Browse the repository at this point in the history
Ongoing experimentation:
* Flexible property matching for query trees...
* Simplified property predicates
* Filter by arbitrary properties; auto-lower property terms
* Simple query builder
* Bug fixes
  • Loading branch information
umeshma authored Jan 31, 2025
1 parent 9dd2d10 commit 35a8ea4
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 207 deletions.
32 changes: 26 additions & 6 deletions ts/examples/chat/src/memory/knowproMemory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,7 @@ export async function createKnowproCommands(
description: "Search current knowPro conversation by terms",
options: {
maxToDisplay: argNum("Maximum matches to display", 25),
type: arg("Knowledge type"),
speaker: arg("Speaker"),
ktype: arg("Knowledge type"),
},
};
}
Expand All @@ -190,10 +189,11 @@ export async function createKnowproCommands(
`Searching ${conversation.nameTag}...`,
);

const matches = await kp.searchConversation(conversation, terms, {
type: namedArgs.type,
speaker: namedArgs.speaker,
});
const matches = await kp.searchConversation(
conversation,
terms,
filterFromArgs(namedArgs),
);
if (matches === undefined || matches.size === 0) {
context.printer.writeLine("No matches");
return;
Expand All @@ -210,6 +210,26 @@ export async function createKnowproCommands(
}
}

function filterFromArgs(namedArgs: NamedArgs) {
let filter: kp.SearchFilter = { type: namedArgs.ktype };
let argCopy = { ...namedArgs };
delete argCopy.maxToDisplay;
delete argCopy.ktype;
let keys = Object.keys(argCopy);
if (keys.length > 0) {
for (const key of keys) {
const value = argCopy[key];
if (typeof value === "function") {
delete argCopy[key];
}
}
if (Object.keys(argCopy).length > 0) {
filter.propertiesToMatch = argCopy;
}
}
return filter;
}

function entitiesDef(): CommandMetadata {
return {
description: "Display entities in current conversation",
Expand Down
79 changes: 22 additions & 57 deletions ts/packages/knowPro/src/accumulators.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { collections, createTopNList } from "typeagent";
import { createTopNList } from "typeagent";
import {
IMessage,
KnowledgeType,
Expand Down Expand Up @@ -167,7 +167,7 @@ export class MatchAccumulator<T = any> {
}

export class SemanticRefAccumulator extends MatchAccumulator<SemanticRefIndex> {
constructor(public queryTermMatches = new QueryTermAccumulator()) {
constructor(public queryTermMatches = new TermMatchAccumulator()) {
super();
}

Expand Down Expand Up @@ -289,77 +289,41 @@ export class SemanticRefAccumulator extends MatchAccumulator<SemanticRefIndex> {
}
}

export class QueryTermAccumulator {
export class TermMatchAccumulator {
constructor(
public termMatches: Set<string> = new Set<string>(),
public relatedTermToTerms: Map<string, Set<string>> = new Map<
// Related terms work 'on behalf' of a primary term
// For each related term, we track the primary terms it matched on behalf of
public relatedTermMatchedFor: Map<string, Set<string>> = new Map<
string,
Set<string>
>(),
) {}

public add(term: Term, relatedTerm?: Term) {
this.termMatches.add(term.text);
public add(primaryTerm: Term, relatedTerm?: Term) {
this.termMatches.add(primaryTerm.text);
if (relatedTerm !== undefined) {
let relatedTermToTerms = this.relatedTermToTerms.get(
relatedTerm.text,
);
if (relatedTermToTerms === undefined) {
relatedTermToTerms = new Set<string>();
this.relatedTermToTerms.set(
relatedTerm.text,
relatedTermToTerms,
);
// Related term matched on behalf of term
let primaryTerms = this.relatedTermMatchedFor.get(relatedTerm.text);
if (primaryTerms === undefined) {
primaryTerms = new Set<string>();
this.relatedTermMatchedFor.set(relatedTerm.text, primaryTerms);
}
relatedTermToTerms.add(term.text);
// Track that this related term matched on behalf of term
primaryTerms.add(primaryTerm.text);
}
}

public matched(testText: string | string[], expectedText: string): boolean {
if (Array.isArray(testText)) {
if (testText.length > 0) {
for (const text of testText) {
if (this.matched(text, expectedText)) {
return true;
}
}
}
return false;
}

if (collections.stringEquals(testText, expectedText, false)) {
public has(text: string, includeRelated: boolean = true): boolean {
if (this.termMatches.has(text)) {
return true;
}

// Maybe the test text matched a related term.
// If so, the matching related term should have matched *on behalf* of
// of expectedTerm
const relatedTermToTerms = this.relatedTermToTerms.get(testText);
return relatedTermToTerms !== undefined
? relatedTermToTerms.has(expectedText)
: false;
return includeRelated ? this.relatedTermMatchedFor.has(text) : false;
}

public didValueMatch(
obj: Record<string, any>,
key: string,
expectedValue: string,
): boolean {
const value = obj[key];
if (value === undefined) {
return false;
}
if (Array.isArray(value)) {
for (const item of value) {
if (this.didValueMatch(item, key, expectedValue)) {
return true;
}
}
return false;
} else {
const stringValue = value.toString().toLowerCase();
return this.matched(stringValue, expectedValue);
}
public hasRelatedMatch(primaryTerm: string, relatedTerm: string): boolean {
let primaryTerms = this.relatedTermMatchedFor.get(relatedTerm);
return primaryTerms?.has(primaryTerm) ?? false;
}
}

Expand All @@ -378,6 +342,7 @@ export class TextRangeAccumulator {
if (textRanges === undefined) {
textRanges = [textRange];
}
// Future: Merge ranges
textRanges.push(textRange);
}

Expand Down
50 changes: 25 additions & 25 deletions ts/packages/knowPro/src/conversationIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,17 @@ import { openai } from "aiclient";
import { Result } from "typechat";
import { async } from "typeagent";

function addFacet(
facet: conversation.Facet | undefined,
refIndex: number,
semanticRefIndex: ITermToSemanticRefIndex,
) {
if (facet !== undefined) {
semanticRefIndex.addTerm(facet.name, refIndex);
if (facet.value !== undefined) {
semanticRefIndex.addTerm(
conversation.knowledgeValueToString(facet.value),
refIndex,
);
}
}
function createKnowledgeModel() {
const chatModelSettings = openai.apiSettingsFromEnv(
openai.ModelType.Chat,
undefined,
"GPT_4_O",
);
chatModelSettings.retryPauseMs = 10000;
const chatModel = openai.createJsonChatModel(chatModelSettings, [
"chatExtractor",
]);
return chatModel;
}

function textLocationFromLocation(
Expand All @@ -52,17 +49,20 @@ function textRangeFromLocation(
};
}

function createKnowledgeModel() {
const chatModelSettings = openai.apiSettingsFromEnv(
openai.ModelType.Chat,
undefined,
"GPT_4_O",
);
chatModelSettings.retryPauseMs = 10000;
const chatModel = openai.createJsonChatModel(chatModelSettings, [
"chatExtractor",
]);
return chatModel;
function addFacet(
facet: conversation.Facet | undefined,
refIndex: number,
semanticRefIndex: ITermToSemanticRefIndex,
) {
if (facet !== undefined) {
semanticRefIndex.addTerm(facet.name, refIndex);
if (facet.value !== undefined) {
semanticRefIndex.addTerm(
conversation.knowledgeValueToString(facet.value),
refIndex,
);
}
}
}

export function addEntityToIndex(
Expand Down
2 changes: 1 addition & 1 deletion ts/packages/knowPro/src/dataFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export interface ITopic {
text: string;
}

type ITag = ITopic;
export type ITag = ITopic;

export interface IConversation<TMeta extends IKnowledgeSource = any> {
nameTag: string;
Expand Down
Loading

0 comments on commit 35a8ea4

Please sign in to comment.