Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions libraries/botbuilder-ai/src/qnamaker-interfaces/prompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @module botbuilder-ai
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* Prompt Object.
*/
export interface Prompt {

/**
* Display Order - index of the prompt - used in ordering of the prompts.
*/
displayOrder: number;

/**
* Qna id corresponding to the prompt - if QnaId is present, QnADTO object is ignored.
*/
qnaId: number;

/**
* The QnA object returned from the API (Optional parameter).
*/
qna?: object;

/**
* Display Text - Text displayed to represent a follow up question prompt.
*/
displayText: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @module botbuilder-ai
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* The context associated with QnA. Used to mark if the current prompt is relevant with a previous question or not.
*/
export interface QnARequestContext {

/**
* The previous QnA Id that was returned.
*/
previousQnAId: number;

/**
* The previous user query/question.
*/
previousUserQuery: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @module botbuilder-ai
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import { Prompt } from './prompt';

/**
* The context associated with QnA. Used to mark if the qna response has related prompts.
*/
export interface QnAResponseContext {

/**
* The prompts collection of related prompts.
*/
prompts: Prompt[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { QnAMakerMetadata } from './qnamakerMetadata';
import { QnARequestContext } from './qnaRequestContext';

/**
* Additional settings used to configure a `QnAMaker` instance.
Expand Down Expand Up @@ -43,4 +44,9 @@ export interface QnAMakerOptions {
* @remarks Defaults to "100000" milliseconds.
*/
timeout?: number;

/**
* The context of the previous turn.
*/
context?: QnARequestContext;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* Licensed under the MIT License.
*/

import { QnAResponseContext } from './qnaResponseContext';

/**
* An individual answer returned by a call to the QnA Maker Service.
*/
Expand Down Expand Up @@ -39,4 +41,9 @@ export interface QnAMakerResult {
* The index of the answer in the knowledge base. V3 uses 'qnaId', V4 uses 'id'. (If any)
*/
id?: number;

/**
* Context for multi-turn responses.
*/
context?: QnAResponseContext;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { Activity } from 'botbuilder-core';
import { QnAMakerResult } from './qnamakerResult';
import { QnARequestContext } from './qnaRequestContext';

/**
* Trace info that we collect and emit from a QnA Maker query
Expand Down Expand Up @@ -47,4 +48,9 @@ export interface QnAMakerTraceInfo {
* Metadata related to query. Not used in JavaScript SDK v4 yet.
*/
metadataBoost: any[];

/**
* The context for multi-turn responses.
*/
context?: QnARequestContext;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { QnAMakerResult } from '../qnamaker-interfaces/qnamakerResult';
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 '..';
Expand Down Expand Up @@ -61,25 +62,26 @@ export class GenerateAnswerUtils {
/**
* Emits a trace event detailing a QnA Maker call and its results.
*
* @param context Context for the current turn of conversation with the user.
* @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(context: TurnContext, answers: QnAMakerResult[], queryOptions?: QnAMakerOptions): Promise<any> {
public async emitTraceInfo(turnContext: TurnContext, answers: QnAMakerResult[], queryOptions?: QnAMakerOptions): Promise<any> {
const requestOptions: QnAMakerOptions = { ...this._options, ...queryOptions };
const { scoreThreshold, top, strictFilters, metadataBoost } = requestOptions;
const { scoreThreshold, top, strictFilters, metadataBoost, context } = requestOptions;

const traceInfo: QnAMakerTraceInfo = {
message: context.activity,
message: turnContext.activity,
queryResults: answers,
knowledgeBaseId: this.endpoint.knowledgeBaseId,
scoreThreshold,
top,
strictFilters,
metadataBoost
metadataBoost,
context
};

return context.sendActivity({
return turnContext.sendActivity({
type: 'trace',
valueType: QNAMAKER_TRACE_TYPE,
name: QNAMAKER_TRACE_NAME,
Expand All @@ -94,7 +96,7 @@ export class GenerateAnswerUtils {
* @param options (Optional) The options for the QnA Maker knowledge base. If null, constructor option is used for this instance.
*/
public validateOptions(options: QnAMakerOptions): void {
const { scoreThreshold, top } = options;
const { scoreThreshold, top, context } = options;

if (scoreThreshold) {
this.validateScoreThreshold(scoreThreshold);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"answers": [
{
"questions": [
"Where can I buy cleaning products?"
],
"answer": "Any DIY store",
"score": 100,
"id": 55,
"source": "Editorial",
"metadata": [],
"context": {
"isContextOnly": true,
"prompts": []
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"answers": [
{
"questions": [
"Where can I buy home appliances?"
],
"answer": "Any Walmart store",
"score": 68,
"id": 56,
"source": "Editorial",
"metadata": [],
"context": {
"isContextOnly": false,
"prompts": []
}
},
{
"questions": [
"Where can I buy cleaning products?"
],
"answer": "Any DIY store",
"score": 56,
"id": 55,
"source": "Editorial",
"metadata": [],
"context": {
"isContextOnly": false,
"prompts": []
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"answers": [
{
"questions": [
"how do I clean the stove?"
],
"answer": "BaseCamp: You can use a damp rag to clean around the Power Pack",
"score": 100,
"id": 5,
"source": "Editorial",
"metadata": [],
"context": {
"isContextOnly": true,
"prompts": [
{
"displayOrder": 0,
"qnaId": 55,
"qna": null,
"displayText": "Where can I buy?"
}
]
}
}
]
}
36 changes: 36 additions & 0 deletions libraries/botbuilder-ai/tests/qnaMaker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,42 @@ describe('QnAMaker', function () {
assert.strictEqual(qnaResults, descendingQnaResults, 'answers should be sorted from greatest to least score');
});

it('should return answer with prompts', async function() {
const qna = new QnAMaker(endpoint);
const context = new TestContext({ text: "how do I clean the stove?" });
const options = { top: 2 };

const qnaResults = await qna.getAnswers(context, options);

assert.strictEqual(qnaResults.length, 1, 'one answer should be returned');
assert.strictEqual(qnaResults[0].context.prompts.length > 0, true, 'One or more prompts should be present');
});

it('should return answer with high score provided context', async function() {
const qna = new QnAMaker(endpoint);
const turnContext = new TestContext({ text: "where can I buy?" });

const context = { previousQnAId: 5, previousUserQuery: "how do I clean the stove?"};
const options = { top: 2, context: context };

const qnaResults = await qna.getAnswers(turnContext, options);

assert.strictEqual(qnaResults.length, 1, 'one answer should be returned');
assert.strictEqual(qnaResults[0].score, 1, 'score should be high');
});

it('should return answer with low score not provided context', async function() {
const qna = new QnAMaker(endpoint);
const turnContext = new TestContext({ text: "where can I buy?" });

const options = { top: 2, context: null };

const qnaResults = await qna.getAnswers(turnContext, options);

assert.strictEqual(qnaResults.length, 2, 'one answer should be returned');
assert.strictEqual(qnaResults[0].score < 1, true, 'score should be low');
});

it('should return answer with timeout option specified', async function() {
const timeoutOption = { timeout: 500000 };
const qna = new QnAMaker(endpoint, timeoutOption);
Expand Down