feat: change Init() to Factory patterns to avoid temporal coupling (Migrated branch) (#8)
* feat: use Factory pattern instead of static Init() * feat: change Init() to Factory patterns to avoid temporal coupling * fix: update sample demos * fix: paths to import from source instead of root * fix: clmodeloptions type * fix: exclude webchat from windows test
This commit is contained in:
Родитель
0b7e73ecb2
Коммит
07f262ec40
|
@ -1,68 +1,68 @@
|
|||
# https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema
|
||||
|
||||
trigger:
|
||||
- master
|
||||
- master
|
||||
|
||||
pr:
|
||||
autoCancel: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
- master
|
||||
|
||||
jobs:
|
||||
- job: linux_build
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
- job: linux_build
|
||||
pool:
|
||||
vmImage: "ubuntu-16.04"
|
||||
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js 10.x'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "10.x"
|
||||
displayName: "Install Node.js 10.x"
|
||||
|
||||
- bash: npm --version
|
||||
displayName: 'npm --version'
|
||||
- bash: npm --version
|
||||
displayName: "npm --version"
|
||||
|
||||
- bash: npx lerna boostrap
|
||||
displayName: 'npx lerna boostrap'
|
||||
- bash: npx lerna boostrap
|
||||
displayName: "npx lerna boostrap"
|
||||
|
||||
# - task: CacheBeta@0
|
||||
# inputs:
|
||||
# key: $(Build.SourcesDirectory)/package-lock.json
|
||||
# path: $(npm_config_cache)
|
||||
# displayName: Cache npm
|
||||
# - task: CacheBeta@0
|
||||
# inputs:
|
||||
# key: $(Build.SourcesDirectory)/package-lock.json
|
||||
# path: $(npm_config_cache)
|
||||
# displayName: Cache npm
|
||||
|
||||
- bash: npx lerna run build
|
||||
displayName: 'npx lerna run build'
|
||||
- bash: npx lerna run build
|
||||
displayName: "npx lerna run build"
|
||||
|
||||
- bash: npx lerna run test --ignore @conversationlearner/webchat
|
||||
displayName: 'npx lerna run test --ignore @conversationlearner/webchat'
|
||||
- bash: npx lerna run test --ignore @conversationlearner/webchat
|
||||
displayName: "npx lerna run test --ignore @conversationlearner/webchat"
|
||||
|
||||
- job: windows_build
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
- job: windows_build
|
||||
pool:
|
||||
vmImage: "windows-2019"
|
||||
|
||||
steps:
|
||||
- task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@2
|
||||
displayName: 'Run CredScan'
|
||||
inputs:
|
||||
debugMode: false
|
||||
steps:
|
||||
- task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@2
|
||||
displayName: "Run CredScan"
|
||||
inputs:
|
||||
debugMode: false
|
||||
|
||||
- task: securedevelopmentteam.vss-secure-development-tools.build-task-postanalysis.PostAnalysis@1
|
||||
displayName: 'Post Analysis'
|
||||
inputs:
|
||||
CredScan: true
|
||||
- task: securedevelopmentteam.vss-secure-development-tools.build-task-postanalysis.PostAnalysis@1
|
||||
displayName: "Post Analysis"
|
||||
inputs:
|
||||
CredScan: true
|
||||
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js 10.x'
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "10.x"
|
||||
displayName: "Install Node.js 10.x"
|
||||
|
||||
- script: npm ci
|
||||
displayName: 'npm ci'
|
||||
- script: npm ci
|
||||
displayName: "npm ci"
|
||||
|
||||
- script: npx lerna run build
|
||||
displayName: 'npx lerna run build'
|
||||
- script: npx lerna run build
|
||||
displayName: "npx lerna run build"
|
||||
|
||||
- script: npx lerna run test
|
||||
displayName: 'npx lerna run test'
|
||||
- script: npx lerna run test --ignore @conversationlearner/webchat
|
||||
displayName: "npx lerna run test --ignore @conversationlearner/webchat"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import * as path from 'path'
|
||||
import * as express from 'express'
|
||||
import { BotFrameworkAdapter } from 'botbuilder'
|
||||
import { ConversationLearner, ClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, ClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from './config'
|
||||
|
||||
|
@ -34,12 +34,12 @@ const fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
|||
//==================================
|
||||
// Initialize Conversation Learner
|
||||
//==================================
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage)
|
||||
const conversationLearnerFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
|
||||
const includeSdk = ['development', 'test'].includes(process.env.NODE_ENV ?? '')
|
||||
if (includeSdk) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', conversationLearnerFactory.sdkRouter)
|
||||
|
||||
// Note: Must be mounted at root to use internal /ui paths
|
||||
console.log(chalk.greenBright(`Adding /ui routes`))
|
||||
|
@ -49,7 +49,7 @@ if (includeSdk) {
|
|||
// Serve default bot summary page. Should be customized by customer.
|
||||
server.use(express.static(path.join(__dirname, '..', 'site')))
|
||||
|
||||
const cl = new ConversationLearner(modelId)
|
||||
const cl = conversationLearnerFactory.create(modelId)
|
||||
|
||||
//=================================
|
||||
// Add Entity Logic
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
import * as dotenv from 'dotenv'
|
||||
import * as convict from 'convict'
|
||||
import { ICLOptions } from '@conversationlearner/sdk'
|
||||
import { CLOptions } from '@conversationlearner/sdk'
|
||||
|
||||
const result = dotenv.config()
|
||||
if (result.error) {
|
||||
|
@ -78,7 +78,7 @@ export const config = convict({
|
|||
|
||||
config.validate({ allowed: 'strict' })
|
||||
|
||||
export interface ICLSampleConfig extends ICLOptions {
|
||||
export interface ICLSampleConfig extends CLOptions {
|
||||
modelId: string | undefined
|
||||
redisServer: string | undefined
|
||||
redisKey: string | undefined
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import * as path from 'path'
|
||||
import * as express from 'express'
|
||||
import { BotFrameworkAdapter } from 'botbuilder'
|
||||
import { ConversationLearner, ClientMemoryManager, FileStorage, uiRouter, CosmosLogStorage } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, ClientMemoryManager, FileStorage, uiRouter, CosmosLogStorage } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import getDolRouter from '../dol'
|
||||
|
@ -54,16 +54,15 @@ async function main() {
|
|||
// Use custom log storage
|
||||
const logStorage = cosmosServer ? await CosmosLogStorage.Get({ endpoint: cosmosServer, key: cosmosKey }) : undefined
|
||||
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage, logStorage)
|
||||
const conversationLearnerFactory = new ConversationLearnerFactory(clOptions, fileStorage, logStorage)
|
||||
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', conversationLearnerFactory.sdkRouter)
|
||||
}
|
||||
|
||||
const cl = new ConversationLearner(modelId)
|
||||
|
||||
const cl = conversationLearnerFactory.create(modelId)
|
||||
cl.EntityDetectionCallback = (async (text: string, memoryManager: ClientMemoryManager): Promise<void> => {
|
||||
|
||||
memoryManager.Get("name", ClientMemoryManager.AS_STRING)
|
||||
})
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import * as path from 'path'
|
||||
import * as express from 'express'
|
||||
import { BotFrameworkAdapter, ConversationState, AutoSaveStateMiddleware } from 'botbuilder'
|
||||
import { ConversationLearner, ClientMemoryManager, ReadOnlyClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, ClientMemoryManager, ReadOnlyClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import getDolRouter from '../dol'
|
||||
|
@ -45,10 +45,10 @@ let fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
|||
//==================================
|
||||
// Initialize Conversation Learner
|
||||
//==================================
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage)
|
||||
const clFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', clFactory.sdkRouter)
|
||||
}
|
||||
|
||||
//=================================
|
||||
|
@ -59,7 +59,7 @@ var isInStock = function (topping: string) {
|
|||
return (inStock.indexOf(topping.toLowerCase()) > -1)
|
||||
}
|
||||
|
||||
let clPizza = new ConversationLearner("2d9884f4-75a3-4f63-8b1e-d885ac02663e")
|
||||
let clPizza = clFactory.create("2d9884f4-75a3-4f63-8b1e-d885ac02663e");
|
||||
clPizza.EntityDetectionCallback = async (text: string, memoryManager: ClientMemoryManager): Promise<void> => {
|
||||
|
||||
// Clear OutOfStock List
|
||||
|
@ -112,7 +112,7 @@ var resolveApps = function (appName: string) {
|
|||
return apps.filter(n => n.includes(appName))
|
||||
}
|
||||
|
||||
let clVr = new ConversationLearner("997dc1e2-c0c0-4812-9429-446e31cfdf99")
|
||||
let clVr = clFactory.create("997dc1e2-c0c0-4812-9429-446e31cfdf99");
|
||||
clVr.EntityDetectionCallback = async (text: string, memoryManager: ClientMemoryManager): Promise<void> => {
|
||||
|
||||
// Clear disambigApps
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import * as path from 'path'
|
||||
import * as express from 'express'
|
||||
import { BotFrameworkAdapter } from 'botbuilder'
|
||||
import { ConversationLearner, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import getDolRouter from '../dol'
|
||||
|
@ -46,12 +46,12 @@ let fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
|||
//==================================
|
||||
// Initialize Conversation Learner
|
||||
//==================================
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage)
|
||||
const clFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', clFactory.sdkRouter)
|
||||
}
|
||||
let cl = new ConversationLearner(modelId)
|
||||
let cl = clFactory.create(modelId);
|
||||
|
||||
//=================================
|
||||
// Add Entity Logic
|
||||
|
@ -65,8 +65,8 @@ let cl = new ConversationLearner(modelId)
|
|||
// Define any API callbacks
|
||||
//=================================
|
||||
//
|
||||
// No API calls are used in this demo, so there are no calls to ConversationLearner.AddAPICallback
|
||||
// See other demos, or app.ts in the src directory, for an example of ConversationLearner.AddAPICallback
|
||||
// No API calls are used in this demo, so there are no calls to ConversationLearner.AddCallback
|
||||
// See other demos, or app.ts in the src directory, for an example of ConversationLearner.AddCallback
|
||||
//
|
||||
|
||||
//=================================
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import * as path from 'path'
|
||||
import * as express from 'express'
|
||||
import { BotFrameworkAdapter } from 'botbuilder'
|
||||
import { ConversationLearner, ClientMemoryManager, ReadOnlyClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, ClientMemoryManager, ReadOnlyClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import getDolRouter from '../dol'
|
||||
|
@ -46,12 +46,12 @@ let fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
|||
//==================================
|
||||
// Initialize Conversation Learner
|
||||
//==================================
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage)
|
||||
const clFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', clFactory.sdkRouter)
|
||||
}
|
||||
let cl = new ConversationLearner(modelId)
|
||||
let cl = clFactory.create(modelId);
|
||||
|
||||
//=========================================================
|
||||
// Bots Buisness Logic
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
import * as express from 'express'
|
||||
import { BotFrameworkAdapter } from 'botbuilder'
|
||||
import { ConversationLearner, RedisStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, RedisStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import getDolRouter from '../dol'
|
||||
|
@ -40,14 +40,14 @@ const adapter = new BotFrameworkAdapter({ appId: bfAppId, appPassword: bfAppPass
|
|||
// IN-MEMORY STORAGE
|
||||
// Stores bot state in memory.
|
||||
// If the bot is stopped and re-started, the state of in-progress sessions will be lost.
|
||||
//ConversationLearner.Init(clOptions);
|
||||
// const clFactory = new ConversationLearnerFactory(clOptions)
|
||||
|
||||
// FILE STORAGE
|
||||
// Stores bot state in a local file.
|
||||
// With this option, the bot can be stopped and re-started without losing the state of in-progress sessions.
|
||||
// Requires local disk access.
|
||||
//let fileStorage = new FileStorage( {path: path.join(__dirname, 'storage')})
|
||||
//ConversationLearner.Init(clOptions, fileStorage);
|
||||
// const fileStorage = new FileStorage( {path: path.join(__dirname, 'storage')})
|
||||
// const clFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
|
||||
// REDIS
|
||||
// Stores bot state in a redis cache.
|
||||
|
@ -61,23 +61,22 @@ if (typeof config.redisKey !== 'string' || config.redisKey.length === 0) {
|
|||
throw new Error(`When using Redis storage: redisKey value must be non-empty. You passed: ${config.redisKey}`)
|
||||
}
|
||||
|
||||
let redisStorage = new RedisStorage({ server: config.redisServer, key: config.redisKey })
|
||||
const redisStorage = new RedisStorage({ server: config.redisServer, key: config.redisKey })
|
||||
|
||||
//==================================
|
||||
// Initialize Conversation Learner
|
||||
//==================================
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, redisStorage)
|
||||
const clFactory = new ConversationLearnerFactory(clOptions, redisStorage)
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', clFactory.sdkRouter)
|
||||
}
|
||||
let cl = new ConversationLearner(modelId)
|
||||
|
||||
const cl = clFactory.create(modelId)
|
||||
|
||||
//=================================
|
||||
// Handle Incoming Messages
|
||||
//=================================
|
||||
|
||||
server.post('/api/messages', (req, res) => {
|
||||
adapter.processActivity(req, res, async context => {
|
||||
let result = await cl.recognize(context)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import * as path from 'path'
|
||||
import * as express from 'express'
|
||||
import { BotFrameworkAdapter } from 'botbuilder'
|
||||
import { ConversationLearner, ClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, ClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import getDolRouter from '../dol'
|
||||
|
@ -41,23 +41,23 @@ const adapter = new BotFrameworkAdapter({ appId: bfAppId, appPassword: bfAppPass
|
|||
// Initialize ConversationLearner using file storage.
|
||||
// Recommended only for development
|
||||
// See "storageDemo.ts" for other storage options
|
||||
let fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
||||
const fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
||||
|
||||
//==================================
|
||||
// Initialize Conversation Learner
|
||||
//==================================
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage)
|
||||
const clFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', clFactory.sdkRouter)
|
||||
}
|
||||
let cl = new ConversationLearner(modelId)
|
||||
const cl = clFactory.create(modelId)
|
||||
|
||||
//=========================================================
|
||||
// Bots Buisness Logic
|
||||
// Bots Business Logic
|
||||
//=========================================================
|
||||
var apps = ["skype", "outlook", "amazon video", "amazon music"]
|
||||
var resolveApps = function (appName: string) {
|
||||
const apps = ["skype", "outlook", "amazon video", "amazon music"]
|
||||
const resolveApps = (appName: string) => {
|
||||
return apps.filter(n => n.includes(appName))
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ cl.EntityDetectionCallback = async (text: string, memoryManager: ClientMemoryMan
|
|||
memoryManager.Delete("UnknownAppName")
|
||||
|
||||
// Get list of (possibly) ambiguous apps
|
||||
var appNames = memoryManager.Get("AppName", ClientMemoryManager.AS_STRING_LIST)
|
||||
const appNames = memoryManager.Get("AppName", ClientMemoryManager.AS_STRING_LIST)
|
||||
if (appNames.length > 0) {
|
||||
const resolvedAppNames = appNames
|
||||
.map(appName => resolveApps(appName))
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
import * as path from 'path'
|
||||
import * as express from 'express'
|
||||
import { ConversationLearner, ClientMemoryManager, FileStorage, ReadOnlyClientMemoryManager, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, ClientMemoryManager, FileStorage, ReadOnlyClientMemoryManager, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import * as request from 'request'
|
||||
|
@ -43,25 +43,25 @@ const adapter = new BB.BotFrameworkAdapter({ appId: bfAppId, appPassword: bfAppP
|
|||
// Initialize ConversationLearner using file storage.
|
||||
// Recommended only for development
|
||||
// See "storageDemo.ts" for other storage options
|
||||
let fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
||||
const fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
||||
|
||||
//==================================
|
||||
// Initialize Conversation Learner
|
||||
//==================================
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage)
|
||||
const clFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', clFactory.sdkRouter)
|
||||
}
|
||||
let cl = new ConversationLearner(modelId)
|
||||
const cl = clFactory.create(modelId)
|
||||
|
||||
//=========================================================
|
||||
// Bots Buisness Logic
|
||||
// Bots Business Logic
|
||||
//=========================================================
|
||||
var greetings = [
|
||||
const greetings = [
|
||||
"Hello!",
|
||||
"Greetings!",
|
||||
"Hi there!"
|
||||
"Hi there!",
|
||||
]
|
||||
|
||||
//=================================
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import * as path from 'path'
|
||||
import * as express from 'express'
|
||||
import { BotFrameworkAdapter } from 'botbuilder'
|
||||
import { ConversationLearner, ClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, ClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import getDolRouter from '../dol'
|
||||
|
@ -41,23 +41,23 @@ const adapter = new BotFrameworkAdapter({ appId: bfAppId, appPassword: bfAppPass
|
|||
// Initialize ConversationLearner using file storage.
|
||||
// Recommended only for development
|
||||
// See "storageDemo.ts" for other storage options
|
||||
let fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
||||
const fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
||||
|
||||
//==================================
|
||||
// Initialize Conversation Learner
|
||||
//==================================
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage)
|
||||
const clFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', clFactory.sdkRouter)
|
||||
}
|
||||
let cl = new ConversationLearner(modelId)
|
||||
const cl = clFactory.create(modelId)
|
||||
|
||||
//=========================================================
|
||||
// Bots Business Logic
|
||||
//=========================================================
|
||||
let cities = ['new york', 'boston', 'new orleans', 'chicago']
|
||||
let cityMap: { [index: string]: string } = {}
|
||||
const cities = ['new york', 'boston', 'new orleans', 'chicago']
|
||||
const cityMap: { [key: string]: string } = {}
|
||||
cityMap['big apple'] = 'new york'
|
||||
cityMap['windy city'] = 'chicago'
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import * as path from 'path'
|
|||
import * as express from 'express'
|
||||
import * as BB from 'botbuilder'
|
||||
import { BotFrameworkAdapter, AutoSaveStateMiddleware } from 'botbuilder'
|
||||
import { ConversationLearner, ClientMemoryManager, FileStorage, SessionEndState, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, ClientMemoryManager, FileStorage, SessionEndState, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import getDolRouter from '../dol'
|
||||
|
@ -47,12 +47,12 @@ let fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
|||
//==================================
|
||||
// Initialize Conversation Learner
|
||||
//==================================
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage)
|
||||
const clFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', clFactory.sdkRouter)
|
||||
}
|
||||
let cl = new ConversationLearner(modelId)
|
||||
let cl = clFactory.create(modelId)
|
||||
|
||||
//==================================
|
||||
// Add Start / End Session callbacks
|
||||
|
|
|
@ -6,7 +6,7 @@ import * as path from 'path'
|
|||
import * as express from 'express'
|
||||
import * as BB from 'botbuilder'
|
||||
import { BotFrameworkAdapter } from 'botbuilder'
|
||||
import { ConversationLearner, ClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, ClientMemoryManager, FileStorage, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import getDolRouter from '../dol'
|
||||
|
@ -47,12 +47,12 @@ let fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
|||
//==================================
|
||||
// Initialize Conversation Learner
|
||||
//==================================
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage)
|
||||
const clFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', clFactory.sdkRouter)
|
||||
}
|
||||
let cl = new ConversationLearner(modelId)
|
||||
const cl = clFactory.create(modelId)
|
||||
|
||||
//==================================
|
||||
// Add Start / End Session callbacks
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import * as path from 'path'
|
||||
import * as express from 'express'
|
||||
import * as botBuilder from 'botbuilder'
|
||||
import { ConversationLearner, ClientMemoryManager, FileStorage, ReadOnlyClientMemoryManager, uiRouter } from '@conversationlearner/sdk'
|
||||
import { ConversationLearnerFactory, ClientMemoryManager, FileStorage, ReadOnlyClientMemoryManager, uiRouter } from '@conversationlearner/sdk'
|
||||
import chalk from 'chalk'
|
||||
import config from '../config'
|
||||
import getDolRouter from '../dol'
|
||||
|
@ -14,15 +14,15 @@ const server = express()
|
|||
|
||||
const { bfAppId, bfAppPassword, modelId, ...clOptions } = config
|
||||
const adapter = new botBuilder.BotFrameworkAdapter({ appId: bfAppId, appPassword: bfAppPassword })
|
||||
let fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
||||
const sdkRouter = ConversationLearner.Init(clOptions, fileStorage)
|
||||
const fileStorage = new FileStorage(path.join(__dirname, 'storage'))
|
||||
const clFactory = new ConversationLearnerFactory(clOptions, fileStorage)
|
||||
const isDevelopment = process.env.NODE_ENV === 'development'
|
||||
if (isDevelopment) {
|
||||
console.log(chalk.yellowBright(`Adding /directline routes`))
|
||||
server.use(getDolRouter(config.botPort))
|
||||
|
||||
console.log(chalk.cyanBright(`Adding /sdk routes`))
|
||||
server.use('/sdk', sdkRouter)
|
||||
server.use('/sdk', clFactory.sdkRouter)
|
||||
|
||||
console.log(chalk.greenBright(`Adding /ui routes`))
|
||||
server.use(uiRouter as any)
|
||||
|
@ -35,14 +35,13 @@ server.listen(config.botPort, () => {
|
|||
console.log(`Server listening at: http://localhost:${config.botPort}`)
|
||||
})
|
||||
|
||||
const cl = new ConversationLearner(modelId)
|
||||
const cl = clFactory.create(modelId)
|
||||
|
||||
//=========================================================
|
||||
// Bots Business Logic
|
||||
//=========================================================
|
||||
|
||||
var inStock = ["cheese", "sausage", "mushrooms", "olives", "peppers"]
|
||||
var isInStock = function (topping: string) {
|
||||
const inStock = ["cheese", "sausage", "mushrooms", "olives", "peppers"]
|
||||
const isInStock = function (topping: string) {
|
||||
return (inStock.indexOf(topping.toLowerCase()) > -1)
|
||||
}
|
||||
|
||||
|
@ -55,7 +54,7 @@ var isInStock = function (topping: string) {
|
|||
* @param {ClientMemoryManager} memoryManager Allows for viewing and manipulating Bot's memory
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
cl.EntityDetectionCallback = async (text: string, memoryManager: ClientMemoryManager): Promise<void> => {
|
||||
cl.EntityDetectionCallback = async (text, memoryManager) => {
|
||||
let entityError = memoryManager.Get("entityError", ClientMemoryManager.AS_STRING)
|
||||
console.log(chalk.redBright(`entityError: ${entityError}`))
|
||||
if (entityError === "entityError") {
|
||||
|
|
|
@ -7,7 +7,6 @@ export const DEFAULT_MAX_SESSION_LENGTH = 20 * 60 * 1000 // 20 minutes
|
|||
|
||||
// Model Settings
|
||||
export interface CLModelOptions {
|
||||
|
||||
// How long before a session automatically times out
|
||||
sessionTimout: number
|
||||
sessionTimeout: number
|
||||
}
|
||||
|
|
|
@ -2,14 +2,8 @@
|
|||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
export interface CLOptions {
|
||||
import { ICLClientOptions } from './CLClient'
|
||||
|
||||
export interface CLOptions extends ICLClientOptions {
|
||||
botPort: any
|
||||
|
||||
LUIS_AUTHORING_KEY: string | undefined
|
||||
LUIS_SUBSCRIPTION_KEY: string | undefined
|
||||
APIM_SUBSCRIPTION_KEY: string | undefined
|
||||
|
||||
// URL for Conversation Learner service
|
||||
CONVERSATION_LEARNER_SERVICE_URI: string
|
||||
CONVERSATION_LEARNER_UI_PORT: number
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import * as CLM from '@conversationlearner/models'
|
|||
import * as Utils from './Utils'
|
||||
import * as ModelOptions from './CLModelOptions'
|
||||
import { CLState } from './Memory/CLState'
|
||||
import { CLStateFactory } from './Memory/CLStateFactory'
|
||||
import { CLDebug, DebugType } from './CLDebug'
|
||||
import { CLClient } from './CLClient'
|
||||
import { CLStrings } from './CLStrings'
|
||||
|
@ -18,6 +19,8 @@ import { ConversationLearner } from './ConversationLearner'
|
|||
import { InputQueue } from './Memory/InputQueue'
|
||||
import { UIMode } from './Memory/BotState'
|
||||
import { EntityState } from './Memory/EntityState'
|
||||
import { ILogStorage } from './Memory/ILogStorage'
|
||||
import { CLOptions } from './CLOptions'
|
||||
|
||||
interface RunnerLookup {
|
||||
[appId: string]: CLRunner
|
||||
|
@ -115,29 +118,36 @@ export class CLRunner {
|
|||
private static Runners: RunnerLookup = {}
|
||||
private static UIRunner: CLRunner
|
||||
|
||||
public clClient: CLClient
|
||||
public adapter: BB.BotAdapter | undefined
|
||||
private stateFactory: CLStateFactory
|
||||
private options: CLOptions
|
||||
private modelOptions: ModelOptions.CLModelOptions
|
||||
private logStorage: ILogStorage | undefined
|
||||
// Used to detect changes in API callbacks / Templates when bot reloaded and UI running
|
||||
private checksum: string | null = null
|
||||
|
||||
public clClient: CLClient
|
||||
public adapter: BB.BotAdapter | undefined
|
||||
/* Model Id passed in from configuration. Used when not running in Conversation Learner UI */
|
||||
public readonly configModelId: string | undefined
|
||||
|
||||
private modelOptions: ModelOptions.CLModelOptions
|
||||
|
||||
public readonly modelId: string | undefined
|
||||
/* Mapping between user defined API names and functions */
|
||||
public callbacks: CallbackMap = {}
|
||||
|
||||
public static Create(configModelId: string | undefined, client: CLClient, newOptions?: Partial<ModelOptions.CLModelOptions>): CLRunner {
|
||||
|
||||
public static Create(
|
||||
stateFactory: CLStateFactory,
|
||||
client: CLClient,
|
||||
option: CLOptions,
|
||||
modelId: string | undefined,
|
||||
logStorage: ILogStorage | undefined,
|
||||
newOptions?: Partial<ModelOptions.CLModelOptions>,
|
||||
): CLRunner {
|
||||
const modelOptions = this.validiatedModelOptions(newOptions)
|
||||
|
||||
// Ok to not provide modelId when just running in training UI.
|
||||
// If not, Use UI_RUNNER_APPID const as lookup value
|
||||
let newRunner = new CLRunner(configModelId, client, modelOptions)
|
||||
CLRunner.Runners[configModelId ?? Utils.UI_RUNNER_APPID] = newRunner
|
||||
const newRunner = new CLRunner(stateFactory, client, option, modelId, modelOptions, logStorage)
|
||||
CLRunner.Runners[modelId ?? Utils.UI_RUNNER_APPID] = newRunner
|
||||
|
||||
// Bot can define multiple CLs. Always run UI on first CL defined in the bot
|
||||
// Bot can define multiple CLs. Always run UI on first CL defined in the bot
|
||||
if (!CLRunner.UIRunner) {
|
||||
CLRunner.UIRunner = newRunner
|
||||
}
|
||||
|
@ -146,14 +156,12 @@ export class CLRunner {
|
|||
}
|
||||
|
||||
private static validiatedModelOptions(modelOptions?: Partial<ModelOptions.CLModelOptions>): ModelOptions.CLModelOptions {
|
||||
const sessionTimout = (!modelOptions
|
||||
|| !modelOptions.sessionTimout
|
||||
|| typeof modelOptions.sessionTimout !== 'number')
|
||||
const sessionTimeout = (typeof modelOptions?.sessionTimeout !== 'number')
|
||||
? ModelOptions.DEFAULT_MAX_SESSION_LENGTH
|
||||
: modelOptions.sessionTimout
|
||||
: modelOptions.sessionTimeout
|
||||
|
||||
return {
|
||||
sessionTimout
|
||||
sessionTimeout
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,10 +179,20 @@ export class CLRunner {
|
|||
return CLRunner.Runners[appId]
|
||||
}
|
||||
|
||||
private constructor(configModelId: string | undefined, client: CLClient, modelOptions: ModelOptions.CLModelOptions) {
|
||||
this.configModelId = configModelId
|
||||
this.modelOptions = modelOptions
|
||||
private constructor(
|
||||
stateFactory: CLStateFactory,
|
||||
client: CLClient,
|
||||
options: CLOptions,
|
||||
modelId: string | undefined,
|
||||
modelOptions: ModelOptions.CLModelOptions,
|
||||
logStorage: ILogStorage | undefined,
|
||||
) {
|
||||
this.stateFactory = stateFactory
|
||||
this.clClient = client
|
||||
this.options = options
|
||||
this.modelId = modelId
|
||||
this.modelOptions = modelOptions
|
||||
this.logStorage = logStorage
|
||||
}
|
||||
|
||||
public botChecksum(): string {
|
||||
|
@ -205,10 +223,10 @@ export class CLRunner {
|
|||
|
||||
public async InTrainingUI(turnContext: BB.TurnContext): Promise<boolean> {
|
||||
if (turnContext.activity.from?.name === Utils.CL_DEVELOPER) {
|
||||
const state = CLState.GetFromContext(turnContext, this.configModelId)
|
||||
const state = this.stateFactory.getFromContext(turnContext, this.modelId)
|
||||
const app = await state.BotState.GetApp()
|
||||
// If no app selected in UI or no app set in config, or they don't match return true
|
||||
if (!app || !this.configModelId || app.appId !== this.configModelId) {
|
||||
if (!app || !this.modelId || app.appId !== this.modelId) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -228,7 +246,7 @@ export class CLRunner {
|
|||
}
|
||||
|
||||
try {
|
||||
const state = CLState.GetFromContext(turnContext, this.configModelId)
|
||||
const state = this.stateFactory.getFromContext(turnContext, this.modelId)
|
||||
const app = await this.GetRunningApp(state, false)
|
||||
|
||||
if (app) {
|
||||
|
@ -271,7 +289,7 @@ export class CLRunner {
|
|||
return null
|
||||
}
|
||||
|
||||
const state = CLState.GetFromContext(turnContext, this.configModelId)
|
||||
const state = this.stateFactory.getFromContext(turnContext, this.modelId)
|
||||
|
||||
// If I'm in teach or edit mode, or testing process message right away
|
||||
let uiMode = await state.BotState.getUIMode()
|
||||
|
@ -342,14 +360,14 @@ export class CLRunner {
|
|||
const saveToLog = createParams.saveToLog
|
||||
|
||||
// Don't save logs on server if custom storage was provided
|
||||
if (ConversationLearner.logStorage) {
|
||||
if (this.logStorage) {
|
||||
createParams.saveToLog = false
|
||||
}
|
||||
|
||||
const session = await this.clClient.StartSession(appId, createParams as CLM.SessionCreateParams)
|
||||
|
||||
// If using customer storage add to log storage
|
||||
if (ConversationLearner.logStorage && saveToLog) {
|
||||
if (this.logStorage && saveToLog) {
|
||||
// For self-hosted log storage logDialogId is sessionId
|
||||
session.logDialogId = session.sessionId
|
||||
const logDialog: CLM.LogDialog = {
|
||||
|
@ -365,7 +383,7 @@ export class CLRunner {
|
|||
lastModifiedDateTime: new Date().toJSON(),
|
||||
metrics: ""
|
||||
}
|
||||
await ConversationLearner.logStorage.Add(appId, logDialog)
|
||||
await this.logStorage.Add(appId, logDialog)
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
@ -375,13 +393,13 @@ export class CLRunner {
|
|||
const extractResponse = await this.clClient.SessionExtract(appId, sessionId, userInput)
|
||||
const stepEndDatetime = new Date().toJSON()
|
||||
|
||||
// Add to dev's self-hosted log storage account (if it exists)
|
||||
if (ConversationLearner.logStorage) {
|
||||
// For self-holsted logDialogId = sessionId
|
||||
// If dev provided log storage, save round in storage
|
||||
if (this.logStorage) {
|
||||
// For provided storage logDialogId = sessionId
|
||||
const logDialogId = sessionId
|
||||
|
||||
// Append an extractor step to already existing log dialog
|
||||
const logDialog: CLM.LogDialog | undefined = await ConversationLearner.logStorage.Get(appId, logDialogId)
|
||||
const logDialog: CLM.LogDialog | undefined = await this.logStorage.Get(appId, logDialogId)
|
||||
if (!logDialog) {
|
||||
throw new Error(`Log Dialog does not exist App:${appId} Id:${logDialogId}`)
|
||||
}
|
||||
|
@ -391,8 +409,9 @@ export class CLRunner {
|
|||
}
|
||||
logDialog.rounds.push(newRound)
|
||||
logDialog.lastModifiedDateTime = new Date().toJSON()
|
||||
await ConversationLearner.logStorage.Replace(appId, logDialog)
|
||||
await this.logStorage.Replace(appId, logDialog)
|
||||
}
|
||||
|
||||
return extractResponse
|
||||
}
|
||||
|
||||
|
@ -400,9 +419,9 @@ export class CLRunner {
|
|||
const stepBeginDatetime = new Date().toJSON()
|
||||
const scoreResponse = await this.clClient.SessionScore(appId, sessionId, scoreInput)
|
||||
|
||||
// Add to dev's log storage account (if it exists)
|
||||
if (ConversationLearner.logStorage) {
|
||||
// For self-hosted storage logDialogId is sessionId
|
||||
// If log storage was provided save score there
|
||||
if (this.logStorage) {
|
||||
// For provided storage logDialogId is sessionId
|
||||
const logDialogId = sessionId
|
||||
const predictedAction = scoreResponse.scoredActions[0]?.actionId ?? ""
|
||||
|
||||
|
@ -419,6 +438,7 @@ export class CLRunner {
|
|||
actionId: sa.actionId
|
||||
}
|
||||
})
|
||||
|
||||
// Need to use recursive partial as scored and unscored have only partial data
|
||||
const logScorerStep: Utils.RecursivePartial<CLM.LogScorerStep> = {
|
||||
input: scoreInput,
|
||||
|
@ -427,10 +447,10 @@ export class CLRunner {
|
|||
predictionDetails: { scoredActions, unscoredActions },
|
||||
stepBeginDatetime,
|
||||
stepEndDatetime: new Date().toJSON(),
|
||||
metrics: scoreResponse.metrics
|
||||
metrics: scoreResponse.metrics,
|
||||
}
|
||||
|
||||
const logDialog: CLM.LogDialog | undefined = await ConversationLearner.logStorage.Get(appId, logDialogId)
|
||||
const logDialog: CLM.LogDialog | undefined = await this.logStorage.Get(appId, logDialogId)
|
||||
if (!logDialog) {
|
||||
throw new Error(`Log Dialog does not exist App:${appId} Log:${logDialogId}`)
|
||||
}
|
||||
|
@ -440,7 +460,7 @@ export class CLRunner {
|
|||
}
|
||||
lastRound.scorerSteps.push(logScorerStep as any)
|
||||
logDialog.lastModifiedDateTime = new Date().toJSON()
|
||||
await ConversationLearner.logStorage.Replace(appId, logDialog)
|
||||
await this.logStorage.Replace(appId, logDialog)
|
||||
}
|
||||
return scoreResponse
|
||||
}
|
||||
|
@ -450,15 +470,15 @@ export class CLRunner {
|
|||
let app = await state.BotState.GetApp()
|
||||
|
||||
// If this instance is configured to use a specific model, check conditions to use that model.
|
||||
if (this.configModelId
|
||||
if (this.modelId
|
||||
// If current app is not set
|
||||
&& (!app
|
||||
// If I'm not in the editing UI and config model id differs than the current app
|
||||
|| (!inEditingUI && this.configModelId != app.appId))
|
||||
|| (!inEditingUI && this.modelId != app.appId))
|
||||
) {
|
||||
// Get app specified by options
|
||||
CLDebug.Log(`Switching to app specified in config: ${this.configModelId}`)
|
||||
app = await this.clClient.GetApp(this.configModelId)
|
||||
CLDebug.Log(`Switching to app specified in config: ${this.modelId}`)
|
||||
app = await this.clClient.GetApp(this.modelId)
|
||||
await state.SetAppAsync(app)
|
||||
}
|
||||
|
||||
|
@ -495,19 +515,19 @@ export class CLRunner {
|
|||
const inEditingUI = conversationReference.user?.name === Utils.CL_DEVELOPER
|
||||
|
||||
// Validate setup
|
||||
if (!inEditingUI && !this.configModelId) {
|
||||
if (!inEditingUI && !this.modelId) {
|
||||
const msg = 'Must specify modelId in ConversationLearner constructor when not running bot in Editing UI\n\n'
|
||||
CLDebug.Error(msg)
|
||||
return null
|
||||
}
|
||||
|
||||
if (!ConversationLearner.options?.LUIS_AUTHORING_KEY) {
|
||||
if (!this.options?.LUIS_AUTHORING_KEY) {
|
||||
const msg = 'Options must specify luisAuthoringKey. Set the LUIS_AUTHORING_KEY.\n\n'
|
||||
CLDebug.Error(msg)
|
||||
return null
|
||||
}
|
||||
|
||||
const state = CLState.GetFromContext(turnContext, this.configModelId)
|
||||
const state = this.stateFactory.getFromContext(turnContext, this.modelId)
|
||||
let app = await this.GetRunningApp(state, inEditingUI)
|
||||
const uiMode = await state.BotState.getUIMode()
|
||||
|
||||
|
@ -529,7 +549,7 @@ export class CLRunner {
|
|||
const currentTicks = new Date().getTime()
|
||||
let lastActive = await state.BotState.GetLastActive()
|
||||
let passedTicks = currentTicks - lastActive
|
||||
if (passedTicks > this.modelOptions.sessionTimout!) {
|
||||
if (passedTicks > this.modelOptions.sessionTimeout) {
|
||||
|
||||
// Parameters for new session
|
||||
const sessionCreateParams: CLM.SessionCreateParams = {
|
||||
|
@ -551,13 +571,13 @@ export class CLRunner {
|
|||
// If I'm not in the UI, reload the App to get any changes (live package version may have been updated)
|
||||
if (!inEditingUI) {
|
||||
|
||||
if (!this.configModelId) {
|
||||
if (!this.modelId) {
|
||||
let error = "ERROR: ModelId not specified. When running in a channel (i.e. Skype) or the Bot Framework Emulator, CONVERSATION_LEARNER_MODEL_ID must be specified in your Bot's .env file or Application Settings on the server"
|
||||
await this.SendMessage(state, error, activity)
|
||||
return null
|
||||
}
|
||||
|
||||
app = await this.clClient.GetApp(this.configModelId)
|
||||
app = await this.clClient.GetApp(this.modelId)
|
||||
await state.SetAppAsync(app)
|
||||
|
||||
if (!app) {
|
||||
|
@ -644,7 +664,7 @@ export class CLRunner {
|
|||
} catch (error) {
|
||||
// Try to end the session, so use can potentially recover
|
||||
try {
|
||||
const state = CLState.GetFromContext(turnContext, this.configModelId)
|
||||
const state = this.stateFactory.getFromContext(turnContext, this.modelId)
|
||||
await this.EndSessionAsync(state, CLM.SessionEndState.OPEN)
|
||||
} catch {
|
||||
CLDebug.Log(`Failed to End Session`)
|
||||
|
@ -1194,19 +1214,24 @@ export class CLRunner {
|
|||
}
|
||||
|
||||
private async forwardInputToModel(modelId: string, state: CLState, changeActiveModel: boolean = false) {
|
||||
if (modelId === this.configModelId) {
|
||||
if (modelId === this.modelId) {
|
||||
throw new Error(`Cannot forward input to model with same ID as active model. This shouldn't be possible open an issue.`)
|
||||
}
|
||||
|
||||
// Reuse model instance from cache or create it
|
||||
let model = ConversationLearner.models.find(m => m.clRunner.configModelId === modelId)
|
||||
let model = ConversationLearner.models.find(m => m.clRunner.modelId === modelId)
|
||||
if (!model) {
|
||||
model = new ConversationLearner(modelId)
|
||||
model = new ConversationLearner(
|
||||
this.stateFactory,
|
||||
this.clClient,
|
||||
this.options,
|
||||
modelId
|
||||
)
|
||||
}
|
||||
|
||||
// Save the model id for the conversation so all future input is directed to it.
|
||||
if (changeActiveModel) {
|
||||
state.ConversationModelState.set(model.clRunner.configModelId)
|
||||
state.ConversationModelState.set(model.clRunner.modelId)
|
||||
}
|
||||
|
||||
const turnContext = state.turnContext
|
||||
|
|
|
@ -3,51 +3,32 @@
|
|||
* Licensed under the MIT License.
|
||||
*/
|
||||
import * as BB from 'botbuilder'
|
||||
import * as express from 'express'
|
||||
import getRouter from './http/router'
|
||||
import { CLRunner, EntityDetectionCallback, OnSessionStartCallback, OnSessionEndCallback, ICallbackInput } from './CLRunner'
|
||||
import { CLOptions } from './CLOptions'
|
||||
import { CLState } from './Memory/CLState'
|
||||
import { CLDebug } from './CLDebug'
|
||||
import { CLClient } from './CLClient'
|
||||
import { CLRecognizerResult } from './CLRecognizeResult'
|
||||
import { CLModelOptions } from '.'
|
||||
import CLStateFactory from './Memory/CLStateFactory'
|
||||
import { CLOptions } from './CLOptions'
|
||||
import { CLModelOptions } from './CLModelOptions'
|
||||
import { ILogStorage } from './Memory/ILogStorage'
|
||||
|
||||
/**
|
||||
* Main CL class used by Bot
|
||||
*/
|
||||
export class ConversationLearner {
|
||||
public static options: CLOptions | null = null
|
||||
public static clClient: CLClient
|
||||
public static logStorage: ILogStorage
|
||||
public static models: ConversationLearner[] = []
|
||||
public clRunner: CLRunner
|
||||
private stateFactory: CLStateFactory
|
||||
|
||||
public static Init(options: CLOptions, stateStorage?: BB.Storage, logStorage?: ILogStorage): express.Router {
|
||||
ConversationLearner.options = options
|
||||
|
||||
try {
|
||||
this.clClient = new CLClient(options)
|
||||
CLState.Init(stateStorage)
|
||||
|
||||
// Is developer providing their own log storage
|
||||
if (logStorage) {
|
||||
this.logStorage = logStorage
|
||||
}
|
||||
} catch (error) {
|
||||
CLDebug.Error(error, 'Conversation Learner Initialization')
|
||||
}
|
||||
|
||||
return getRouter(this.clClient, options)
|
||||
}
|
||||
|
||||
constructor(modelId: string | undefined, modelOptions?: Partial<CLModelOptions>) {
|
||||
if (!ConversationLearner.options) {
|
||||
throw new Error("Init() must be called on ConversationLearner before instances are created")
|
||||
}
|
||||
|
||||
this.clRunner = CLRunner.Create(modelId, ConversationLearner.clClient, modelOptions)
|
||||
constructor(
|
||||
stateFactory: CLStateFactory,
|
||||
client: CLClient,
|
||||
options: CLOptions,
|
||||
modelId: string | undefined,
|
||||
modelOptions?: CLModelOptions,
|
||||
logStorage?: ILogStorage,
|
||||
) {
|
||||
this.stateFactory = stateFactory
|
||||
this.clRunner = CLRunner.Create(stateFactory, client, options, modelId, logStorage, modelOptions)
|
||||
ConversationLearner.models.push(this)
|
||||
}
|
||||
|
||||
|
@ -57,9 +38,9 @@ export class ConversationLearner {
|
|||
// If there is more than one model in use for running bot we need to check which model is active for conversation
|
||||
// This check avoids doing work for normal singe model model bots
|
||||
if (ConversationLearner.models.length > 1) {
|
||||
const context = CLState.GetFromContext(turnContext)
|
||||
const context = this.stateFactory.getFromContext(turnContext)
|
||||
const activeModelIdForConversation = await context.ConversationModelState.get<string>()
|
||||
const model = ConversationLearner.models.find(m => m.clRunner.configModelId === activeModelIdForConversation)
|
||||
const model = ConversationLearner.models.find(m => m.clRunner.modelId === activeModelIdForConversation)
|
||||
if (model) {
|
||||
activeModel = model
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
import * as BB from 'botbuilder'
|
||||
import * as express from 'express'
|
||||
import { ConversationLearner } from './ConversationLearner'
|
||||
import { CLOptions } from './CLOptions'
|
||||
import { CLClient } from './CLClient'
|
||||
import getRouter from './http/router'
|
||||
import CLStateFactory from './Memory/CLStateFactory'
|
||||
import { ILogStorage } from './Memory/ILogStorage'
|
||||
import { CLModelOptions } from './CLModelOptions'
|
||||
|
||||
/**
|
||||
* Conversation Learner Factory. Produces instances that all use the same storage, client, and options.
|
||||
* Alternative which ConversationLearner.Init() which set the statics but this created temporal coupling (need to call Init before constructor)
|
||||
*/
|
||||
export default class ConversationLearnerFactory {
|
||||
private storageFactory: CLStateFactory
|
||||
private client: CLClient
|
||||
private logStorage: ILogStorage | undefined
|
||||
private options: CLOptions
|
||||
|
||||
sdkRouter: express.Router
|
||||
|
||||
constructor(
|
||||
options: CLOptions,
|
||||
bbStorage: BB.Storage = new BB.MemoryStorage(),
|
||||
logStorage?: ILogStorage,
|
||||
) {
|
||||
this.storageFactory = new CLStateFactory(bbStorage)
|
||||
this.logStorage = logStorage
|
||||
this.options = options
|
||||
this.client = new CLClient(options)
|
||||
|
||||
this.sdkRouter = getRouter(this.client, this.storageFactory, options, logStorage)
|
||||
}
|
||||
|
||||
create(modelId?: string, modelOptions?: CLModelOptions) {
|
||||
return new ConversationLearner(
|
||||
this.storageFactory,
|
||||
this.client,
|
||||
this.options,
|
||||
modelId,
|
||||
modelOptions,
|
||||
this.logStorage
|
||||
)
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
* Licensed under the MIT License.
|
||||
*/
|
||||
import * as BB from 'botbuilder'
|
||||
import { ConversationLearner } from '../ConversationLearner'
|
||||
import { CLStorage } from './CLStorage'
|
||||
import { AppBase } from '@conversationlearner/models'
|
||||
import { SessionStartFlags } from '../CLRunner'
|
||||
|
@ -72,7 +71,11 @@ export class BotState {
|
|||
private readonly getKey: GetKey
|
||||
private readonly conversationReferenceToConversationIdMapper: ConvIdMapper
|
||||
|
||||
constructor(storage: CLStorage, getKey: GetKey, conversationReferenceToConvIdMapper: ConvIdMapper = BotState.DefaultConversationIdMapper) {
|
||||
constructor(
|
||||
storage: CLStorage,
|
||||
getKey: GetKey,
|
||||
conversationReferenceToConvIdMapper: ConvIdMapper = BotState.DefaultConversationIdMapper
|
||||
) {
|
||||
this.storage = storage
|
||||
this.getKey = getKey
|
||||
this.conversationReferenceToConversationIdMapper = conversationReferenceToConvIdMapper
|
||||
|
@ -276,8 +279,9 @@ export class BotState {
|
|||
conversation: { id: conversationId },
|
||||
channelId: 'emulator',
|
||||
// TODO: Refactor away from static coupling. BotState needs to have access to options object through constructor
|
||||
// Doesn't seemed to be used, so hard-code to default port for now.
|
||||
// tslint:disable-next-line:no-http-string
|
||||
serviceUrl: `http://127.0.0.1:${ConversationLearner.options!.botPort}`
|
||||
serviceUrl: `http://127.0.0.1:${3978}`
|
||||
} as Partial<BB.ConversationReference>
|
||||
this.SetConversationReference(conversationReference)
|
||||
}
|
||||
|
|
|
@ -4,12 +4,9 @@
|
|||
*/
|
||||
import * as BB from 'botbuilder'
|
||||
import * as CLM from '@conversationlearner/models'
|
||||
import * as Utils from '../Utils'
|
||||
import { CLDebug } from '../CLDebug'
|
||||
import { EntityState } from './EntityState'
|
||||
import { BotState } from './BotState'
|
||||
import { InProcessMessageState as MessageState } from './InProcessMessageState'
|
||||
import { CLStorage } from './CLStorage'
|
||||
import { BrowserSlotState } from './BrowserSlot'
|
||||
|
||||
/**
|
||||
|
@ -25,7 +22,6 @@ import { BrowserSlotState } from './BrowserSlot'
|
|||
* - Example: SetApp (which affects BotState, EntityState, and MessageState)
|
||||
*/
|
||||
export class CLState {
|
||||
private static bbStorage: BB.Storage
|
||||
public readonly turnContext?: BB.TurnContext
|
||||
|
||||
BotState: BotState
|
||||
|
@ -34,14 +30,14 @@ export class CLState {
|
|||
ConversationModelState: MessageState
|
||||
BrowserSlotState: BrowserSlotState
|
||||
|
||||
private constructor(
|
||||
constructor(
|
||||
botState: BotState,
|
||||
entityState: EntityState,
|
||||
messageState: MessageState,
|
||||
conversationModelState: MessageState,
|
||||
browserSlotState: BrowserSlotState,
|
||||
turnContext?: BB.TurnContext,
|
||||
) {
|
||||
turnContext?: BB.TurnContext) {
|
||||
|
||||
this.BotState = botState
|
||||
this.EntityState = entityState
|
||||
this.MessageState = messageState
|
||||
|
@ -51,69 +47,11 @@ export class CLState {
|
|||
this.turnContext = turnContext
|
||||
}
|
||||
|
||||
public static Init(storage?: BB.Storage): void {
|
||||
// If memory storage not defined use disk storage
|
||||
if (!storage) {
|
||||
CLDebug.Log('Storage not defined. Defaulting to in-memory storage.')
|
||||
storage = new BB.MemoryStorage()
|
||||
}
|
||||
|
||||
CLState.bbStorage = storage
|
||||
}
|
||||
|
||||
public static Get(key: string, modelId: string = '', turnContext?: BB.TurnContext): CLState {
|
||||
const storage = new CLStorage(CLState.bbStorage)
|
||||
|
||||
// Used for state shared through lifetime of conversation (conversationId)
|
||||
const keyPrefix = Utils.getSha256Hash(key)
|
||||
// Used for state shared between models within a conversation (Dispatcher has multiple models per conversation)
|
||||
const modelUniqueKeyPrefix = Utils.getSha256Hash(`${modelId}${key}`)
|
||||
|
||||
const botState = new BotState(storage, (datakey) => `${modelUniqueKeyPrefix}_BOTSTATE_${datakey}`)
|
||||
const entityState = new EntityState(storage, () => `${modelUniqueKeyPrefix}_ENTITYSTATE`)
|
||||
const messageState = new MessageState(storage, () => `${keyPrefix}_MESSAGE_MUTEX`)
|
||||
const conversationModelState = new MessageState(storage, () => `${keyPrefix}_CONVERSATION_MODEL`)
|
||||
const browserSlotState = new BrowserSlotState(storage, () => `BROWSER_SLOTS`)
|
||||
|
||||
return new CLState(
|
||||
botState,
|
||||
entityState,
|
||||
messageState,
|
||||
conversationModelState,
|
||||
browserSlotState,
|
||||
turnContext,
|
||||
)
|
||||
}
|
||||
|
||||
public static GetFromContext(turnContext: BB.TurnContext, modelId: string = ''): CLState {
|
||||
const conversationReference = BB.TurnContext.getConversationReference(turnContext.activity)
|
||||
const user = conversationReference.user
|
||||
|
||||
let keyPrefix: string
|
||||
const isRunningInUI = Utils.isRunningInClUI(turnContext)
|
||||
if (isRunningInUI) {
|
||||
if (!user) {
|
||||
throw new Error(`Attempted to initialize state, but cannot get state key because current request did not have 'from'/user specified`)
|
||||
}
|
||||
if (!user.id) {
|
||||
throw new Error(`Attempted to initialize state, but user.id was not provided which is required for use as state key.`)
|
||||
}
|
||||
// User ID is the browser slot assigned to the UI
|
||||
keyPrefix = user.id
|
||||
} else {
|
||||
// CLState uses conversation Id as the prefix key for all the objects kept in CLStorage when bot is not running against CL UI
|
||||
if (!conversationReference.conversation || !conversationReference.conversation.id) {
|
||||
throw new Error(`Attempted to initialize state, but conversationReference.conversation.id was not provided which is required for use as state key.`)
|
||||
}
|
||||
keyPrefix = conversationReference.conversation.id
|
||||
}
|
||||
|
||||
return CLState.Get(keyPrefix, !isRunningInUI ? modelId : undefined, turnContext)
|
||||
}
|
||||
|
||||
public async SetAppAsync(app: CLM.AppBase | null): Promise<void> {
|
||||
const curApp = await this.BotState.GetApp()
|
||||
await this.BotState.SetAppAsync(app)
|
||||
await this.MessageState.remove()
|
||||
await this.ConversationModelState.remove()
|
||||
|
||||
if (!app || !curApp || curApp.appId !== app.appId) {
|
||||
await this.EntityState.ClearAsync()
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
import * as BB from 'botbuilder'
|
||||
import { CLStorage } from './CLStorage'
|
||||
import { CLState } from './CLState'
|
||||
import * as Utils from '../Utils'
|
||||
import { BotState } from './BotState'
|
||||
import { EntityState } from './EntityState'
|
||||
import { InProcessMessageState as MessageState } from './InProcessMessageState'
|
||||
import { BrowserSlotState } from './BrowserSlot'
|
||||
|
||||
/**
|
||||
* Conversation Learner State Factory.
|
||||
*
|
||||
* Produces instances that all use the same BotBuilder storage provider.
|
||||
*/
|
||||
export class CLStateFactory {
|
||||
private bbStorage: BB.Storage
|
||||
|
||||
constructor(bbStorage: BB.Storage = new BB.MemoryStorage()) {
|
||||
this.bbStorage = bbStorage
|
||||
}
|
||||
|
||||
get(key: string, modelId: string = '', turnContext?: BB.TurnContext): CLState {
|
||||
const storage = new CLStorage(this.bbStorage)
|
||||
|
||||
// Used for state shared through lifetime of conversation (conversationId)
|
||||
const keyPrefix = Utils.getSha256Hash(key)
|
||||
// Used for state shared between models within a conversation (Dispatcher has multiple models per conversation)
|
||||
const modelUniqueKeyPrefix = Utils.getSha256Hash(`${modelId}${key}`)
|
||||
|
||||
const botState = new BotState(storage, (datakey) => `${modelUniqueKeyPrefix}_BOTSTATE_${datakey}`)
|
||||
const entityState = new EntityState(storage, () => `${modelUniqueKeyPrefix}_ENTITYSTATE`)
|
||||
const messageState = new MessageState(storage, () => `${keyPrefix}_MESSAGE_MUTEX`)
|
||||
const conversationModelState = new MessageState(storage, () => `${keyPrefix}_CONVERSATION_MODEL`)
|
||||
const browserSlotState = new BrowserSlotState(storage, () => `BROWSER_SLOTS`)
|
||||
|
||||
return new CLState(
|
||||
botState,
|
||||
entityState,
|
||||
messageState,
|
||||
conversationModelState,
|
||||
browserSlotState,
|
||||
turnContext,
|
||||
)
|
||||
}
|
||||
|
||||
getFromContext(turnContext: BB.TurnContext, modelId: string = ''): CLState {
|
||||
const conversationReference = BB.TurnContext.getConversationReference(turnContext.activity)
|
||||
const user = conversationReference.user
|
||||
|
||||
let keyPrefix: string
|
||||
if (Utils.isRunningInClUI(turnContext)) {
|
||||
if (!user) {
|
||||
throw new Error(`Attempted to initialize state, but cannot get state key because current request did not have 'from'/user specified`)
|
||||
}
|
||||
if (!user.id) {
|
||||
throw new Error(`Attempted to initialize state, but user.id was not provided which is required for use as state key.`)
|
||||
}
|
||||
// User ID is the browser slot assigned to the UI
|
||||
keyPrefix = user.id
|
||||
} else {
|
||||
// CLState uses conversation Id as the prefix key for all the objects kept in CLStorage when bot is not running against CL UI
|
||||
if (!conversationReference.conversation || !conversationReference.conversation.id) {
|
||||
throw new Error(`Attempted to initialize state, but conversationReference.conversation.id was not provided which is required for use as state key.`)
|
||||
}
|
||||
keyPrefix = conversationReference.conversation.id
|
||||
}
|
||||
|
||||
return this.get(keyPrefix, modelId, turnContext)
|
||||
}
|
||||
}
|
||||
|
||||
export default CLStateFactory
|
|
@ -5,7 +5,7 @@
|
|||
import { CLStorage } from './CLStorage'
|
||||
import { CLDebug } from '../CLDebug'
|
||||
import { Memory, FilledEntity, MemoryValue, FilledEntityMap } from '@conversationlearner/models'
|
||||
import { ClientMemoryManager } from '..'
|
||||
import { ClientMemoryManager } from './ClientMemoryManager'
|
||||
|
||||
const NEGATIVE_PREFIX = '~'
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ export interface LogQueryResult {
|
|||
}
|
||||
|
||||
/**
|
||||
* Interface for generating custom LogStorage implimentations
|
||||
* Interface for generating custom LogStorage implementations
|
||||
*/
|
||||
export declare class ILogStorage {
|
||||
|
||||
|
@ -29,7 +29,7 @@ export declare class ILogStorage {
|
|||
*/
|
||||
GetMany(appId: string, packageIds: string[], continuationToken?: string, pageSize?: number): Promise<LogQueryResult>
|
||||
|
||||
/** Replace and exisiting log dialog */
|
||||
/** Replace and existing log dialog */
|
||||
Replace(appId: string, logDialog: CLM.LogDialog): Promise<void>
|
||||
|
||||
/** Delete a log dialog in storage */
|
||||
|
|
|
@ -7,6 +7,7 @@ import * as BB from 'botbuilder'
|
|||
import * as CLM from '@conversationlearner/models'
|
||||
import { CLState } from './CLState'
|
||||
import { InputQueue } from '../Memory/InputQueue'
|
||||
import CLStateFactory from './CLStateFactory'
|
||||
|
||||
// InputQueue requires tiny delay between inputs or InputQueue mutex won't fishing setting
|
||||
const mutexDelay = 50
|
||||
|
@ -35,8 +36,8 @@ function delay(ms: number) {
|
|||
// Simulate AddInput function in CLRunner
|
||||
async function addInput(state: CLState, message: string, conversationId: string) {
|
||||
const activity = makeActivity(message, conversationId)
|
||||
let addInputPromise = util.promisify(InputQueue.AddInput)
|
||||
let isReady = await addInputPromise(state.MessageState, activity)
|
||||
const addInputPromise = util.promisify(InputQueue.AddInput)
|
||||
const isReady = await addInputPromise(state.MessageState, activity)
|
||||
|
||||
if (isReady) {
|
||||
// Handle input with a delay to simulate CLRunner ProcessInput
|
||||
|
@ -52,16 +53,16 @@ async function addInput(state: CLState, message: string, conversationId: string)
|
|||
}
|
||||
|
||||
describe('InputQueue', () => {
|
||||
const stateFactory = new CLStateFactory()
|
||||
|
||||
beforeEach(() => {
|
||||
CLState.Init()
|
||||
responses = {}
|
||||
})
|
||||
|
||||
it('should handle all messages in a single queue', async () => {
|
||||
|
||||
const conversationId = CLM.ModelUtils.generateGUID()
|
||||
const clState = CLState.Get(conversationId)
|
||||
const clState = stateFactory.get(conversationId)
|
||||
const inputs = ["A", "B", "C", "D", "E"]
|
||||
|
||||
// Need longer jest timeout for this test
|
||||
|
@ -87,8 +88,8 @@ describe('InputQueue', () => {
|
|||
|
||||
const conversation1Id = CLM.ModelUtils.generateGUID()
|
||||
const conversation2Id = CLM.ModelUtils.generateGUID()
|
||||
const clState1 = CLState.Get(conversation1Id)
|
||||
const clState2 = CLState.Get(conversation2Id)
|
||||
const clState1 = stateFactory.get(conversation1Id)
|
||||
const clState2 = stateFactory.get(conversation2Id)
|
||||
const inputs1 = ["A", "B", "C", "D", "E"]
|
||||
const inputs2 = ["1", "2", "3", "4", "5"]
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import * as supertest from 'supertest'
|
|||
import * as express from 'express'
|
||||
import router from './router'
|
||||
import { ICLClientOptions, CLClient } from '../CLClient'
|
||||
import CLStateFactory from '../Memory/CLStateFactory'
|
||||
|
||||
describe('Test SDK router', () => {
|
||||
const options: ICLClientOptions = {
|
||||
|
@ -13,8 +14,10 @@ describe('Test SDK router', () => {
|
|||
APIM_SUBSCRIPTION_KEY: undefined,
|
||||
LUIS_AUTHORING_KEY: undefined
|
||||
}
|
||||
const clClient = new CLClient(options)
|
||||
const sdkRouter = router(clClient, options)
|
||||
|
||||
const client = new CLClient(options)
|
||||
const stateFactory = new CLStateFactory()
|
||||
const sdkRouter = router(client, stateFactory, options, undefined)
|
||||
const app = express()
|
||||
app.use(sdkRouter)
|
||||
|
||||
|
|
|
@ -19,12 +19,12 @@ import getAppDefinitionChange from '../upgrade'
|
|||
import { CLDebug } from '../CLDebug'
|
||||
import { CLClient, ICLClientOptions } from '../CLClient'
|
||||
import { CLRunner, SessionStartFlags } from '../CLRunner'
|
||||
import { ConversationLearner } from '../ConversationLearner'
|
||||
import { CLState } from '../Memory/CLState'
|
||||
import { CLStateFactory } from '../Memory/CLStateFactory'
|
||||
import { CLRecognizerResult } from '../CLRecognizeResult'
|
||||
import { TemplateProvider } from '../TemplateProvider'
|
||||
import { CLStrings } from '../CLStrings'
|
||||
import { UIMode } from '../Memory/BotState'
|
||||
import { ILogStorage } from '../Memory/ILogStorage'
|
||||
|
||||
// Extract error text from HTML error
|
||||
export const HTML2Error = (htmlText: string): string => {
|
||||
|
@ -122,7 +122,20 @@ const getBanner = (source: string): Promise<CLM.Banner | null> => {
|
|||
})
|
||||
}
|
||||
|
||||
export const getRouter = (client: CLClient, options: ICLClientOptions): express.Router => {
|
||||
/**
|
||||
* Create Express.Router using given options (LUIS_AUTHORING_KEY, etc)
|
||||
*
|
||||
* Requests that need to manipulate the Bot such as operations from UI on train dialogs must be intercepted, deconstructed, then forwarded using the ClClient
|
||||
* Requests that do NOT need to manipulate the Bot can flow through uninterrupted and go through HttpProxy middleware.
|
||||
*
|
||||
* @param options Options for Conversation Learner client
|
||||
*/
|
||||
export const getRouter = (
|
||||
client: CLClient,
|
||||
stateFactory: CLStateFactory,
|
||||
options: ICLClientOptions,
|
||||
logStorage: ILogStorage | undefined,
|
||||
): express.Router => {
|
||||
const router = express.Router({ caseSensitive: false })
|
||||
router.use(cors())
|
||||
router.use(bodyParser.json({
|
||||
|
@ -144,7 +157,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const key = getMemoryKey(req)
|
||||
const app: CLM.AppBase = req.body
|
||||
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
await state.SetAppAsync(app)
|
||||
res.sendStatus(200)
|
||||
} catch (error) {
|
||||
|
@ -157,7 +170,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
try {
|
||||
const key = getMemoryKey(req)
|
||||
const { conversationId, userName } = getQuery(req)
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
await state.BotState.CreateConversationReference(userName, key, conversationId)
|
||||
res.sendStatus(200)
|
||||
} catch (error) {
|
||||
|
@ -177,10 +190,10 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
|
||||
// TODO: This is code smell, this is using internal knowledge that BrowserState full key is static and will be the same regardless of key
|
||||
// It makes BrowserState accessed in consistent way other states. Would be more natural as static object but need to share underlying storage.
|
||||
const state = CLState.Get('')
|
||||
const state = stateFactory.get('')
|
||||
// Generate id
|
||||
const browserSlotId = await state.BrowserSlotState.get(browserId)
|
||||
const key = ConversationLearner.options!.LUIS_AUTHORING_KEY!
|
||||
const key = options.LUIS_AUTHORING_KEY
|
||||
const hashedKey = key ? crypto.createHash('sha256').update(key).digest('hex') : ""
|
||||
const id = `${browserSlotId}-${hashedKey}`
|
||||
|
||||
|
@ -235,7 +248,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
res.send(app)
|
||||
|
||||
// Initialize memory
|
||||
CLState.Get(key).SetAppAsync(app)
|
||||
stateFactory.get(key).SetAppAsync(app)
|
||||
} catch (error) {
|
||||
HandleError(res, error)
|
||||
}
|
||||
|
@ -250,7 +263,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
await client.ArchiveApp(appId)
|
||||
|
||||
// Did I delete my loaded app, if so clear my state
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const app = await state.BotState.GetApp()
|
||||
if (app?.appId === appId) {
|
||||
await state.SetAppAsync(null)
|
||||
|
@ -272,7 +285,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const apps = await client.GetApps(query)
|
||||
|
||||
// Get lookup table for which apps packages are being edited
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const activeApps = await state.BotState.GetEditingPackages()
|
||||
|
||||
const uiAppList = { appList: apps, activeApps: activeApps } as CLM.UIAppList
|
||||
|
@ -375,7 +388,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
}
|
||||
}
|
||||
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const updatedPackageVersions = await state.BotState.SetEditingPackage(appId, packageId)
|
||||
res.send(updatedPackageVersions)
|
||||
|
||||
|
@ -493,7 +506,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const { appId, logDialogId, turnIndex } = req.params
|
||||
const userInput: CLM.UserInput = req.body
|
||||
const extractResponse = await client.LogDialogExtract(appId, logDialogId, turnIndex, userInput)
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
|
||||
const memories = await state.EntityState.DumpMemory()
|
||||
const uiExtractResponse: CLM.UIExtractResponse = { extractResponse, memories }
|
||||
|
@ -508,8 +521,8 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
|
||||
try {
|
||||
let logDialog
|
||||
if (ConversationLearner.logStorage) {
|
||||
logDialog = await ConversationLearner.logStorage.Get(appId, logDialogId)
|
||||
if (logStorage) {
|
||||
logDialog = await logStorage.Get(appId, logDialogId)
|
||||
}
|
||||
else {
|
||||
logDialog = await client.GetLogDialog(appId, logDialogId)
|
||||
|
@ -529,13 +542,15 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
if (typeof packageIds === "string") {
|
||||
packageIds = [packageIds]
|
||||
}
|
||||
|
||||
let logQueryResult: CLM.LogQueryResult
|
||||
if (ConversationLearner.logStorage) {
|
||||
logQueryResult = await ConversationLearner.logStorage.GetMany(appId, packageIds, continuationToken, maxPageSize)
|
||||
if (logStorage) {
|
||||
logQueryResult = await logStorage.GetMany(appId, packageIds, continuationToken, maxPageSize)
|
||||
}
|
||||
else {
|
||||
logQueryResult = await client.GetLogDialogs(appId, packageIds, continuationToken, maxPageSize)
|
||||
}
|
||||
|
||||
res.send(logQueryResult)
|
||||
} catch (error) {
|
||||
HandleError(res, error)
|
||||
|
@ -547,8 +562,8 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const { appId, logDialogId } = req.params
|
||||
|
||||
try {
|
||||
if (ConversationLearner.logStorage) {
|
||||
await ConversationLearner.logStorage.Delete(appId, logDialogId)
|
||||
if (logStorage) {
|
||||
await logStorage.Delete(appId, logDialogId)
|
||||
}
|
||||
else {
|
||||
await client.DeleteLogDialog(appId, logDialogId)
|
||||
|
@ -567,8 +582,8 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
logDialogIds = [logDialogIds]
|
||||
}
|
||||
try {
|
||||
if (ConversationLearner.logStorage) {
|
||||
ConversationLearner.logStorage.DeleteMany(appId, logDialogIds)
|
||||
if (logStorage) {
|
||||
logStorage.DeleteMany(appId, logDialogIds)
|
||||
}
|
||||
else {
|
||||
await client.DeleteLogDialogs(appId, logDialogIds)
|
||||
|
@ -592,7 +607,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const userInput: CLM.UserInput = req.body
|
||||
const extractResponse = await client.TrainDialogExtract(appId, trainDialogId, turnIndex, userInput)
|
||||
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const memories = await state.EntityState.DumpMemory()
|
||||
const uiExtractResponse: CLM.UIExtractResponse = { extractResponse, memories }
|
||||
res.send(uiExtractResponse)
|
||||
|
@ -613,7 +628,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
trainDialog.rounds = trainDialog.rounds.slice(0, turnIndex)
|
||||
|
||||
// Get activities and replay to put bot into last round
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const clRunner = CLRunner.GetRunnerForUI(appId)
|
||||
const teachWithActivities = await clRunner.GetActivities(trainDialog, userName, userId, state)
|
||||
if (!teachWithActivities) {
|
||||
|
@ -649,7 +664,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const clRunner = CLRunner.GetRunnerForUI(appId)
|
||||
validateBot(req, clRunner.botChecksum())
|
||||
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
|
||||
// Clear memory when running Log from UI
|
||||
state.EntityState.ClearAsync()
|
||||
|
@ -667,7 +682,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
router.put('/app/:appId/session', async (req, res, next) => {
|
||||
try {
|
||||
const key = getMemoryKey(req)
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const conversationId = await state.BotState.GetConversationId()
|
||||
// If conversation is empty
|
||||
if (!conversationId) {
|
||||
|
@ -698,7 +713,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const { appId } = req.params
|
||||
|
||||
// Session may be a replacement for an expired one
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
|
||||
const sessionId = await state.BotState.GetSessionIdAsync()
|
||||
// May have already been closed
|
||||
|
@ -732,7 +747,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
|
||||
validateBot(req, clRunner.botChecksum())
|
||||
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
|
||||
const createTeachParams: CLM.CreateTeachParams = {
|
||||
contextDialog: [],
|
||||
|
@ -756,7 +771,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
try {
|
||||
const key = getMemoryKey(req)
|
||||
// Update Memory
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
await state.EntityState.ClearAsync()
|
||||
res.sendStatus(200)
|
||||
|
||||
|
@ -780,7 +795,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const userInput: CLM.UserInput = req.body.userInput
|
||||
|
||||
// Get activities and replay to put bot into last round
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
|
||||
const clRunner = CLRunner.GetRunnerForUI(appId)
|
||||
|
||||
|
@ -851,7 +866,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const key = getMemoryKey(req)
|
||||
const appId = req.params.appId
|
||||
const trainDialog: CLM.TrainDialog = req.body
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const clRunner = CLRunner.GetRunnerForUI(appId)
|
||||
|
||||
// Replay the TrainDialog logic (API calls and EntityDetectionCallback)
|
||||
|
@ -902,7 +917,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const appId = req.params.appId
|
||||
const trainDialog: CLM.TrainDialog = req.body.trainDialog
|
||||
const userInput: CLM.UserInput = req.body.userInput
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const clRunner = CLRunner.GetRunnerForUI(appId)
|
||||
|
||||
// Replay the TrainDialog logic (API calls and EntityDetectionCallback)
|
||||
|
@ -937,7 +952,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const key = getMemoryKey(req)
|
||||
const appId = req.params.appId
|
||||
const trainDialog: CLM.TrainDialog = req.body
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const clRunner = CLRunner.GetRunnerForUI(appId)
|
||||
validateBot(req, clRunner.botChecksum())
|
||||
|
||||
|
@ -971,7 +986,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
}
|
||||
const extractResponse = await client.TeachExtract(appId, teachId, userInput, excludeConflictCheckId)
|
||||
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const memories = await state.EntityState.DumpMemory()
|
||||
const uiExtractResponse: CLM.UIExtractResponse = { extractResponse, memories }
|
||||
res.send(uiExtractResponse)
|
||||
|
@ -994,7 +1009,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const key = getMemoryKey(req)
|
||||
const { appId, teachId } = req.params
|
||||
const uiScoreInput: CLM.UIScoreInput = req.body
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
|
||||
// There will be no extraction step if performing a 2nd scorer round after a non-terminal action
|
||||
if (uiScoreInput.trainExtractorStep) {
|
||||
|
@ -1094,7 +1109,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const key = getMemoryKey(req)
|
||||
const { appId, teachId } = req.params
|
||||
const scoreInput: CLM.ScoreInput = req.body
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
|
||||
// Get new score response re-using scoreInput from previous score request
|
||||
const scoreResponse = await client.TeachScore(appId, teachId, scoreInput)
|
||||
|
@ -1128,7 +1143,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
}
|
||||
delete uiTrainScorerStep.trainScorerStep.scoredAction
|
||||
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
|
||||
// Now send the trained intent
|
||||
const intent = {
|
||||
|
@ -1187,7 +1202,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const response = await client.EndTeach(appId, teachId, save)
|
||||
res.send(response)
|
||||
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const clRunner = CLRunner.GetRunnerForUI(appId)
|
||||
clRunner.EndSessionAsync(state, CLM.SessionEndState.OPEN)
|
||||
} catch (error) {
|
||||
|
@ -1207,7 +1222,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const markdown = useMarkdown === "true"
|
||||
const trainDialog: CLM.TrainDialog = req.body
|
||||
|
||||
const state = CLState.Get(key)
|
||||
const state = stateFactory.get(key)
|
||||
const clRunner = CLRunner.GetRunnerForUI(appId)
|
||||
validateBot(req, clRunner.botChecksum())
|
||||
|
||||
|
@ -1243,7 +1258,7 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
const app = await client.GetApp(appId)
|
||||
|
||||
// TestId allows us to run multiple validations in parallel
|
||||
const state = CLState.Get(testId)
|
||||
const state = stateFactory.get(testId)
|
||||
|
||||
// Need to set app as not using default key
|
||||
await state.SetAppAsync(app)
|
||||
|
@ -1334,8 +1349,8 @@ export const getRouter = (client: CLClient, options: ICLClientOptions): express.
|
|||
CLDebug.Error(error.message)
|
||||
|
||||
// Delete log dialog
|
||||
if (ConversationLearner.logStorage) {
|
||||
await ConversationLearner.logStorage.Delete(appId, logDialogId)
|
||||
if (logStorage) {
|
||||
await logStorage.Delete(appId, logDialogId)
|
||||
}
|
||||
else {
|
||||
await client.DeleteLogDialog(appId, logDialogId)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
import { ConversationLearner } from './ConversationLearner'
|
||||
import ConversationLearnerFactory from './ConversationLearnerFactory'
|
||||
import { CLOptions } from './CLOptions'
|
||||
import { CLModelOptions } from './CLModelOptions'
|
||||
import { ClientMemoryManager, ReadOnlyClientMemoryManager } from './Memory/ClientMemoryManager'
|
||||
|
@ -16,8 +16,8 @@ import { CosmosLogStorage } from './CosmosLogStorage'
|
|||
|
||||
export {
|
||||
uiRouter,
|
||||
ConversationLearner,
|
||||
CLOptions as ICLOptions,
|
||||
ConversationLearnerFactory,
|
||||
CLOptions,
|
||||
CLModelOptions,
|
||||
ClientMemoryManager,
|
||||
ReadOnlyClientMemoryManager,
|
||||
|
|
Загрузка…
Ссылка в новой задаче