From 63efa588a88e9ae357d6a8409ce4058fd0c653dd Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Wed, 4 Sep 2024 00:18:19 -0300 Subject: [PATCH] fix(llm.gblib): Tool fix. --- .vscode/launch.json | 1 - packages/basic.gblib/services/GBVMService.ts | 32 ++++----- .../basic.gblib/services/vm2-process/index.ts | 15 +++-- .../services/vm2-process/vm2ProcessRunner.ts | 8 +++ packages/llm.gblib/services/ChatServices.ts | 65 +++++++++++++------ .../llm-tools.gbdata/products.csv | 3 +- .../llm-tools.gbdialog/get-price.bas | 13 ++-- .../llm-tools.gbdialog/start.bas | 1 + .../llm-tools.gbai/llm-tools.gbot/config.csv | 1 + 9 files changed, 92 insertions(+), 47 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 1e5af120..817a5373 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,6 @@ "NODE_NO_WARNINGS":"1" }, "args": [ - "--max-old-space-size 5120", "--no-deprecation", "--loader ts-node/esm", "--require ${workspaceRoot}/suppress-node-warnings.cjs", diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index 363f3c18..5ff0b408 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -577,10 +577,10 @@ export class GBVMService extends GBService { // Transfers auto variables into global object. - for(__indexer in this.variables) { - global[__indexer] = this.variables[__indexer]; - } - + for (const key of Object.keys(this.variables)) { + global[key] = this.variables[key]; + console.log('Defining global variable: ' + key); + } // Defines local utility BASIC functions. @@ -805,21 +805,22 @@ export class GBVMService extends GBService { const propertiesExp = propertiesText[i]; const t = getType(propertiesExp[2]); let element; + const description = propertiesExp[4]?.trim(); if (t === 'enum') { const list = propertiesExp[2] as any; element = z.enum(list.split(',')); } else if (t === 'string') { - element = z.string(); + element = z.string({description:description}); } else if (t === 'object') { - element = z.string(); // Assuming 'object' is represented as a string here + element = z.string({description:description}); // Assuming 'object' is represented as a string here } else if (t === 'number') { - element = z.number(); + element = z.number({description:description}); } else { GBLog.warn(`Element type invalid specified on .docx: ${propertiesExp[0]}`); } - element['description'] = propertiesExp[4]?.trim(); // Assuming description is in the 4th index + element['type'] = t; properties[propertiesExp[1].trim()] = element; } @@ -829,7 +830,7 @@ export class GBVMService extends GBService { function: { name: mainName, description: description ? description : '', - parameters: zodToJsonSchema(z.object(properties)) + schema: zodToJsonSchema(z.object(properties)) }, arguments: propertiesText.reduce((acc, prop) => { acc[prop[1].trim()] = prop[3]?.trim(); // Assuming value is in the 3rd index @@ -1049,7 +1050,7 @@ export class GBVMService extends GBService { GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') ); - let variables = []; + let variables = {}; // These variables will be automatically be available as normal BASIC variables. @@ -1090,7 +1091,7 @@ export class GBVMService extends GBService { const botId = min.botId; const path = DialogKeywords.getGBAIPath(min.botId, `gbdialog`); const gbdialogPath = urlJoin(process.cwd(), 'work', path); - const scriptPath = urlJoin(gbdialogPath, `${text}.js`); + const scriptFilePath = urlJoin(gbdialogPath, `${text}.js`); let code = min.sandBoxMap[text]; const channel = step ? step.context.activity.channelId : 'web'; @@ -1148,7 +1149,7 @@ export class GBVMService extends GBService { context: 'sandbox' } }); - const s = new VMScript(code, { filename: scriptPath }); + const s = new VMScript(code, { filename: scriptFilePath }); result = vm1.run(s); }); })(); @@ -1167,16 +1168,17 @@ export class GBVMService extends GBService { min: 0, max: 0, debug: debug, - debuggerport: GBVMService.DEBUGGER_PORT, + // debuggerport: GBVMService.DEBUGGER_PORT, botId: botId, cpu: 100, memory: 50000, time: 60 * 60 * 24 * 14, - cwd: scriptPath, + cwd: gbdialogPath, script: runnerPath + }); - result = await run(code, { filename: scriptPath, sandbox: sandbox }); + result = await run(code, Object.assign( sandbox, { filename: scriptFilePath})); } } catch (error) { throw new Error(`BASIC RUNTIME ERR: ${error.message ? error.message : error}\n Stack:${error.stack}`); diff --git a/packages/basic.gblib/services/vm2-process/index.ts b/packages/basic.gblib/services/vm2-process/index.ts index ae24cbdd..680a5d36 100644 --- a/packages/basic.gblib/services/vm2-process/index.ts +++ b/packages/basic.gblib/services/vm2-process/index.ts @@ -118,7 +118,6 @@ const systemVariables = [ 'valueOf' ]; - export const createVm2Pool = ({ min, max, ...limits }) => { limits = Object.assign( { @@ -140,8 +139,14 @@ export const createVm2Pool = ({ min, max, ...limits }) => { let stderrCache = ''; const run = async (code: any, scope: any) => { + // Configure environment variables + const env = Object.assign({}, process.env, { + NODE_ENV: 'production', + NODE_OPTIONS: '' // Clear NODE_OPTIONS if needed + }); + const childProcess = spawn( - 'cpulimit', + '/usr/bin/cpulimit', [ '-ql', limits.cpu, @@ -153,7 +158,7 @@ export const createVm2Pool = ({ min, max, ...limits }) => { limits.script, ref ], - { cwd: limits.cwd, shell: false } + { cwd: limits.cwd, shell: true, env: env } ); childProcess.stdout.on('data', data => { @@ -186,7 +191,7 @@ export const createVm2Pool = ({ min, max, ...limits }) => { // Only attach if called by debugger/run. - if (GBServer.globals.debuggers[limits.botId]) { + if (limits.debug) { const debug = async () => { return new Promise((resolve, reject) => { CDP(async client => { @@ -212,7 +217,7 @@ export const createVm2Pool = ({ min, max, ...limits }) => { }); } GBServer.globals.debuggers[limits.botId].scope = variablesText; - GBLogEx.info(min,`Breakpoint variables: ${variablesText}`); // (zero-based) + GBLogEx.info(min, `Breakpoint variables: ${variablesText}`); // (zero-based) // Processes breakpoint hits. if (hitBreakpoints.length >= 1) { diff --git a/packages/basic.gblib/services/vm2-process/vm2ProcessRunner.ts b/packages/basic.gblib/services/vm2-process/vm2ProcessRunner.ts index dee088a3..fce8d125 100644 --- a/packages/basic.gblib/services/vm2-process/vm2ProcessRunner.ts +++ b/packages/basic.gblib/services/vm2-process/vm2ProcessRunner.ts @@ -26,10 +26,13 @@ const server = net1.createServer(socket => { const buffer = []; const sync = async () => { + const request = buffer.join('').toString(); + console.log(request); if (request.includes('\n')) { try { const { code, scope } = JSON.parse(request); + const result = await evaluate(code, { ...scope, module: null @@ -45,6 +48,11 @@ const server = net1.createServer(socket => { } } }; + socket.on('error', err => { + + console.log(err); + }); + socket.on('data', data => { buffer.push(data); diff --git a/packages/llm.gblib/services/ChatServices.ts b/packages/llm.gblib/services/ChatServices.ts index b234733a..d76117eb 100644 --- a/packages/llm.gblib/services/ChatServices.ts +++ b/packages/llm.gblib/services/ChatServices.ts @@ -39,6 +39,7 @@ import { ChatGeneration, Generation } from '@langchain/core/outputs'; import { AIMessagePromptTemplate, ChatPromptTemplate, + SystemMessagePromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts'; @@ -72,6 +73,7 @@ import { } from 'langchain/chains/sql_db'; import { GBUtil } from '../../../src/util.js'; import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; export interface CustomOutputParserFields {} export type ExpectedOutput = any; @@ -323,25 +325,38 @@ export class ChatServices { let tools = await ChatServices.getTools(min); let toolsAsText = ChatServices.getToolsAsText(tools); + let openaiTools = tools.map(tool => convertToOpenAITool(tool, { strict: true })); + function updateFields(schemas) { + schemas.forEach(schema => { + + if (schema.function && schema.function.parameters) { + delete schema.function.strict; + schema.function.parameters.additionalProperties = false; + } + }); + } + updateFields(openaiTools); + const modelWithTools = model.bind({ - tools: tools.map(convertToOpenAITool) + tools: openaiTools }); const questionGeneratorTemplate = ChatPromptTemplate.fromMessages([ - AIMessagePromptTemplate.fromTemplate( + SystemMessagePromptTemplate.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: + ${systemPrompt} + + When a tool is required, use the tools provided below. + The tools available to you are listed below, along with their names, parameters, and descriptions: + IMPORTANT: Never call a tool with a missing required param, without asking them first to the user! + List of tools: + ${toolsAsText} - ${toolsAsText} - - Do not use any previous tools output in the {chat_history}. ` ), - new MessagesPlaceholder('chat_history'), - AIMessagePromptTemplate.fromTemplate(`Follow Up Input: {question} + new MessagesPlaceholder('chat_history'), + HumanMessagePromptTemplate.fromTemplate(`Follow Up Input: {question} Standalone question:`) ]); @@ -353,14 +368,23 @@ export class ChatServices { ]); 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. + SystemMessagePromptTemplate.fromTemplate( + ` + ${systemPrompt} + + List of tools: + ${toolsAsText} + ` ), - new MessagesPlaceholder('chat_history'), - AIMessagePromptTemplate.fromTemplate(`Tool output: {tool_output} - Standalone question:`) + AIMessagePromptTemplate.fromTemplate( + ` + The tool just returned value in last call answer the question based on tool description. + ` + ), + + HumanMessagePromptTemplate.fromTemplate(`Tool output: {tool_output} + Folowing answer:`) ]); const jsonInformation = `VERY IMPORTANT: ALWAYS return VALID standard JSON with the folowing structure: 'text' as answer, @@ -415,13 +439,14 @@ export class ChatServices { tool_output: async (output: object) => { const name = output['func'][0].function.name; const args = JSON.parse(output['func'][0].function.arguments); - GBLogEx.info(min, `Running .gbdialog '${name}' as LLM tool...`); + GBLogEx.info(min, `LLM Tool called .gbdialog '${name}'...`); const pid = GBVMService.createProcessInfo(null, min, 'LLM', null); return await GBVMService.callVM(name, min, false, pid, false, args); }, chat_history: async () => { const { chat_history } = await memory.loadMemoryVariables({}); + return chat_history; } }, @@ -623,10 +648,10 @@ export class ChatServices { return Object.keys(tools) .map(toolname => { const tool = tools[toolname]; - const properties = tool.lc_kwargs.parameters.properties; + const properties = tool.lc_kwargs.schema.properties; const params = Object.keys(properties).map(param => { const { description, type } = properties[param]; - return `${param} (${type}): ${description}`; + return `${param} *REQUIRED* (${type}): ${description}`; }).join(', '); return `- ${tool.name}: ${tool.description}\n Parameters: ${params?? 'No parameters'}`; @@ -649,7 +674,7 @@ export class ChatServices { if (funcObj) { // TODO: Use ajv. - funcObj.schema = jsonSchemaToZod(funcObj.parameters); + funcObj.schema = eval(funcObj.schema); functions.push(new DynamicStructuredTool(funcObj)); } } diff --git a/templates/llm-tools.gbai/llm-tools.gbdata/products.csv b/templates/llm-tools.gbai/llm-tools.gbdata/products.csv index 51e981f9..29d19bb6 100644 --- a/templates/llm-tools.gbai/llm-tools.gbdata/products.csv +++ b/templates/llm-tools.gbai/llm-tools.gbdata/products.csv @@ -1,5 +1,6 @@ name,price fax, 500 -TV, 1200 +tv, 1200 mobile,200 console, 250 +chocolate, 30 diff --git a/templates/llm-tools.gbai/llm-tools.gbdialog/get-price.bas b/templates/llm-tools.gbai/llm-tools.gbdialog/get-price.bas index 9ec29e6f..aaef165d 100644 --- a/templates/llm-tools.gbai/llm-tools.gbdialog/get-price.bas +++ b/templates/llm-tools.gbai/llm-tools.gbdialog/get-price.bas @@ -1,6 +1,9 @@ -PARAM product AS string LIKE telephone DESCRIPTION The name of the product to have the price retrieved. -DESCRIPTION Returns the price of the given product. +PARAM product AS string LIKE fax DESCRIPTION "Required name of the item you want to inquire about." +DESCRIPTION "Returns the price of the specified product name." -product = FIND "products.csv", "name = ${product}" -price = product.price -RETURN price \ No newline at end of file +price = -1 +productRecord = FIND "products.csv", "name = ${product}" +IF (productRecord) THEN + price = productRecord.price +END IF +RETURN price diff --git a/templates/llm-tools.gbai/llm-tools.gbdialog/start.bas b/templates/llm-tools.gbai/llm-tools.gbdialog/start.bas index bf81f7aa..669f0239 100644 --- a/templates/llm-tools.gbai/llm-tools.gbdialog/start.bas +++ b/templates/llm-tools.gbai/llm-tools.gbdialog/start.bas @@ -4,5 +4,6 @@ There exist some helpful predefined internal tools which can help me by extending my functionalities or get me helpful information. These tools **should** be abstracted away from the user. These tools can be invoked only by me before I respond to a user. +If get price tool return value of -1, says there is no such product. END SYSTEM PROMPT \ No newline at end of file diff --git a/templates/llm-tools.gbai/llm-tools.gbot/config.csv b/templates/llm-tools.gbai/llm-tools.gbot/config.csv index 4f0821f8..b073cbdf 100644 --- a/templates/llm-tools.gbai/llm-tools.gbot/config.csv +++ b/templates/llm-tools.gbai/llm-tools.gbot/config.csv @@ -1,2 +1,3 @@ name,value Answer Mode,tool +Start Dialog,start \ No newline at end of file