From ce36ac476e6fab05a44c5e88e62219a065487e77 Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Wed, 13 Mar 2024 20:12:05 -0300 Subject: [PATCH] new(gpt.gblib): GPT Tools and .gbdialog. --- .vscode/launch.json | 1 + boot.mjs | 11 +- packages/core.gbapp/services/GBDeployer.ts | 4 +- packages/gpt.gblib/services/ChatServices.ts | 133 +++++++++----------- 4 files changed, 69 insertions(+), 80 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 9e3fe8b9..2e078e75 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,6 +6,7 @@ "request": "launch", "sourceMaps": true, "name": "Debug Program", + "runtimeExecutable": "/root/.nvm/versions/node/v19.9.0/bin/node", "program": "${workspaceRoot}/boot.mjs", "cwd": "${workspaceRoot}", "env": { diff --git a/boot.mjs b/boot.mjs index 45bc5470..d1b8c84d 100644 --- a/boot.mjs +++ b/boot.mjs @@ -1,24 +1,23 @@ #!/usr/bin/env node -import chalkAnimation from 'chalk-animation'; import Fs from 'fs'; +import os from 'node:os'; import Path from 'path'; import { exec } from 'child_process'; import pjson from './package.json' assert { type: 'json' }; -import { GBUtil } from './dist/src/util.js'; // Displays version of Node JS being used at runtime and others attributes. -console.log(`General Bots is loading source code files...`); +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(); try { var run = () => { import('./dist/src/app.js').then((gb)=> { - console.log(`\n`); - process.stdout.write(`${pjson.version}, botlib@${pjson.dependencies.botlib}, botbuilder@${pjson.dependencies.botbuilder}, node@${process.version.replace('v', '')}, ${process.platform} ${process.arch} `); - console.log(`\n`); gb.GBServer.run() }); }; diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index 74d21064..ac74c153 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -56,8 +56,8 @@ import MicrosoftGraph from '@microsoft/microsoft-graph-client'; import { GBLogEx } from './GBLogEx.js'; import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js'; import { GBUtil } from '../../../src/util.js'; -import { HNSWLib } from 'langchain/vectorstores/hnswlib'; -import { OpenAIEmbeddings } from 'langchain/embeddings/openai'; +import { HNSWLib } from '@langchain/community/vectorstores/hnswlib'; +import { OpenAIEmbeddings } from '@langchain/openai'; /** * Deployer service for bots, themes, ai and more. diff --git a/packages/gpt.gblib/services/ChatServices.ts b/packages/gpt.gblib/services/ChatServices.ts index 76b1d585..ed07fa73 100644 --- a/packages/gpt.gblib/services/ChatServices.ts +++ b/packages/gpt.gblib/services/ChatServices.ts @@ -48,16 +48,6 @@ import { GBConfigService } from '../../core.gbapp/services/GBConfigService.js'; import { GuaribasSubject } from '../../kb.gbapp/models/index.js'; import { z } from "zod"; import { DynamicStructuredTool } from "@langchain/core/tools"; -import { JsonOutputToolsParser } from "langchain/output_parsers"; -import { - RunnableLambda, - RunnablePassthrough, -} from "@langchain/core/runnables"; -import { - CombiningOutputParser, -} from "langchain/output_parsers"; - - import { BaseLLMOutputParser, OutputParserException, @@ -65,8 +55,6 @@ import { import { ChatGeneration, Generation } from "@langchain/core/outputs"; export interface CustomOutputParserFields { } - -// This can be more generic, like Record export type ExpectedOutput = string; function isChatGeneration( @@ -78,8 +66,13 @@ function isChatGeneration( export class CustomLLMOutputParser extends BaseLLMOutputParser { lc_namespace = ["langchain", "output_parsers"]; - constructor(fields?: CustomOutputParserFields) { - super(fields); + private toolChain: RunnableSequence + private documentChain: RunnableSequence; + + constructor( toolChain: RunnableSequence, documentChain: RunnableSequence) { + super(); + this.toolChain = toolChain; + this.documentChain = documentChain; } async parseResult( @@ -92,15 +85,19 @@ export class CustomLLMOutputParser extends BaseLLMOutputParser { } let parsedOutput; + if (llmOutputs[0]['message'].lc_kwargs.additional_kwargs.tool_calls) { + this.toolChain.invoke({func: llmOutputs[0]['message'].lc_kwargs.additional_kwargs.tool_calls}); + } if (isChatGeneration(llmOutputs[0])) { parsedOutput = llmOutputs[0].message.content; } else { parsedOutput = llmOutputs[0].text; } - let parsedText; - parsedText = parsedOutput; - return parsedText; + + this.documentChain.invoke(parsedOutput); + + return ``; } } @@ -153,12 +150,8 @@ export class ChatServices { GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') ); - let tools = await ChatServices.getTools(min); - let toolsAsText = ChatServices.getToolsAsText(tools); - const toolMap: Record = { - multiply: tools[0] - }; + const context = min['vectorStore']; const memory = new BufferWindowMemory({ returnMessages: true, @@ -173,41 +166,17 @@ export class ChatServices { temperature: 0, }); - const context = min['vectorStore']; + let tools = await ChatServices.getTools(min); + let toolsAsText = ChatServices.getToolsAsText(tools); const modelWithTools = model.bind({ tools: tools.map(convertToOpenAITool) }); - // Function for dynamically constructing the end of the chain based on the model-selected tool. - const callSelectedTool = RunnableLambda.from( - (toolInvocation: Record) => { - const selectedTool = toolMap[toolInvocation.type]; - if (!selectedTool) { - throw new Error( - `No matching tool available for requested type "${toolInvocation.type}".` - ); - } - const toolCallChain = RunnableSequence.from([ - (toolInvocation) => toolInvocation.args, - selectedTool, - ]); - // We use `RunnablePassthrough.assign` here to return the intermediate `toolInvocation` params - // as well, but you can omit if you only care about the answer. - return RunnablePassthrough.assign({ - output: toolCallChain, - }); - }, - - ); - const questionGeneratorTemplate = ChatPromptTemplate.fromMessages([ AIMessagePromptTemplate.fromTemplate( `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: - + You have access to the following set of tools. Here are the names and descriptions for each tool: ${toolsAsText} ` ), @@ -227,6 +196,26 @@ export class ChatServices { HumanMessagePromptTemplate.fromTemplate("Question: {question}"), ]); + const callToolChain = RunnableSequence.from([ + { + func: async (output: object) =>{ + + const pid = 1; + const name = output['func'][0].function.name; + const args = JSON.parse(output['func'][0].function.arguments); + return await GBVMService.callVM(name, min, false, pid, false, args); + + }, + chat_history: async () => { + const { chat_history } = await memory.loadMemoryVariables({}); + return chat_history; + }, + }, + new StringOutputParser() + ]); + + + const combineDocumentsChain = RunnableSequence.from([ { question: (output: string) => output, @@ -236,14 +225,14 @@ export class ChatServices { }, context: async (output: string) => { return await ChatServices.getRelevantContext(context, output, 1); - + }, }, combineDocumentsPrompt, modelWithTools, - + ]); - + const conversationalQaChain = RunnableSequence.from([ { question: (i: { question: string }) => i.question, @@ -254,7 +243,7 @@ export class ChatServices { }, questionGeneratorTemplate, modelWithTools, - new CustomLLMOutputParser() + new CustomLLMOutputParser(callToolChain, combineDocumentsChain) ]); const systemPrompt = user['systemPrompt']; @@ -262,20 +251,28 @@ export class ChatServices { let result = await conversationalQaChain.invoke({ question, }); - // await memory.saveContext( - // { - // input: query, - // }, - // { - // output: result, - // } - // ); - GBLog.info(`GPT Result: ${result.toString()}`); - return { answer: result.toString(), questionId: 0 }; + if (result['name']) { + const func = result['func']; + await func.func(min, result['args']); + + } else { + + // await memory.saveContext( + // { + // input: query, + // }, + // { + // output: result, + // } + // ); + + GBLog.info(`GPT Result: ${result.toString()}`); + return { answer: result.toString(), questionId: 0 }; + + } } - private static getToolsAsText(tools) { return Object.keys(tools) .map((toolname) => `- ${tools[toolname].name}: ${tools[toolname].description}`) @@ -293,14 +290,6 @@ export class ChatServices { if (Fs.existsSync(functionJSON)) { const func = JSON.parse(Fs.readFileSync(functionJSON, 'utf8')); func.schema = jsonSchemaToZod(func.properties, { module: "esm" }); - func.func = async () => { - const name = ''; - const pid = 1; - const text = ''; // TODO: - const result = await GBVMService.callVM(name, min, false, pid, false, [text]); - - } - functions.push(func); }