-
Notifications
You must be signed in to change notification settings - Fork 283
/
Copy pathgenerateAnswerUtils.ts
170 lines (142 loc) · 6.82 KB
/
generateAnswerUtils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/**
* @module botbuilder-ai
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { TurnContext } from 'botbuilder-core';
import { QnAMakerResult } from '../qnamaker-interfaces/qnamakerResult';
import { QnAMakerResults } from '../qnamaker-interfaces/qnamakerResults';
import { QnAMakerEndpoint } from '../qnamaker-interfaces/qnamakerEndpoint';
import { QnAMakerOptions } from '../qnamaker-interfaces/qnamakerOptions';
import { QnAMakerTraceInfo } from '../qnamaker-interfaces/qnamakerTraceInfo';
import { QnARequestContext } from '../qnamaker-interfaces/qnaRequestContext';
import { HttpRequestUtils } from './httpRequestUtils';
import { QNAMAKER_TRACE_TYPE, QNAMAKER_TRACE_LABEL, QNAMAKER_TRACE_NAME } from '..';
import { RankerTypes } from '../qnamaker-interfaces/rankerTypes';
/**
* Generate Answer api utils class.
*
* @remarks
* This class is helper class for generate answer api, which is used to make queries to a single QnA Maker knowledge base and return the result.
*/
export class GenerateAnswerUtils {
httpRequestUtils: HttpRequestUtils;
/**
* Creates new Generate answer utils.
* @param _options Settings used to configure the instance.
* @param endpoint The endpoint of the knowledge base to query.
*/
constructor (public _options: QnAMakerOptions, private readonly endpoint: QnAMakerEndpoint) {
this.httpRequestUtils = new HttpRequestUtils();
this.validateOptions(this._options);
}
/**
* Called internally to query the QnA Maker service.
* @param endpoint The endpoint of the knowledge base to query.
* @param question Question which need to be queried.
* @param options (Optional) The options for the QnA Maker knowledge base. If null, constructor option is used for this instance.
*/
public async queryQnaService(endpoint: QnAMakerEndpoint, question: string, options?: QnAMakerOptions): Promise<QnAMakerResult[]> {
var result = await this.queryQnaServiceRaw(endpoint, question, options);
return result.answers;
}
/**
* Called internally to query the QnA Maker service.
* @param endpoint The endpoint of the knowledge base to query.
* @param question Question which need to be queried.
* @param options (Optional) The options for the QnA Maker knowledge base. If null, constructor option is used for this instance.
*/
public async queryQnaServiceRaw(endpoint: QnAMakerEndpoint, question: string, options?: QnAMakerOptions): Promise<QnAMakerResults> {
const url: string = `${ endpoint.host }/knowledgebases/${ endpoint.knowledgeBaseId }/generateanswer`;
var queryOptions: QnAMakerOptions = { ...this._options, ...options } as QnAMakerOptions;
queryOptions.rankerType = !queryOptions.rankerType ? RankerTypes.default : queryOptions.rankerType;
this.validateOptions(queryOptions);
var payloadBody = JSON.stringify({
question: question,
...queryOptions
});
const qnaResultJson: any = await this.httpRequestUtils.executeHttpRequest(url, payloadBody, this.endpoint, queryOptions.timeout);
return this.formatQnaResult(qnaResultJson);
}
/**
* Emits a trace event detailing a QnA Maker call and its results.
*
* @param turnContext Turn Context for the current turn of conversation with the user.
* @param answers Answers returned by QnA Maker.
* @param options (Optional) The options for the QnA Maker knowledge base. If null, constructor option is used for this instance.
*/
public async emitTraceInfo(turnContext: TurnContext, answers: QnAMakerResult[], queryOptions?: QnAMakerOptions): Promise<any> {
const requestOptions: QnAMakerOptions = { ...this._options, ...queryOptions };
const { scoreThreshold, top, strictFilters, metadataBoost, context, qnaId } = requestOptions;
const traceInfo: QnAMakerTraceInfo = {
message: turnContext.activity,
queryResults: answers,
knowledgeBaseId: this.endpoint.knowledgeBaseId,
scoreThreshold,
top,
strictFilters,
metadataBoost,
context,
qnaId,
};
return turnContext.sendActivity({
type: 'trace',
valueType: QNAMAKER_TRACE_TYPE,
name: QNAMAKER_TRACE_NAME,
label: QNAMAKER_TRACE_LABEL,
value: traceInfo
});
}
/**
* Validate qna maker options
*
* @param options (Optional) The options for the QnA Maker knowledge base. If null, constructor option is used for this instance.
*/
public validateOptions(options: QnAMakerOptions) {
const { scoreThreshold, top, rankerType } = options;
if (scoreThreshold) {
this.validateScoreThreshold(scoreThreshold);
}
if (top) {
this.validateTop(top);
}
}
/**
* Sorts all QnAMakerResult from highest-to-lowest scoring.
* Filters QnAMakerResults within threshold specified (default threshold: .001).
*
* @param answers Answers returned by QnA Maker.
* @param options (Optional) The options for the QnA Maker knowledge base. If null, constructor option is used for this instance.
*/
public static sortAnswersWithinThreshold(answers: QnAMakerResult[] = [] as QnAMakerResult[], queryOptions: QnAMakerOptions): QnAMakerResult[] {
const minScore: number = typeof queryOptions.scoreThreshold === 'number' ? queryOptions.scoreThreshold : 0.001;
return answers.filter((ans: QnAMakerResult) => ans.score >= minScore)
.sort((a: QnAMakerResult, b: QnAMakerResult) => b.score - a.score);
}
private formatQnaResult(qnaResult: any): QnAMakerResults {
qnaResult.answers = qnaResult.answers.map((ans: any) => {
ans.score = ans.score / 100;
if (ans.qnaId) {
ans.id = ans.qnaId;
delete ans.qnaId;
}
return ans as QnAMakerResult;
});
qnaResult.activeLearningEnabled = (qnaResult.activeLearningEnabled != null) ? qnaResult.activeLearningEnabled : true;
return qnaResult;
}
private validateScoreThreshold(scoreThreshold: number): void {
if (typeof scoreThreshold !== 'number' || !(scoreThreshold > 0 && scoreThreshold < 1)) {
throw new TypeError(
`"${ scoreThreshold }" is an invalid scoreThreshold. QnAMakerOptions.scoreThreshold must have a value between 0 and 1.`
);
}
}
private validateTop(qnaOptionTop: number): void {
if (!Number.isInteger(qnaOptionTop) || qnaOptionTop < 1) {
throw new RangeError(`"${ qnaOptionTop }" is an invalid top value. QnAMakerOptions.top must be an integer greater than 0.`);
}
}
}