Skip to content
Draft
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
3 changes: 2 additions & 1 deletion samples/javascript_nodejs/48.customQABot-all-features/.env
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ MicrosoftAppTenantId=

ProjectName=
LanguageEndpointKey=
LanguageManagedIdentityClientId=
LanguageEndpointHostName=
DefaultAnswer=
DefaultWelcomeMessage=
EnablePreciseAnswer= true
DisplayPreciseAnswerOnly= false
UseTeamsAdaptiveCard= false
UseTeamsAdaptiveCard= false
31 changes: 30 additions & 1 deletion samples/javascript_nodejs/48.customQABot-all-features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,36 @@ This bot was created using [Bot Framework][BF].
- Go to `Deploy knowledge base` and click on `Deploy`.

### Connect your bot to the project.
Follow these steps to update [.env file](.env).
There are two ways the bot could authenticate to the Language resource.

Pick one and follow these steps to update [.env file](.env) accordingly.

1. Using an `Endpoint Key`: _provides an easier configuration by using a secret. Great way to test the bot locally_.

- In the [Azure Portal][Azure], go to your resource.
- Go to `Keys and Endpoint` under Resource Management.
- Copy one of the keys as value of `LanguageEndpointKey` and Endpoint as value of `LanguageEndpointHostName` in [.env file](.env).
- `ProjectName` is the name of the project created in [Language Studio][LS].
- `LanguageManagedIdentityClientId` is not needed when using an Endpoint Key, so you can remove it from [.env file](.env).

2. Using a `User Managed Identity` resource: _provides a more complex configuration by using a User Managed Identity resource. Great way to authenticate without the need of a secret_.
- Create a [User Managed Identity][create-msi] resource in the same region as the Language resource.
- Copy the `ClientId` as value of `LanguageManagedIdentityClientId` in [.env file](.env).
- In the [Azure Portal][Azure], go to the WebApp resource, where the bot is hosted.
- Go to `Identity` under Settings and select `User assigned`. More information on Identity assignment can be found [here][webapp-msi].
- Click on `Add` and select the User Managed Identity created in the previous step.
- Click `Save` to assign the User Managed Identity to the WebApp resource.
- This will allow the WebApp to communicate with the Language resource using the User Managed Identity.
- In the [Azure Portal][Azure], go to the Language resource.
- Assign the following role in the `Access Control (IAM)` section. More information on role assignment can be found [here][language-custom-role].
- `Cognitive Services User`: _this role is required so the Managed Identity can access the keys of the Cognitive Service resource_.
- In the Language resource, go to `Keys and Endpoint` under Resource Management.
- Copy the `Endpoint` as value of `LanguageEndpointHostName` in [.env file](.env).
- `ProjectName` is the name of the project created in [Language Studio][LS].
- `LanguageEndpointKey` is not needed when using a User Managed Identity, so you can remove it from [.env file](.env).

> [!NOTE]
> This method requires [the bot to be deployed in Azure][deploy-bot], so the User Managed Identity can authenticate to the Language resource to get access to the keys.

## To try this sample

Expand Down Expand Up @@ -188,3 +213,7 @@ If you are new to Microsoft Azure, please refer to [Getting started with Azure][
[Quickstart]: https://docs.microsoft.com/azure/cognitive-services/language-service/question-answering/quickstart/sdk
[Azure]: https://portal.azure.com/
[BFE]: https://github.com/Microsoft/BotFramework-Emulator/releases
[deploy-bot]: #deploy-the-bot-to-azure
[create-msi]: https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities?pivots=identity-mi-methods-azp#create-a-user-assigned-managed-identity
[language-custom-role]: https://learn.microsoft.com/en-us/azure/operator-service-manager/how-to-create-user-assigned-managed-identity#assign-custom-role-1
[webapp-msi]: https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal%2Chttp
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ const INCLUDE_UNSTRUCTURED_SOURCES = true;
/**
* Creates QnAMakerDialog instance with provided configuraton values.
*/
const createQnAMakerDialog = (knowledgeBaseId, endpointKey, endpointHostName, defaultAnswer, enablePreciseAnswerRaw, displayPreciseAnswerOnlyRaw, useTeamsAdaptiveCard) => {
const createQnAMakerDialog = (knowledgeBaseId, endpointKey, managedIdentityClientId, endpointHostName, defaultAnswer, enablePreciseAnswerRaw, displayPreciseAnswerOnlyRaw, useTeamsAdaptiveCard) => {
let noAnswerActivity;
if (typeof defaultAnswer === 'string' && defaultAnswer !== '') {
noAnswerActivity = MessageFactory.text(defaultAnswer);
}

const qnaMakerDialog = new QnAMakerDialog(
knowledgeBaseId,
endpointKey,
undefined,
endpointHostName,
// @ts-ignore
noAnswerActivity,
Expand All @@ -48,6 +48,12 @@ const createQnAMakerDialog = (knowledgeBaseId, endpointKey, endpointHostName, de
RANKER_TYPE
);

if (managedIdentityClientId) {
qnaMakerDialog.withManagedIdentityClientId(managedIdentityClientId);
} else {
qnaMakerDialog.withEndpointKey(endpointKey);
}

qnaMakerDialog.enablePreciseAnswer = enablePreciseAnswerRaw === 'true';
qnaMakerDialog.displayPreciseAnswerOnly = displayPreciseAnswerOnlyRaw === 'true';
qnaMakerDialog.qnaServiceType = ServiceType.language;
Expand All @@ -63,21 +69,22 @@ class RootDialog extends ComponentDialog {
/**
* Root dialog for this bot. Creates a QnAMakerDialog.
* @param {string} knowledgeBaseId Knowledge Base ID of the QnA Maker instance.
* @param {string} endpointKey Endpoint key needed to query QnA Maker.
* @param {any} endpointKey (optional) Endpoint key needed to query QnA Maker.
* @param {any} managedIdentityClientId (optional) Managed identity client ID to use for authentication.
* @param {string} endpointHostName Host name of the QnA Maker instance.
* @param {string} defaultAnswer (optional) Text used to create a fallback response when QnA Maker doesn't have an answer for a question.
* @param {string} enablePreciseAnswer
* @param {string} displayPreciseAnswerOnly

*/
constructor(knowledgeBaseId, endpointKey, endpointHostName, defaultAnswer, enablePreciseAnswer, displayPreciseAnswerOnly, useTeamsAdaptiveCard) {
constructor(knowledgeBaseId, endpointKey, managedIdentityClientId, endpointHostName, defaultAnswer, enablePreciseAnswer, displayPreciseAnswerOnly, useTeamsAdaptiveCard) {
super(ROOT_DIALOG);
// Initial waterfall dialog.
this.addDialog(new WaterfallDialog(INITIAL_DIALOG, [
this.startInitialDialog.bind(this)
]));

this.addDialog(createQnAMakerDialog(knowledgeBaseId, endpointKey, endpointHostName, defaultAnswer, enablePreciseAnswer, displayPreciseAnswerOnly, useTeamsAdaptiveCard));
this.addDialog(createQnAMakerDialog(knowledgeBaseId, endpointKey, managedIdentityClientId, endpointHostName, defaultAnswer, enablePreciseAnswer, displayPreciseAnswerOnly, useTeamsAdaptiveCard));
this.initialDialogId = INITIAL_DIALOG;
}

Expand Down
28 changes: 17 additions & 11 deletions samples/javascript_nodejs/48.customQABot-all-features/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@ const restify = require('restify');

// Import required bot services.
// See https://aka.ms/bot-services to learn more about the different parts of a bot.
const { BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } = require('botbuilder');
const { CloudAdapter, ConversationState, MemoryStorage, UserState, ConfigurationBotFrameworkAuthentication } = require('botbuilder');

const { CustomQABot } = require('./bots/CustomQABot');
const { RootDialog } = require('./dialogs/rootDialog');

const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(process.env);

// Create adapter.
// See https://aka.ms/about-bot-adapter to learn more about adapters.
const adapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
});
// See https://aka.ms/about-bot-adapter to learn more about how bots work.
const adapter = new CloudAdapter(botFrameworkAuthentication);

// Catch-all for errors.
adapter.onTurnError = async (context, error) => {
Expand Down Expand Up @@ -65,13 +64,20 @@ if (!endpointHostName?.startsWith('https://')) {
endpointHostName = 'https://' + endpointHostName;
}

const managedIdentityClientId = process.env.LanguageManagedIdentityClientId;

// To support backward compatibility for Key Names, fallback to process.env.QnAAuthKey.
const endpointKey = process.env.LanguageEndpointKey || process.env.QnAAuthKey;

if (!managedIdentityClientId?.trim() && !endpointKey?.trim()) {
throw new Error('Either LanguageManagedIdentityClientId or LanguageEndpointKey should be set.');
}

// Create the main dialog.
const dialog = new RootDialog(
process.env.ProjectName ?? '',
endpointKey ?? '',
endpointKey,
managedIdentityClientId,
endpointHostName,
process.env.DefaultAnswer ?? '',
process.env.EnablePreciseAnswer?.toLowerCase() ?? 'false',
Expand All @@ -83,6 +89,8 @@ const bot = new CustomQABot(conversationState, userState, dialog);

// Create HTTP server.
const server = restify.createServer();
server.use(restify.plugins.bodyParser());

server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }.`);
console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');
Expand All @@ -91,8 +99,6 @@ server.listen(process.env.port || process.env.PORT || 3978, function() {

// Listen for incoming requests.
server.post('/api/messages', async (req, res) => {
adapter.processActivity(req, res, async (turnContext) => {
// Route the message to the bot's main handler.
await bot.run(turnContext);
});
// Route received a request to adapter for processing
await adapter.process(req, res, (context) => bot.run(context));
});
Loading