new(gpt.gblib): GPT Tools and .gbdialog.
This commit is contained in:
parent
a0c3481c7d
commit
359c1beb02
7 changed files with 192 additions and 98 deletions
|
@ -1,3 +1,4 @@
|
|||
|
||||
/*****************************************************************************\
|
||||
| █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® |
|
||||
| ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
||||
|
|
|
@ -1249,43 +1249,60 @@ export class DialogKeywords {
|
|||
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
||||
GBLogEx.info(min,`MESSAGE BOT: ${text}.`);
|
||||
|
||||
const conversation = min['apiConversations'][pid];
|
||||
const { conversation, client} = min['apiConversations'][pid];
|
||||
|
||||
conversation.client.apis.Conversations.Conversations_PostActivity({
|
||||
await client.apis.Conversations.Conversations_PostActivity({
|
||||
conversationId: conversation.conversationId,
|
||||
activity: {
|
||||
textFormat: 'plain',
|
||||
text: text,
|
||||
type: 'message',
|
||||
from: {
|
||||
id: 'word',
|
||||
name: 'word'
|
||||
id: user.userSystemId,
|
||||
name: user.userName
|
||||
}
|
||||
}
|
||||
});
|
||||
const watermarkMap = conversation.watermarkMap;
|
||||
|
||||
|
||||
let messages = [];
|
||||
|
||||
const response = await conversation.client.apis.Conversations.Conversations_GetActivities({
|
||||
conversationId: conversation.conversationId,
|
||||
watermark: conversation.watermark
|
||||
});
|
||||
conversation.watermarkMap = response.obj.watermark;
|
||||
let activities = response.obj.activites;
|
||||
|
||||
GBLog.info(`MessageBot: Starting message polling ${conversation.conversationId}).`);
|
||||
|
||||
if (activities && activities.length) {
|
||||
activities = activities.filter(m => m.from.id === min.botId && m.type === 'message');
|
||||
if (activities.length) {
|
||||
activities.forEach(activity => {
|
||||
messages.push({ text: activity.text });
|
||||
GBLogEx.info(min, `MESSAGE BOT answer from bot: ${activity.text}`);
|
||||
|
||||
|
||||
const worker = async () => {
|
||||
try {
|
||||
|
||||
const response = await client.apis.Conversations.Conversations_GetActivities({
|
||||
conversationId: conversation.conversationId,
|
||||
watermark: conversation.watermark
|
||||
});
|
||||
conversation.watermarkMap = response.obj.watermark;
|
||||
let activities = response.obj.activites;
|
||||
|
||||
|
||||
if (activities && activities.length) {
|
||||
activities = activities.filter(m => m.from.id === min.botId && m.type === 'message');
|
||||
if (activities.length) {
|
||||
activities.forEach(activity => {
|
||||
messages.push({ text: activity.text });
|
||||
GBLogEx.info(min, `MESSAGE BOT answer from bot: ${activity.text}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return messages.join('\n');
|
||||
|
||||
} catch (err) {
|
||||
GBLog.error(
|
||||
`Error calling printMessages API ${err.data === undefined ? err : err.data} ${err.errObj ? err.errObj.message : ''
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return messages.join('\n');
|
||||
};
|
||||
setInterval(worker, DEFAULT_HEAR_POLL_INTERVAL);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -645,6 +645,8 @@ export class GBVMService extends GBService {
|
|||
}
|
||||
catch(e){
|
||||
console.log(e);
|
||||
|
||||
reject ({message: e.message, name: e.name});
|
||||
}
|
||||
finally{
|
||||
|
||||
|
@ -652,6 +654,8 @@ export class GBVMService extends GBService {
|
|||
|
||||
await wa.closeHandles({pid: pid});
|
||||
await sys.closeHandles({pid: pid});
|
||||
|
||||
resolve(true);
|
||||
}
|
||||
})();
|
||||
`;
|
||||
|
@ -1085,8 +1089,9 @@ export class GBVMService extends GBService {
|
|||
try {
|
||||
if (GBConfigService.get('GBVM') === 'false') {
|
||||
return await (async () => {
|
||||
return await new Promise(resolve => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
sandbox['resolve'] = resolve;
|
||||
sandbox['reject'] = reject;
|
||||
const vm1 = new NodeVM({
|
||||
allowAsync: true,
|
||||
sandbox: sandbox,
|
||||
|
|
|
@ -714,7 +714,7 @@ export class KeywordsExpressions {
|
|||
keywords[i++] = [
|
||||
/\= NEW OBJECT/gim,
|
||||
($0, $1, $2, $3) => {
|
||||
return ` = {pid: pid}`;
|
||||
return ` = {}`;
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -1440,16 +1440,19 @@ export class SystemKeywords {
|
|||
return ret;
|
||||
}
|
||||
|
||||
public static async setSystemPrompt({ pid, systemPrompt }) {
|
||||
public async setSystemPrompt({ pid, text }) {
|
||||
|
||||
let { min, user } = await DialogKeywords.getProcessInfo(pid);
|
||||
const sec = new SecService();
|
||||
|
||||
|
||||
|
||||
if (user) {
|
||||
user['systemPrompt'] = systemPrompt;
|
||||
ChatServices.userSystemPrompt[user.userSystemId] = text;
|
||||
|
||||
const path = DialogKeywords.getGBAIPath(min.botId);
|
||||
const systemPromptFile = urlJoin(process.cwd(), 'work', path, 'users',user.userSystemId, 'systemPrompt.txt');
|
||||
Fs.writeFileSync(systemPromptFile, systemPrompt);
|
||||
Fs.writeFileSync(systemPromptFile, text);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -455,8 +455,7 @@ export class GBMinService {
|
|||
if (!res) {
|
||||
return 'GeneralBots';
|
||||
}
|
||||
if (req.body?.AccountSid)
|
||||
{
|
||||
if (req.body?.AccountSid) {
|
||||
return 'official';
|
||||
}
|
||||
return req.body.phone_id ? 'maytapi' : 'chatapi';
|
||||
|
@ -842,8 +841,8 @@ export class GBMinService {
|
|||
|
||||
await min.whatsAppDirectLine.setup(true);
|
||||
} else {
|
||||
if (min !== minBoot && minBoot.instance.whatsappServiceKey
|
||||
&& min.instance.webchatKey) {
|
||||
if (min !== minBoot && minBoot.instance.whatsappServiceKey
|
||||
&& min.instance.webchatKey) {
|
||||
min.whatsAppDirectLine = new WhatsappDirectLine(
|
||||
min,
|
||||
min.botId,
|
||||
|
@ -957,7 +956,7 @@ export class GBMinService {
|
|||
|
||||
req.body.channelId = req.body.from.channelIdEx === 'whatsapp' ? 'omnichannel' : req.body.channelId;
|
||||
req.body.group = req.body.from.group;
|
||||
|
||||
|
||||
// Default activity processing and handler.
|
||||
|
||||
const handler = async context => {
|
||||
|
@ -1027,7 +1026,7 @@ export class GBMinService {
|
|||
}
|
||||
|
||||
let pid = step.context.activity['pid'];
|
||||
if (!pid){
|
||||
if (!pid) {
|
||||
pid = GBVMService.createProcessInfo(user, min, step.context.activity.channelId, null);
|
||||
}
|
||||
step.context.activity['pid'] = pid;
|
||||
|
@ -1086,7 +1085,7 @@ export class GBMinService {
|
|||
|
||||
const startDialog = min.core.getParam(min.instance, 'Start Dialog', null);
|
||||
|
||||
|
||||
|
||||
if (context.activity.type === 'installationUpdate') {
|
||||
GBLog.info(`Bot installed on Teams.`);
|
||||
} else if (context.activity.type === 'conversationUpdate' && context.activity.membersAdded.length > 0) {
|
||||
|
@ -1421,7 +1420,7 @@ export class GBMinService {
|
|||
GBLog.info(
|
||||
`Auto start (whatsapp) dialog is now being called: ${startDialog} for ${min.instance.instanceId}...`
|
||||
);
|
||||
await GBVMService.callVM(startDialog.toLowerCase(), min, step,pid);
|
||||
await GBVMService.callVM(startDialog.toLowerCase(), min, step, pid);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -1455,7 +1454,7 @@ export class GBMinService {
|
|||
} else if (cmdOrDialogName === '/callsch') {
|
||||
await GBVMService.callVM(args, min, null, pid);
|
||||
} else if (cmdOrDialogName === '/calldbg') {
|
||||
await GBVMService.callVM(args, min, step, pid, true);
|
||||
await GBVMService.callVM(args, min, step, pid, true);
|
||||
} else {
|
||||
await step.beginDialog(cmdOrDialogName, { args: args });
|
||||
}
|
||||
|
@ -1613,8 +1612,40 @@ export class GBMinService {
|
|||
await CollectionUtil.asyncForEach(Object.values(min.scriptMap), async script => {
|
||||
|
||||
dialogs[script] = async (data) => {
|
||||
let params = JSON.parse(data);
|
||||
return await GBVMService.callVM(script, min, null, params.pid, false, params);
|
||||
let sec = new SecService();
|
||||
const user = await sec.ensureUser(
|
||||
min,
|
||||
data.userSystemId,
|
||||
data.userSystemId,
|
||||
'',
|
||||
'api',
|
||||
data.userSystemId,
|
||||
null
|
||||
);
|
||||
|
||||
let pid = data?.pid;
|
||||
if (script === 'start'){
|
||||
pid = GBVMService.createProcessInfo(user, min, 'api', null);
|
||||
|
||||
|
||||
const client = await new SwaggerClient({
|
||||
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
|
||||
requestInterceptor: req => {
|
||||
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
|
||||
}
|
||||
});
|
||||
const response = await client.apis.Conversations.Conversations_StartConversation();
|
||||
|
||||
min['apiConversations'][pid] = {conversation: response.obj, client: client};
|
||||
}
|
||||
|
||||
let ret = await GBVMService.callVM(script, min, null, pid, false, data);
|
||||
|
||||
if (script === 'start')
|
||||
{
|
||||
ret = pid;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ export class CustomLLMOutputParser extends BaseLLMOutputParser<ExpectedOutput> {
|
|||
private toolChain: RunnableSequence
|
||||
private documentChain: RunnableSequence;
|
||||
|
||||
constructor( toolChain: RunnableSequence, documentChain: RunnableSequence) {
|
||||
constructor(toolChain: RunnableSequence, documentChain: RunnableSequence) {
|
||||
super();
|
||||
this.toolChain = toolChain;
|
||||
this.documentChain = documentChain;
|
||||
|
@ -83,19 +83,19 @@ export class CustomLLMOutputParser extends BaseLLMOutputParser<ExpectedOutput> {
|
|||
"Output parser did not receive any generations."
|
||||
);
|
||||
}
|
||||
let parsedOutput;
|
||||
let result;
|
||||
|
||||
if (llmOutputs[0]['message'].lc_kwargs.additional_kwargs.tool_calls) {
|
||||
this.toolChain.invoke({func: llmOutputs[0]['message'].lc_kwargs.additional_kwargs.tool_calls});
|
||||
return this.toolChain.invoke({ func: llmOutputs[0]['message'].lc_kwargs.additional_kwargs.tool_calls });
|
||||
}
|
||||
|
||||
if (isChatGeneration(llmOutputs[0])) {
|
||||
parsedOutput = llmOutputs[0].message.content;
|
||||
result = llmOutputs[0].message.content;
|
||||
} else {
|
||||
parsedOutput = llmOutputs[0].text;
|
||||
result = llmOutputs[0].text;
|
||||
}
|
||||
|
||||
return this.documentChain.invoke(parsedOutput);
|
||||
return this.documentChain ? this.documentChain.invoke(result) : result;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +131,8 @@ export class ChatServices {
|
|||
|
||||
}
|
||||
|
||||
private static memoryMap = {};
|
||||
public static userSystemPrompt = {};
|
||||
|
||||
public static async answerByGPT(min: GBMinInstance, user, pid,
|
||||
question: string,
|
||||
|
@ -142,22 +144,24 @@ export class ChatServices {
|
|||
return { answer: undefined, questionId: 0 };
|
||||
}
|
||||
|
||||
|
||||
const contentLocale = min.core.getParam(
|
||||
min.instance,
|
||||
'Default Content Language',
|
||||
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
|
||||
);
|
||||
|
||||
const docsContext = min['vectorStore'];
|
||||
|
||||
const context = min['vectorStore'];
|
||||
|
||||
const memory = new BufferWindowMemory({
|
||||
returnMessages: true,
|
||||
memoryKey: 'chat_history',
|
||||
inputKey: 'input',
|
||||
k: 2,
|
||||
});
|
||||
if (!this.memoryMap[user.userSystemId]) {
|
||||
this.memoryMap[user.userSystemId] = new BufferWindowMemory({
|
||||
returnMessages: true,
|
||||
memoryKey: 'chat_history',
|
||||
inputKey: 'input',
|
||||
k: 2,
|
||||
})
|
||||
}
|
||||
const memory = this.memoryMap[user.userSystemId];
|
||||
const systemPrompt = this.userSystemPrompt[user.userSystemId];
|
||||
|
||||
const model = new ChatOpenAI({
|
||||
openAIApiKey: process.env.OPENAI_API_KEY,
|
||||
|
@ -168,15 +172,19 @@ export class ChatServices {
|
|||
|
||||
let tools = await ChatServices.getTools(min);
|
||||
let toolsAsText = ChatServices.getToolsAsText(tools);
|
||||
|
||||
const modelWithTools = model.bind({
|
||||
tools: tools.map(convertToOpenAITool)
|
||||
});
|
||||
|
||||
const questionGeneratorTemplate = ChatPromptTemplate.fromMessages([
|
||||
AIMessagePromptTemplate.fromTemplate(
|
||||
`Answer the question without calling any tool, but if there is a need to call:
|
||||
`
|
||||
Answer the question without calling any tool, but if there is a need to call:
|
||||
You have access to the following set of tools. Here are the names and descriptions for each tool:
|
||||
${toolsAsText}
|
||||
|
||||
Do not use any previous tools output in the chat_history.
|
||||
`
|
||||
),
|
||||
new MessagesPlaceholder("chat_history"),
|
||||
|
@ -184,10 +192,22 @@ export class ChatServices {
|
|||
Standalone question:`),
|
||||
]);
|
||||
|
||||
const toolsResultPrompt = ChatPromptTemplate.fromMessages([
|
||||
AIMessagePromptTemplate.fromTemplate(
|
||||
`The tool just returned value in last call. Using {chat_history}
|
||||
rephrase the answer to the user using this tool output.
|
||||
`
|
||||
),
|
||||
new MessagesPlaceholder("chat_history"),
|
||||
AIMessagePromptTemplate.fromTemplate(`Tool output: {tool_output}
|
||||
Standalone question:`),
|
||||
]);
|
||||
|
||||
const combineDocumentsPrompt = ChatPromptTemplate.fromMessages([
|
||||
AIMessagePromptTemplate.fromTemplate(
|
||||
`Use the following pieces of context to answer the question at the end.
|
||||
`
|
||||
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
|
||||
`
|
||||
),
|
||||
|
@ -197,13 +217,11 @@ export class ChatServices {
|
|||
|
||||
const callToolChain = RunnableSequence.from([
|
||||
{
|
||||
func: async (output: object) =>{
|
||||
tool_output: async (output: object) => {
|
||||
|
||||
const name = output['func'][0].function.name;
|
||||
const args = JSON.parse(output['func'][0].function.arguments);
|
||||
|
||||
GBLog.info(`Running .gbdialog '${name}' as GPT tool...`);
|
||||
|
||||
const pid = GBVMService.createProcessInfo(null, min, 'gpt', null);
|
||||
|
||||
return await GBVMService.callVM(name, min, false, pid, false, args);
|
||||
|
@ -212,12 +230,13 @@ export class ChatServices {
|
|||
const { chat_history } = await memory.loadMemoryVariables({});
|
||||
return chat_history;
|
||||
},
|
||||
|
||||
},
|
||||
toolsResultPrompt,
|
||||
model,
|
||||
new StringOutputParser()
|
||||
]);
|
||||
|
||||
|
||||
|
||||
const combineDocumentsChain = RunnableSequence.from([
|
||||
{
|
||||
question: (output: string) => output,
|
||||
|
@ -226,7 +245,8 @@ export class ChatServices {
|
|||
return chat_history;
|
||||
},
|
||||
context: async (output: string) => {
|
||||
return await ChatServices.getRelevantContext(context, output, 1);
|
||||
const c = await ChatServices.getRelevantContext(docsContext, output, 1);
|
||||
return c ?? 'answer just with user question.';
|
||||
|
||||
},
|
||||
},
|
||||
|
@ -245,34 +265,63 @@ export class ChatServices {
|
|||
},
|
||||
questionGeneratorTemplate,
|
||||
modelWithTools,
|
||||
new CustomLLMOutputParser(callToolChain, combineDocumentsChain)
|
||||
new CustomLLMOutputParser(callToolChain, docsContext?.docstore?._docs.length > 0 ? combineDocumentsChain : null),
|
||||
new StringOutputParser()
|
||||
]);
|
||||
|
||||
const systemPrompt = user['systemPrompt'];
|
||||
const directChain = RunnableSequence.from([
|
||||
{
|
||||
question: (i: { question: string }) => {
|
||||
return `
|
||||
${systemPrompt}
|
||||
|
||||
${i.question}`
|
||||
},
|
||||
chat_history: async () => {
|
||||
const { chat_history } = await memory.loadMemoryVariables({});
|
||||
return chat_history;
|
||||
},
|
||||
},
|
||||
modelWithTools,
|
||||
new CustomLLMOutputParser(callToolChain, docsContext?.docstore?._docs.length > 0 ? combineDocumentsChain : null),
|
||||
new StringOutputParser()
|
||||
]);
|
||||
|
||||
let result = await conversationalQaChain.invoke({
|
||||
question,
|
||||
});
|
||||
const direct = true;
|
||||
let result;
|
||||
|
||||
if (result['name']) {
|
||||
const func = result['func'];
|
||||
await func.func(min, result['args']);
|
||||
if (direct) {
|
||||
result = await (tools.length > 0 ? modelWithTools : model).invoke(`
|
||||
${systemPrompt}
|
||||
|
||||
${question}`);
|
||||
|
||||
} else {
|
||||
result = result.content;
|
||||
}
|
||||
else {
|
||||
|
||||
// await memory.saveContext(
|
||||
// {
|
||||
// input: query,
|
||||
// },
|
||||
// {
|
||||
// output: result,
|
||||
// }
|
||||
// );
|
||||
|
||||
GBLog.info(`GPT Result: ${result.toString()}`);
|
||||
return { answer: result.toString(), questionId: 0 };
|
||||
result = await (directChain ?? conversationalQaChain).invoke({
|
||||
question,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
await memory.saveContext(
|
||||
{
|
||||
input: question,
|
||||
},
|
||||
{
|
||||
output: result,
|
||||
}
|
||||
);
|
||||
|
||||
GBLog.info(`GPT Result: ${result.toString()}`);
|
||||
return { answer: result.toString(), questionId: 0 };
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static getToolsAsText(tools) {
|
||||
|
@ -286,39 +335,27 @@ export class ChatServices {
|
|||
|
||||
// Adds .gbdialog as functions if any to GPT Functions.
|
||||
await CollectionUtil.asyncForEach(Object.keys(min.scriptMap), async (script) => {
|
||||
|
||||
|
||||
const path = DialogKeywords.getGBAIPath(min.botId, "gbdialog", null);
|
||||
const jsonFile = Path.join('work', path, `${script}.json`);
|
||||
|
||||
if (Fs.existsSync(jsonFile)) {
|
||||
if (Fs.existsSync(jsonFile) && script.toLowerCase() !== 'start.vbs') {
|
||||
|
||||
const funcJSON = JSON.parse(Fs.readFileSync(jsonFile, 'utf8'));
|
||||
const funcObj = funcJSON?.function;
|
||||
|
||||
if (funcObj){
|
||||
|
||||
if (funcObj) {
|
||||
|
||||
// TODO: Use ajv.
|
||||
funcObj.schema = eval(jsonSchemaToZod(funcObj.parameters));
|
||||
functions.push(new DynamicStructuredTool(funcObj));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const multiplyTool = new DynamicStructuredTool({
|
||||
name: "multiply",
|
||||
description: "Multiply two integers together.",
|
||||
schema: z.object({
|
||||
firstInt: z.number(),
|
||||
secondInt: z.number(),
|
||||
}),
|
||||
func: async ({ firstInt, secondInt }) => {
|
||||
return (firstInt * secondInt).toString();
|
||||
},
|
||||
});
|
||||
|
||||
functions.push(multiplyTool);
|
||||
|
||||
|
||||
return functions;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue