Skip to content

Commit 413c5d3

Browse files
authored
knowPro: batch validation updates (#1433)
Batch testing of scope query schema.
1 parent a984dec commit 413c5d3

File tree

6 files changed

+238
-67
lines changed

6 files changed

+238
-67
lines changed

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

Lines changed: 111 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export async function createKnowproTestCommands(
5454
commands.kpTestChoices = testMultipleChoice;
5555
commands.kpTestSearch = testSearchScope;
5656
commands.kpTestScoped = setScoped;
57+
commands.kpTestSearchCompare = testSearchCompare;
58+
commands.kpTestSearchCompareBatch = testSearchCompareBatch;
5759

5860
async function testHtml(args: string[]) {
5961
const html = await readAllText(args[0]);
@@ -473,75 +475,118 @@ export async function createKnowproTestCommands(
473475

474476
function testSearchDef(): CommandMetadata {
475477
const def = searchDef();
476-
def.options ??= {};
477-
def.options!.compare = argBool("Compare to V1 mode", true);
478+
def.description = "Test v2 (scoped) search";
478479
return def;
479480
}
480481
commands.kpTestSearch.metadata = testSearchDef();
481482
async function testSearchScope(args: string[]) {
482483
const namedArgs = parseNamedArguments(args, testSearchDef());
483-
const queryTranslator =
484-
context.conversation! instanceof cm.Memory
485-
? context.conversation!.settings.queryTranslator
486-
: context.queryTranslator;
487-
const result = namedArgs.compare
488-
? await queryTranslator.translate(namedArgs.query)
489-
: undefined;
490-
try {
491-
context.printer.pushColor(chalk.gray);
492-
if (result) {
493-
context.printer.writeTranslation(result);
494-
}
495-
} finally {
496-
context.printer.popColor();
497-
}
498-
499-
context.printer.writeHeading("Scope");
484+
const queryTranslator = context.getConversationQueryTranslator();
500485
const resultScope = await queryTranslator.translate2!(namedArgs.query);
501486
context.printer.writeTranslation(resultScope);
502-
if ((result === undefined || result.success) && resultScope.success) {
503-
if (result !== undefined) {
504-
const error = kpTest.compareSearchQueryScope(
505-
result.data,
506-
resultScope.data,
507-
);
508-
if (error !== undefined) {
509-
context.printer.writeError(error);
510-
}
511-
}
512-
if (!context.conversation) {
513-
return;
514-
}
515-
const request = parseTypedArguments<kpTest.SearchRequest>(
516-
args,
517-
searchDef(),
518-
);
519-
const options: kp.LanguageSearchOptions = {
520-
...kpTest.createSearchOptions(request),
521-
compileOptions: {
522-
exactScope: request.exactScope,
523-
applyScope: request.applyScope,
524-
},
525-
};
526-
const query = kp.compileSearchQuery2(
487+
if (!resultScope.success || !ensureConversationLoaded()) {
488+
return;
489+
}
490+
const request = parseTypedArguments<kpTest.SearchRequest>(
491+
args,
492+
searchDef(),
493+
);
494+
const options: kp.LanguageSearchOptions = {
495+
...kpTest.createSearchOptions(request),
496+
compileOptions: {
497+
exactScope: request.exactScope,
498+
applyScope: request.applyScope,
499+
},
500+
};
501+
const query = kp.compileSearchQuery2(
502+
context.conversation!,
503+
resultScope.data,
504+
options.compileOptions,
505+
);
506+
507+
const results = (
508+
await kp.runSearchQueries(context.conversation!, query, options)
509+
).flat();
510+
for (let i = 0; i < results.length; ++i) {
511+
context.printer.writeConversationSearchResult(
527512
context.conversation!,
528-
resultScope.data,
529-
options.compileOptions,
513+
results[i],
514+
namedArgs.showKnowledge,
515+
namedArgs.showMessages,
516+
namedArgs.maxToDisplay,
517+
namedArgs.distinct,
530518
);
519+
}
520+
}
521+
522+
commands.kpTestSearchCompare.metadata = testSearchDef();
523+
async function testSearchCompare(args: string[]) {
524+
const namedArgs = parseNamedArguments(args, testSearchDef());
525+
const queryTranslator = context.getConversationQueryTranslator();
526+
const result = await kpTest.compareSearchQueryTranslations(
527+
queryTranslator,
528+
namedArgs.query,
529+
);
530+
if (!result.success) {
531+
context.printer.writeError(result.message);
532+
return;
533+
}
534+
writeSearchComparison(result.data);
535+
}
531536

532-
const results = (
533-
await kp.runSearchQueries(context.conversation!, query, options)
534-
).flat();
535-
for (let i = 0; i < results.length; ++i) {
536-
context.printer.writeConversationSearchResult(
537-
context.conversation!,
538-
results[i],
539-
namedArgs.showKnowledge,
540-
namedArgs.showMessages,
541-
namedArgs.maxToDisplay,
542-
namedArgs.distinct,
537+
function compareSearchScopeBatchDef(): CommandMetadata {
538+
return {
539+
description: "Compare query translations in a batch",
540+
args: {
541+
srcPath: argSourceFile(),
542+
},
543+
options: {
544+
verbose: argBool("Verbose error output", false),
545+
},
546+
};
547+
}
548+
commands.kpTestSearchCompareBatch.metadata = compareSearchScopeBatchDef();
549+
async function testSearchCompareBatch(args: string[]) {
550+
const namedArgs = parseNamedArguments(args, verifySearchBatchDef());
551+
const prevOptions = beginTestBatch(namedArgs);
552+
try {
553+
const srcPath = namedArgs.srcPath;
554+
const results = await kpTest.compareSearchQueryTranslationsBatch(
555+
context,
556+
srcPath,
557+
(result, index, total) => {
558+
context.printer.writeProgress(index + 1, total);
559+
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+
}
569+
} else {
570+
context.printer.writeError(result.message);
571+
}
572+
},
573+
);
574+
if (!results.success) {
575+
context.printer.writeError(results.message);
576+
return;
577+
}
578+
let errorResults = results.data.filter(
579+
(r) => r.error !== undefined && r.error.length > 0,
580+
);
581+
if (errorResults.length > 0) {
582+
context.printer.writeLine(`${errorResults.length} errors`);
583+
context.printer.writeList(
584+
errorResults.map((e) => e.query),
585+
{ type: "ol" },
543586
);
544587
}
588+
} finally {
589+
endTestBatch(prevOptions);
545590
}
546591
}
547592

@@ -644,6 +689,15 @@ export async function createKnowproTestCommands(
644689
}
645690
}
646691

692+
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);
696+
if (cmp.error) {
697+
context.printer.writeError(cmp.error);
698+
}
699+
}
700+
647701
function beginTestBatch(
648702
namedArgs: NamedArgs,
649703
): kpTest.KnowproContextOptions {

ts/packages/knowProTest/src/common.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@
33

44
import { TextEmbeddingModel } from "aiclient";
55
import fs from "fs";
6-
import { ArgDef, NamedArgs, parseCommandLine } from "interactive-app";
6+
import {
7+
ArgDef,
8+
getBatchFileLines,
9+
NamedArgs,
10+
parseCommandLine,
11+
} from "interactive-app";
712
import path from "path";
813
import {
914
collections,
1015
dateTime,
1116
dotProduct,
1217
generateTextEmbeddingsWithRetry,
1318
getFileName,
19+
writeJsonFile,
1420
} from "typeagent";
15-
import { error, Result, Error } from "typechat";
16-
import { ComparisonResult } from "./types.js";
21+
import { error, Result, Error, success } from "typechat";
22+
import { BatchCallback, ComparisonResult } from "./types.js";
1723

1824
export function ensureDirSync(folderPath: string): string {
1925
if (!fs.existsSync(folderPath)) {
@@ -85,6 +91,40 @@ export async function execCommandLine<T>(
8591
return error("No args");
8692
}
8793

94+
export async function runTestBatch<T>(
95+
batchFilePath: string,
96+
cmdHandler: (cmd: string, args: string[]) => Promise<Result<T>>,
97+
destFilePath?: string,
98+
cb?: BatchCallback<Result<T>>,
99+
stopOnError: boolean = false,
100+
): Promise<Result<T[]>> {
101+
const batchLines = getBatchFileLines(batchFilePath);
102+
const results: T[] = [];
103+
for (let i = 0; i < batchLines.length; ++i) {
104+
const cmd = batchLines[i];
105+
const args = getCommandArgs(cmd);
106+
if (args.length === 0) {
107+
continue;
108+
}
109+
let response = await cmdHandler(cmd, args);
110+
if (!response.success) {
111+
response = queryError(cmd, response);
112+
}
113+
if (cb) {
114+
cb(response, i, batchLines.length);
115+
}
116+
if (response.success) {
117+
results.push(response.data);
118+
} else if (stopOnError) {
119+
return response;
120+
}
121+
}
122+
if (destFilePath) {
123+
await writeJsonFile(destFilePath, results);
124+
}
125+
return success(results);
126+
}
127+
88128
export function argSourceFile(defaultValue?: string | undefined): ArgDef {
89129
return {
90130
description: "Path to source file",

ts/packages/knowProTest/src/knowproContext.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,18 @@ export class KnowproContext {
7272
return this.conversation!;
7373
}
7474

75+
public getConversationQueryTranslator(): kp.SearchQueryTranslator {
76+
let queryTranslator = this.queryTranslator;
77+
78+
if (
79+
this.conversation !== undefined &&
80+
this.conversation! instanceof cm.Memory
81+
) {
82+
queryTranslator = this.conversation!.settings.queryTranslator;
83+
}
84+
return queryTranslator;
85+
}
86+
7587
public createMemorySettings(): cm.MemorySettings {
7688
return cm.createMemorySettings(64, undefined, this.knowledgeModel);
7789
}

ts/packages/knowProTest/src/searchTest.ts

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@
44
import { KnowproContext } from "./knowproContext.js";
55
import { dateTime, readJsonFile, writeJsonFile } from "typeagent";
66
import { error, Result, success } from "typechat";
7-
import { BatchCallback, Comparison } from "./types.js";
7+
import {
8+
BatchCallback,
9+
Comparison,
10+
SearchRequest,
11+
searchRequestDef,
12+
} from "./types.js";
813
import {
914
compareNumberArray,
1015
compareObject,
1116
compareStringArray,
1217
dateRangeToTimeRange,
1318
getCommandArgs,
1419
queryError,
20+
runTestBatch,
1521
} from "./common.js";
1622
import { getLangSearchResult } from "./knowproCommands.js";
17-
import { getBatchFileLines } from "interactive-app";
23+
import { getBatchFileLines, parseTypedArguments } from "interactive-app";
1824
import { execSearchRequest } from "./knowproCommands.js";
1925
import * as kp from "knowpro";
2026
import { TestRunReport } from "./logging.js";
@@ -34,6 +40,7 @@ export type LangSearchResult = {
3440
actionMatches?: kp.SemanticRefOrdinal[] | undefined;
3541
};
3642

43+
// TODO: convert this to use runBatch from common.ts
3744
export async function runSearchBatch(
3845
context: KnowproContext,
3946
batchFilePath: string,
@@ -69,6 +76,7 @@ export async function runSearchBatch(
6976
}
7077
return success(results);
7178
}
79+
7280
async function getSearchResults(
7381
context: KnowproContext,
7482
args: string[],
@@ -446,3 +454,61 @@ export function compareSearchExprScope(
446454
}
447455
return undefined;
448456
}
457+
458+
export interface ScopeQueryComparison
459+
extends Comparison<
460+
kp.querySchema2.SearchQuery,
461+
kp.querySchema.SearchQuery
462+
> {
463+
query: string;
464+
}
465+
466+
export async function compareSearchQueryTranslations(
467+
queryTranslator: kp.SearchQueryTranslator,
468+
query: string,
469+
): Promise<Result<ScopeQueryComparison>> {
470+
if (!queryTranslator.translate2) {
471+
return error("not supported");
472+
}
473+
const result = await queryTranslator.translate(query);
474+
if (!result.success) {
475+
return result;
476+
}
477+
const resultScope = await queryTranslator.translate2!(query);
478+
if (!resultScope.success) {
479+
return resultScope;
480+
}
481+
482+
let cmp: ScopeQueryComparison = {
483+
actual: resultScope.data,
484+
expected: result.data,
485+
query,
486+
};
487+
cmp.error = compareSearchQueryScope(result.data, resultScope.data);
488+
return success(cmp);
489+
}
490+
491+
export async function compareSearchQueryTranslationsBatch(
492+
context: KnowproContext,
493+
batchFilePath: string,
494+
cb?: BatchCallback<Result<ScopeQueryComparison>>,
495+
stopOnError: boolean = false,
496+
): Promise<Result<ScopeQueryComparison[]>> {
497+
const queryTranslator = context.getConversationQueryTranslator();
498+
return runTestBatch(
499+
batchFilePath,
500+
(_, args: string[]) => {
501+
const request = parseTypedArguments<SearchRequest>(
502+
args,
503+
searchRequestDef(),
504+
);
505+
return compareSearchQueryTranslations(
506+
queryTranslator,
507+
request.query,
508+
);
509+
},
510+
undefined,
511+
cb,
512+
stopOnError,
513+
);
514+
}

0 commit comments

Comments
 (0)