fix(basic.gblib): Fix Scheduling.
This commit is contained in:
parent
359c1beb02
commit
bb9d8c91e6
10 changed files with 161 additions and 70 deletions
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -6,7 +6,7 @@
|
|||
"request": "launch",
|
||||
"sourceMaps": true,
|
||||
"name": "Debug Program",
|
||||
"runtimeExecutable": "/root/.nvm/versions/node/v19.9.0/bin/node",
|
||||
"runtimeExecutable": "node",
|
||||
"program": "${workspaceRoot}/boot.mjs",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"env": {
|
||||
|
|
2
boot.mjs
2
boot.mjs
|
@ -9,8 +9,6 @@ import pjson from './package.json' assert { type: 'json' };
|
|||
// Displays version of Node JS being used at runtime and others attributes.
|
||||
|
||||
process.stdout.write(`General Bots. BotServer@${pjson.version}, botlib@${pjson.dependencies.botlib}, botbuilder@${pjson.dependencies.botbuilder}, node@${process.version.replace('v', '')}, ${process.platform} ${process.arch} `);
|
||||
os.setPriority(process.pid, os.constants.priority.PRIORITY_HIGHEST);
|
||||
|
||||
console.log(`\nLoading virtual machine source code files...`);
|
||||
|
||||
var __dirname = process.env.PWD || process.cwd();
|
||||
|
|
|
@ -135,6 +135,7 @@
|
|||
"ffmpeg-static": "5.1.0",
|
||||
"google-libphonenumber": "3.2.31",
|
||||
"googleapis": "126.0.1",
|
||||
"hnswlib-node": "^1.4.2",
|
||||
"ibm-watson": "7.1.2",
|
||||
"iso-639-1": "3.1.1",
|
||||
"join-images-updated": "1.1.4",
|
||||
|
@ -149,6 +150,7 @@
|
|||
"language-tags": "^1.0.9",
|
||||
"line-replace": "2.0.1",
|
||||
"lodash": "4.17.21",
|
||||
"lunary": "^0.6.16",
|
||||
"luxon": "3.1.0",
|
||||
"mammoth": "1.7.0",
|
||||
"mariadb": "3.2.2",
|
||||
|
|
|
@ -1346,7 +1346,7 @@ export class DialogKeywords {
|
|||
|
||||
public static async getProcessInfo(pid: number) {
|
||||
const proc = GBServer.globals.processes[pid];
|
||||
|
||||
const step = proc.step;
|
||||
const min = GBServer.globals.minInstances.filter(p => p.instance.instanceId == proc.instanceId)[0];
|
||||
const sec = new SecService();
|
||||
const user = await sec.getUserFromId(min.instance.instanceId, proc.userId);
|
||||
|
@ -1355,7 +1355,8 @@ export class DialogKeywords {
|
|||
min,
|
||||
user,
|
||||
params,
|
||||
proc
|
||||
proc,
|
||||
step
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1363,8 +1364,8 @@ export class DialogKeywords {
|
|||
* Talks to the user by using the specified text.
|
||||
*/
|
||||
public async talk({ pid, text }) {
|
||||
GBLog.info(`BASIC: TALK '${text}'.`);
|
||||
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
||||
const { min, user, step } = await DialogKeywords.getProcessInfo(pid);
|
||||
GBLog.info(`BASIC: TALK '${text} step:${step}'.`);
|
||||
|
||||
if (user) {
|
||||
// TODO: const translate = user ? user.basicOptions.translatorOn : false;
|
||||
|
@ -1376,7 +1377,12 @@ export class DialogKeywords {
|
|||
);
|
||||
GBLog.verbose(`Translated text(playMarkdown): ${text}.`);
|
||||
|
||||
await min.conversationalService['sendOnConversation'](min, user, text);
|
||||
if (step){
|
||||
await min.conversationalService.sendText(min, step, text);
|
||||
}
|
||||
else{
|
||||
await min.conversationalService['sendOnConversation'](min, user, text);
|
||||
}
|
||||
}
|
||||
return { status: 0 };
|
||||
}
|
||||
|
|
19
packages/basic.gblib/services/GBVMService.test.ts
Normal file
19
packages/basic.gblib/services/GBVMService.test.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { GBVMService } from './GBVMService';
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
test('Default', () => {
|
||||
|
||||
|
||||
const args = GBVMService.getSetScheduleKeywordArgs(`
|
||||
|
||||
SET SCHEDULE "0 0 */1 * * *"
|
||||
SET SCHEDULE "0 0 */3 * * *"
|
||||
SET SCHEDULE "0 0 */2 * * *"
|
||||
SET SCHEDULE "0 0 */2 * * *"
|
||||
SET SCHEDULE "0 0 */3 * * *"
|
||||
|
||||
`);
|
||||
|
||||
expect(args.length).toBe(5);
|
||||
|
||||
});
|
|
@ -128,13 +128,19 @@ export class GBVMService extends GBService {
|
|||
|
||||
// Pre process SET SCHEDULE calls.
|
||||
|
||||
const schedule = GBVMService.getSetScheduleKeywordArgs(text);
|
||||
const schedules = GBVMService.getSetScheduleKeywordArgs(text);
|
||||
|
||||
const s = new ScheduleServices();
|
||||
if (schedule) {
|
||||
await s.createOrUpdateSchedule(min, schedule, mainName);
|
||||
} else {
|
||||
await s.deleteScheduleIfAny(min, mainName);
|
||||
}
|
||||
await s.deleteScheduleIfAny(min, mainName);
|
||||
|
||||
let i = 1;
|
||||
await CollectionUtil.asyncForEach(schedules, async (syntax) => {
|
||||
|
||||
if (s) {
|
||||
await s.createOrUpdateSchedule(min, syntax, `${mainName};${i++}`);
|
||||
}
|
||||
});
|
||||
|
||||
text = text.replace(/^\s*SET SCHEDULE (.*)/gim, '');
|
||||
|
||||
// Write VBS file without pragma keywords.
|
||||
|
@ -687,13 +693,27 @@ export class GBVMService extends GBService {
|
|||
return mainName.toLowerCase();
|
||||
}
|
||||
|
||||
public static getSetScheduleKeywordArgs(code: string) {
|
||||
if (!code) return null;
|
||||
const keyword = /^\s*SET SCHEDULE (.*)/gim;
|
||||
const result = keyword.exec(code);
|
||||
return result ? result[1].replace(/\`/, '') : null;
|
||||
}
|
||||
public static getSetScheduleKeywordArgs(code) {
|
||||
if (!code) return [];
|
||||
|
||||
const lines = code.split(/\n/);
|
||||
const results = [];
|
||||
|
||||
lines.forEach(line => {
|
||||
if (line.trim()) {
|
||||
console.log(line);
|
||||
const keyword = /\s*SET SCHEDULE (.*)/gi;
|
||||
let result: any = keyword.exec(line);
|
||||
if (result) {
|
||||
result = result[1].replace(/\`|\"|\'/, '')
|
||||
result = result.trim();
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
private async getTextFromWord(folder: string, filename: string) {
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
const path = urlJoin(folder, filename);
|
||||
|
@ -1141,7 +1161,7 @@ export class GBVMService extends GBService {
|
|||
|
||||
}
|
||||
|
||||
public static createProcessInfo(user: GuaribasUser, min: GBMinInstance, channel: any, executable: string) {
|
||||
public static createProcessInfo(user: GuaribasUser, min: GBMinInstance, channel: any, executable: string, step = null) {
|
||||
const pid = GBAdminService.getNumberIdentifier();
|
||||
GBServer.globals.processes[pid] = {
|
||||
pid: pid,
|
||||
|
@ -1149,6 +1169,7 @@ export class GBVMService extends GBService {
|
|||
instanceId: min.instance.instanceId,
|
||||
channel: channel,
|
||||
roles: 'everyone',
|
||||
step: step,
|
||||
executable: executable
|
||||
};
|
||||
return pid;
|
||||
|
|
|
@ -47,30 +47,38 @@ import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
|
|||
* Basic services for BASIC manipulation.
|
||||
*/
|
||||
export class ScheduleServices extends GBService {
|
||||
public async deleteScheduleIfAny (min: GBMinInstance, name: string) {
|
||||
const task = min['scheduleMap'] ? min['scheduleMap'][name] : null;
|
||||
public async deleteScheduleIfAny(min: GBMinInstance, name: string) {
|
||||
|
||||
if (task) {
|
||||
task.destroy();
|
||||
delete min['scheduleMap'][name];
|
||||
}
|
||||
let i = 1;
|
||||
while (i <= 10) {
|
||||
const task = min['scheduleMap'] ? min['scheduleMap'][name + i] : null;
|
||||
|
||||
const count = await GuaribasSchedule.destroy({
|
||||
where: {
|
||||
instanceId: min.instance.instanceId,
|
||||
name: name
|
||||
if (task) {
|
||||
task.destroy();
|
||||
const id = `${name};${i}`;
|
||||
|
||||
delete min['scheduleMap'][id];
|
||||
const count = await GuaribasSchedule.destroy({
|
||||
where: {
|
||||
instanceId: min.instance.instanceId,
|
||||
name: id
|
||||
}
|
||||
});
|
||||
|
||||
if (count > 0) {
|
||||
GBLogEx.info(min, `BASIC: Removed ${name} SET SCHEDULE and ${count} rows from storage on: ${min.botId}...`);
|
||||
}
|
||||
}
|
||||
});
|
||||
i++;
|
||||
|
||||
if (count > 0) {
|
||||
GBLogEx.info(min,`BASIC: Removed ${name} SET SCHEDULE and ${count} rows from storage on: ${min.botId}...`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and update user agent information to a next available person.
|
||||
*/
|
||||
public async createOrUpdateSchedule (min: GBMinInstance, schedule: string, name: string): Promise<GuaribasSchedule> {
|
||||
public async createOrUpdateSchedule(min: GBMinInstance, schedule: string, name: string): Promise<GuaribasSchedule> {
|
||||
let record = await GuaribasSchedule.findOne({
|
||||
where: {
|
||||
instanceId: min.instance.instanceId,
|
||||
|
@ -97,11 +105,22 @@ export class ScheduleServices extends GBService {
|
|||
/**
|
||||
* Load all cached schedule from BASIC SET SCHEDULE keyword.
|
||||
*/
|
||||
public async scheduleAll () {
|
||||
public async scheduleAll() {
|
||||
let schedules;
|
||||
try {
|
||||
schedules = await GuaribasSchedule.findAll();
|
||||
await CollectionUtil.asyncForEach(schedules, async item => {
|
||||
let i = 0;
|
||||
let lastName = '';
|
||||
|
||||
await CollectionUtil.asyncForEach(schedules, async (item) => {
|
||||
|
||||
if (item.name === lastName) {
|
||||
item.name = item.name + ++i;
|
||||
}
|
||||
else {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
let min: GBMinInstance = GBServer.globals.minInstances.filter(
|
||||
p => p.instance.instanceId === item.instanceId
|
||||
)[0];
|
||||
|
@ -116,8 +135,8 @@ export class ScheduleServices extends GBService {
|
|||
return schedules;
|
||||
}
|
||||
|
||||
private ScheduleItem (item: GuaribasSchedule, min: GBMinInstance) {
|
||||
GBLogEx.info(min,`Scheduling ${item.name} on ${min.botId}...`);
|
||||
private ScheduleItem(item: GuaribasSchedule, min: GBMinInstance) {
|
||||
GBLogEx.info(min, `Scheduling ${item.name} on ${min.botId}...`);
|
||||
try {
|
||||
const options = {
|
||||
scheduled: true,
|
||||
|
@ -134,12 +153,12 @@ export class ScheduleServices extends GBService {
|
|||
item.schedule,
|
||||
function () {
|
||||
const finalData = async () => {
|
||||
let script = item.name;
|
||||
let script = item.name.split(';')[0];
|
||||
let min: GBMinInstance = GBServer.globals.minInstances.filter(
|
||||
p => p.instance.instanceId === item.instanceId
|
||||
)[0];
|
||||
GBLogEx.info(min,`Running .gbdialog word ${item.name} on:${item.schedule}...`);
|
||||
|
||||
GBLogEx.info(min, `Running .gbdialog word ${item.name} on:${item.schedule}...`);
|
||||
|
||||
const pid = GBVMService.createProcessInfo(null, min, 'batch', null);
|
||||
await GBVMService.callVM(script, min, null, pid);
|
||||
};
|
||||
|
@ -149,9 +168,9 @@ export class ScheduleServices extends GBService {
|
|||
},
|
||||
options
|
||||
);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
GBLogEx.error(min,`Running .gbdialog word ${item.name} : ${error}...`);
|
||||
GBLogEx.error(min, `Running .gbdialog word ${item.name} : ${error}...`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1443,9 +1443,6 @@ export class SystemKeywords {
|
|||
public async setSystemPrompt({ pid, text }) {
|
||||
|
||||
let { min, user } = await DialogKeywords.getProcessInfo(pid);
|
||||
const sec = new SecService();
|
||||
|
||||
|
||||
|
||||
if (user) {
|
||||
ChatServices.userSystemPrompt[user.userSystemId] = text;
|
||||
|
|
|
@ -1027,7 +1027,7 @@ export class GBMinService {
|
|||
|
||||
let pid = step.context.activity['pid'];
|
||||
if (!pid) {
|
||||
pid = GBVMService.createProcessInfo(user, min, step.context.activity.channelId, null);
|
||||
pid = GBVMService.createProcessInfo(user, min, step.context.activity.channelId, null, step);
|
||||
}
|
||||
step.context.activity['pid'] = pid;
|
||||
|
||||
|
|
|
@ -46,13 +46,15 @@ import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js';
|
|||
import { GBVMService } from '../../basic.gblib/services/GBVMService.js';
|
||||
import { GBConfigService } from '../../core.gbapp/services/GBConfigService.js';
|
||||
import { GuaribasSubject } from '../../kb.gbapp/models/index.js';
|
||||
import { z } from "zod";
|
||||
import { Serialized } from "@langchain/core/load/serializable";
|
||||
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
||||
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||
import {
|
||||
BaseLLMOutputParser,
|
||||
OutputParserException,
|
||||
} from "@langchain/core/output_parsers";
|
||||
import { ChatGeneration, Generation } from "@langchain/core/outputs";
|
||||
import { LunaryHandler } from "@langchain/community/callbacks/handlers/lunary";
|
||||
|
||||
export interface CustomOutputParserFields { }
|
||||
export type ExpectedOutput = string;
|
||||
|
@ -63,6 +65,28 @@ function isChatGeneration(
|
|||
return "message" in llmOutput;
|
||||
}
|
||||
|
||||
class CustomHandler extends BaseCallbackHandler {
|
||||
name = "custom_handler";
|
||||
|
||||
handleLLMNewToken(token: string) {
|
||||
GBLog.info(`LLM: token: ${JSON.stringify(token)}`);
|
||||
}
|
||||
|
||||
handleLLMStart(llm: Serialized, _prompts: string[]) {
|
||||
GBLog.info(`LLM: handleLLMStart ${JSON.stringify(llm)}, Prompts: ${_prompts.join('\n')}`);
|
||||
}
|
||||
|
||||
handleChainStart(chain: Serialized) {
|
||||
GBLog.info(`LLM: handleChainStart: ${JSON.stringify(chain)}`);
|
||||
}
|
||||
|
||||
handleToolStart(tool: Serialized) {
|
||||
GBLog.info(`LLM: handleToolStart: ${JSON.stringify(tool)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const logHandler = new CustomHandler();
|
||||
|
||||
export class CustomLLMOutputParser extends BaseLLMOutputParser<ExpectedOutput> {
|
||||
lc_namespace = ["langchain", "output_parsers"];
|
||||
|
||||
|
@ -105,7 +129,7 @@ export class ChatServices {
|
|||
private static async getRelevantContext(
|
||||
vectorStore: HNSWLib,
|
||||
sanitizedQuestion: string,
|
||||
numDocuments: number
|
||||
numDocuments: number = 10
|
||||
): Promise<string> {
|
||||
if (sanitizedQuestion === '') {
|
||||
return '';
|
||||
|
@ -149,7 +173,10 @@ export class ChatServices {
|
|||
'Default Content Language',
|
||||
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
|
||||
);
|
||||
|
||||
const LLMMode = min.core.getParam(
|
||||
min.instance,
|
||||
'Answer Mode', 'direct'
|
||||
);
|
||||
const docsContext = min['vectorStore'];
|
||||
|
||||
if (!this.memoryMap[user.userSystemId]) {
|
||||
|
@ -167,6 +194,7 @@ export class ChatServices {
|
|||
openAIApiKey: process.env.OPENAI_API_KEY,
|
||||
modelName: "gpt-3.5-turbo-0125",
|
||||
temperature: 0,
|
||||
callbacks: [logHandler],
|
||||
});
|
||||
|
||||
|
||||
|
@ -206,9 +234,13 @@ export class ChatServices {
|
|||
const combineDocumentsPrompt = ChatPromptTemplate.fromMessages([
|
||||
AIMessagePromptTemplate.fromTemplate(
|
||||
`
|
||||
\n\n{context}\n\n
|
||||
|
||||
And using \n\n{chat_history}\n\n
|
||||
rephrase the answer to the user using this context already spoken.
|
||||
If you don't know the answer, just say that you don't know, don't try to make up an answer.
|
||||
Use the following pieces, if any, of context to answer the question at the end.
|
||||
\n\n{context}\n\n
|
||||
|
||||
`
|
||||
),
|
||||
new MessagesPlaceholder("chat_history"),
|
||||
|
@ -239,14 +271,14 @@ export class ChatServices {
|
|||
|
||||
const combineDocumentsChain = RunnableSequence.from([
|
||||
{
|
||||
question: (output: string) => output,
|
||||
question: (question: string) => question,
|
||||
chat_history: async () => {
|
||||
const { chat_history } = await memory.loadMemoryVariables({});
|
||||
return chat_history;
|
||||
},
|
||||
context: async (output: string) => {
|
||||
const c = await ChatServices.getRelevantContext(docsContext, output, 1);
|
||||
return c ?? 'answer just with user question.';
|
||||
const c = await ChatServices.getRelevantContext(docsContext, output);
|
||||
return `${systemPrompt} \n ${c ? 'Use this context to answer:\n' + c: 'answer just with user question.'}`;
|
||||
|
||||
},
|
||||
},
|
||||
|
@ -269,28 +301,23 @@ export class ChatServices {
|
|||
new StringOutputParser()
|
||||
]);
|
||||
|
||||
const directChain = RunnableSequence.from([
|
||||
const conversationalToolChain = RunnableSequence.from([
|
||||
{
|
||||
question: (i: { question: string }) => {
|
||||
return `
|
||||
${systemPrompt}
|
||||
|
||||
${i.question}`
|
||||
},
|
||||
question: (i: { question: string }) => i.question,
|
||||
chat_history: async () => {
|
||||
const { chat_history } = await memory.loadMemoryVariables({});
|
||||
return chat_history;
|
||||
},
|
||||
},
|
||||
questionGeneratorTemplate,
|
||||
modelWithTools,
|
||||
new CustomLLMOutputParser(callToolChain, docsContext?.docstore?._docs.length > 0 ? combineDocumentsChain : null),
|
||||
new StringOutputParser()
|
||||
]);
|
||||
|
||||
const direct = true;
|
||||
let result;
|
||||
|
||||
if (direct) {
|
||||
if (LLMMode === "direct") {
|
||||
result = await (tools.length > 0 ? modelWithTools : model).invoke(`
|
||||
${systemPrompt}
|
||||
|
||||
|
@ -298,16 +325,18 @@ export class ChatServices {
|
|||
|
||||
result = result.content;
|
||||
}
|
||||
else {
|
||||
else if (LLMMode === "document") {
|
||||
result = await combineDocumentsChain.invoke(question);
|
||||
|
||||
result = await (directChain ?? conversationalQaChain).invoke({
|
||||
} else if (LLMMode === "function") {
|
||||
|
||||
result = await conversationalToolChain.invoke({
|
||||
question,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
else {
|
||||
GBLog.info(`Invalid Answer Mode in Config.xlsx: ${LLMMode}.`);
|
||||
}
|
||||
|
||||
await memory.saveContext(
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue