Skip to content

Commit e5c5286

Browse files
authored
DocMemory, knowpro (#1366)
* SearchTermGroup validation * Unit Tests * Allow for experimentation with custom query schemas * DocMemory * Scope query improvements * Experimental schema with custom instructions for scoped queries
1 parent fcbd851 commit e5c5286

File tree

11 files changed

+372
-43
lines changed

11 files changed

+372
-43
lines changed

ts/.vscode/launch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,9 +329,9 @@
329329
"--runTestsByPath",
330330
"${jest.testFile}"
331331
],
332-
//"cwd": "${workspaceFolder}/packages/knowpro",
332+
"cwd": "${workspaceFolder}/packages/knowpro",
333333
//"cwd": "${workspaceFolder}/packages/memory/storage",
334-
"cwd": "${workspaceFolder}/packages/memory/conversation",
334+
//"cwd": "${workspaceFolder}/packages/memory/conversation",
335335
//"cwd": "${workspaceFolder}/packages/knowledgeProcessor",
336336
//"cwd": "${workspaceFolder}/packages/aiclient",
337337
"console": "integratedTerminal",

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

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -449,26 +449,41 @@ export async function createKnowproTestCommands(
449449
return undefined;
450450
}
451451

452-
commands.kpTestSearch.metadata = searchDef();
452+
function testSearchDef(): CommandMetadata {
453+
const def = searchDef();
454+
def.options ??= {};
455+
def.options!.compare = argBool("Compare to V1 mode", false);
456+
return def;
457+
}
458+
commands.kpTestSearch.metadata = testSearchDef();
453459
async function testSearchScope(args: string[]) {
454-
const namedArgs = parseNamedArguments(args, searchDef());
455-
const result = await context.queryTranslator.translate!(
456-
namedArgs.query,
457-
);
458-
context.printer.writeTranslation(result);
460+
const namedArgs = parseNamedArguments(args, testSearchDef());
461+
const queryTranslator =
462+
context.conversation! instanceof cm.Memory
463+
? context.conversation!.settings.queryTranslator
464+
: context.queryTranslator;
465+
const result = namedArgs.compare
466+
? await queryTranslator.translate(namedArgs.query)
467+
: undefined;
468+
if (result) {
469+
context.printer.writeTranslation(result);
470+
}
459471
context.printer.writeHeading("Scope");
460-
const resultScope = await context.queryTranslator.translateWithScope!(
472+
const resultScope = await queryTranslator.translateWithScope!(
461473
namedArgs.query,
462474
);
463475
context.printer.writeTranslation(resultScope);
464-
if (result.success && resultScope.success) {
465-
const error = kpTest.compareSearchQueryScope(
466-
result.data,
467-
resultScope.data,
468-
);
469-
if (error !== undefined) {
470-
context.printer.writeError(error);
476+
if ((result === undefined || result.success) && resultScope.success) {
477+
if (result !== undefined) {
478+
const error = kpTest.compareSearchQueryScope(
479+
result.data,
480+
resultScope.data,
481+
);
482+
if (error !== undefined) {
483+
context.printer.writeError(error);
484+
}
471485
}
486+
472487
const request = parseTypedArguments<kpTest.SearchRequest>(
473488
args,
474489
searchDef(),

ts/packages/knowPro/src/compileLib.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
SearchTerm,
1313
PropertySearchTerm,
1414
SearchTermGroup,
15+
Term,
16+
SearchTermGroupTypes,
1517
} from "./interfaces.js";
1618
import * as q from "./query.js";
1719

@@ -99,6 +101,89 @@ export function isSearchGroupTerm(
99101
return term.hasOwnProperty("booleanOp");
100102
}
101103

104+
export function validateSearchTermGroup(
105+
termGroup: SearchTermGroup,
106+
): string | undefined {
107+
if (termGroup === undefined) {
108+
return "SearchTermGroup";
109+
}
110+
if (!termGroup.booleanOp) {
111+
return "booleanOp";
112+
}
113+
if (termGroup.terms === undefined || termGroup.terms.length === 0) {
114+
return `terms\n${JSON.stringify(termGroup)}`;
115+
}
116+
let error: string | undefined;
117+
let lastValid: SearchTermGroupTypes | undefined;
118+
for (let i = 0; i < termGroup.terms.length; ++i) {
119+
const term = termGroup.terms[i];
120+
if (isPropertyTerm(term)) {
121+
if (term.propertyName === undefined) {
122+
error = `propertyName\n${JSON.stringify(term)}`;
123+
break;
124+
}
125+
if (typeof term.propertyName !== "string") {
126+
error = validateSearchTerm(term.propertyName);
127+
if (error !== undefined) {
128+
error = "propertyName\n" + error;
129+
break;
130+
}
131+
}
132+
if (term.propertyValue === undefined) {
133+
error = `propertyValue\n${JSON.stringify(term)}`;
134+
break;
135+
}
136+
error = validateSearchTerm(term.propertyValue);
137+
if (error !== undefined) {
138+
error = "propertyValue\n" + error;
139+
break;
140+
}
141+
} else if (isSearchGroupTerm(term)) {
142+
error = validateSearchTermGroup(term);
143+
} else {
144+
error = validateSearchTerm(term);
145+
}
146+
if (error !== undefined) {
147+
break;
148+
}
149+
lastValid = term;
150+
}
151+
if (error !== undefined && lastValid !== undefined) {
152+
error += `\nLast valid term:\n${JSON.stringify(lastValid)}`;
153+
}
154+
return error;
155+
}
156+
157+
function validateSearchTerm(term: SearchTerm): string | undefined {
158+
if (!validateTerm(term.term)) {
159+
return `Invalid SearchTerm\n${JSON.stringify(term)}`;
160+
}
161+
if (term.relatedTerms !== undefined && term.relatedTerms.length > 0) {
162+
for (let i = 0; i < term.relatedTerms.length; ++i) {
163+
let relatedTerm = term.relatedTerms[i];
164+
if (!validateTerm(relatedTerm)) {
165+
let error = `Invalid related term for:\n${JSON.stringify(term.term)}`;
166+
if (i > 0) {
167+
error += `\nLast valid related term:\n${JSON.stringify(term.relatedTerms[i - 1])}`;
168+
}
169+
return error;
170+
}
171+
}
172+
}
173+
return undefined;
174+
}
175+
176+
function validateTerm(term: Term): boolean {
177+
if (
178+
term === undefined ||
179+
term.text === undefined ||
180+
term.text.length === 0
181+
) {
182+
return false;
183+
}
184+
return true;
185+
}
186+
102187
export interface CompiledSearchTerm extends SearchTerm {
103188
/**
104189
* The compiler will eliminate overlapping related terms to reduce hit duplication.

ts/packages/knowPro/src/search.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
isPropertyTerm,
4040
isSearchGroupTerm,
4141
toRequiredSearchTerm,
42+
validateSearchTermGroup,
4243
} from "./compileLib.js";
4344
import { NormalizedEmbedding } from "typeagent";
4445
import { getTimestampedScoredSemanticRefOrdinals } from "./knowledgeLib.js";
@@ -151,6 +152,11 @@ export async function searchConversationKnowledge(
151152
if (!q.isConversationSearchable(conversation)) {
152153
return undefined;
153154
}
155+
let error = validateSearchTermGroup(searchTermGroup);
156+
if (error !== undefined) {
157+
throw new Error(error);
158+
}
159+
154160
options ??= createSearchOptions();
155161
const queryBuilder = new QueryCompiler(
156162
conversation,
@@ -816,7 +822,11 @@ class QueryCompiler {
816822
* Return false if the term should be rejected
817823
*/
818824
private validateAndPrepareTerm(term: Term | undefined): boolean {
819-
if (term) {
825+
if (
826+
term !== undefined &&
827+
term.text !== undefined &&
828+
term.text.length > 0
829+
) {
820830
term.text = term.text.toLowerCase();
821831
}
822832
return true;

ts/packages/knowPro/src/searchLang.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -974,14 +974,29 @@ class SearchQueryCompiler {
974974
private compileScopeFilter(
975975
filter: querySchema2.ScopeFilter,
976976
when: WhenFilter | undefined,
977+
useTags: boolean = true,
977978
): WhenFilter | undefined {
978979
when ??= {};
979980
if (filter.timeRange) {
980981
when.dateRange = dateRangeFromDateTimeRange(filter.timeRange);
981982
}
982983
if (filter.searchTerms !== undefined && filter.searchTerms.length > 0) {
983984
when.tags ??= [];
984-
when.tags.push(...filter.searchTerms);
985+
if (useTags) {
986+
when.tags.push(...filter.searchTerms);
987+
} else {
988+
const topicTerms = createOrMaxTermGroup();
989+
for (const topic of filter.searchTerms) {
990+
topicTerms.terms.push(
991+
createPropertySearchTerm("topic", topic),
992+
);
993+
}
994+
if (when.scopeDefiningTerms) {
995+
when.scopeDefiningTerms.terms.push(topicTerms);
996+
} else {
997+
when.scopeDefiningTerms = topicTerms;
998+
}
999+
}
9851000
}
9861001
return when;
9871002
}

ts/packages/knowPro/src/searchQueryTranslator.ts

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Result,
88
TypeChatLanguageModel,
99
error,
10+
TypeChatJsonTranslator,
1011
} from "typechat";
1112
import * as querySchema from "./searchQuerySchema.js";
1213
import * as querySchema2 from "./searchQuerySchema_v2.js";
@@ -38,30 +39,15 @@ export interface SearchQueryTranslator {
3839
export function createSearchQueryTranslator(
3940
model: TypeChatLanguageModel,
4041
): SearchQueryTranslator {
41-
const typeName = "SearchQuery";
42-
const searchActionSchema = loadSchema(
43-
["dateTimeSchema.ts", "searchQuerySchema.ts"],
44-
import.meta.url,
45-
);
46-
const searchActionSchemaScope = loadSchema(
47-
["dateTimeSchema.ts", "searchQuerySchema_v2.ts"],
48-
import.meta.url,
49-
);
50-
51-
const translator = createJsonTranslator<querySchema.SearchQuery>(
52-
model,
53-
createTypeScriptJsonValidator<querySchema.SearchQuery>(
54-
searchActionSchema,
55-
typeName,
56-
),
57-
);
58-
const translator_V2 = createJsonTranslator<querySchema2.SearchQuery>(
42+
const translator = createSearchQueryJsonTranslator<querySchema.SearchQuery>(
5943
model,
60-
createTypeScriptJsonValidator<querySchema2.SearchQuery>(
61-
searchActionSchemaScope,
62-
typeName,
63-
),
44+
"searchQuerySchema.ts",
6445
);
46+
const translator_V2 =
47+
createSearchQueryJsonTranslator<querySchema2.SearchQuery>(
48+
model,
49+
"searchQuerySchema_v2.ts",
50+
);
6551
return {
6652
translate(request, promptPreamble) {
6753
return translator.translate(request, promptPreamble);
@@ -72,6 +58,29 @@ export function createSearchQueryTranslator(
7258
};
7359
}
7460

61+
/**
62+
* Create a query translator using
63+
* @param {TypeChatLanguageModel} model
64+
* @param schemaFilePath Relative path to schema file
65+
* @returns {SearchQueryTranslator}
66+
*/
67+
export function createSearchQueryJsonTranslator<
68+
T extends querySchema.SearchQuery | querySchema2.SearchQuery,
69+
>(
70+
model: TypeChatLanguageModel,
71+
schemaFilePath: string,
72+
): TypeChatJsonTranslator<T> {
73+
const typeName = "SearchQuery";
74+
const searchActionSchema = loadSchema(
75+
["dateTimeSchema.ts", schemaFilePath],
76+
import.meta.url,
77+
);
78+
return createJsonTranslator<T>(
79+
model,
80+
createTypeScriptJsonValidator<T>(searchActionSchema, typeName),
81+
);
82+
}
83+
7584
export async function searchQueryFromLanguage(
7685
conversation: IConversation,
7786
queryTranslator: SearchQueryTranslator,

0 commit comments

Comments
 (0)