This commit is contained in:
parent
b5bf44fd70
commit
d7d6afa5cc
4 changed files with 195 additions and 121 deletions
|
@ -270,21 +270,18 @@ export class GBDeployer implements IGBDeployer {
|
|||
* Verifies if bot exists on bot catalog.
|
||||
*/
|
||||
public async botExists(botId: string): Promise<boolean> {
|
||||
|
||||
if (GBConfigService.get('GB_MODE') !== 'legacy') {
|
||||
const where = { botId: botId };
|
||||
|
||||
return await GuaribasInstance.findOne({
|
||||
return (
|
||||
(await GuaribasInstance.findOne({
|
||||
where: where
|
||||
}) !== null;
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
})) !== null
|
||||
);
|
||||
} else {
|
||||
const service = await AzureDeployerService.createInstance(this);
|
||||
|
||||
return await service.botExists(botId);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,6 +336,41 @@ export class GBDeployer implements IGBDeployer {
|
|||
public async loadOrCreateEmptyVectorStore(min: GBMinInstance): Promise<HNSWLib> {
|
||||
let vectorStore: HNSWLib;
|
||||
|
||||
// Get AI mode (default to 'azure' for backward compatibility)
|
||||
const aiMode = (await (min.core as any)['getParam'](min.instance, 'AI Mode', 'azure', true)) || 'azure';
|
||||
|
||||
let embedding;
|
||||
|
||||
if (aiMode === 'local') {
|
||||
// Local embedding configuration
|
||||
const localEmbeddingEndpoint = await (min.core as any)['getParam'](
|
||||
min.instance,
|
||||
'Local Embedding Endpoint',
|
||||
'http://localhost:5858/v1',
|
||||
true
|
||||
);
|
||||
const localEmbeddingModel = await (min.core as any)['getParam'](
|
||||
min.instance,
|
||||
'Local Embedding Model',
|
||||
'model',
|
||||
true
|
||||
);
|
||||
|
||||
if (!localEmbeddingEndpoint || !localEmbeddingModel) {
|
||||
GBLogEx.error(min, 'Local embedding configuration incomplete. Please set Local Embedding Endpoint and Model.');
|
||||
return;
|
||||
}
|
||||
|
||||
embedding = new OpenAIEmbeddings({
|
||||
maxConcurrency: 5,
|
||||
openAIApiKey: 'null', // Required field but not used for local
|
||||
modelName: localEmbeddingModel,
|
||||
configuration: {
|
||||
baseURL: localEmbeddingEndpoint
|
||||
}
|
||||
} as any);
|
||||
} else {
|
||||
// Azure OpenAI configuration (original code)
|
||||
const azureOpenAIKey = await (min.core as any)['getParam'](min.instance, 'Azure Open AI Key', null, true);
|
||||
const azureOpenAIVersion = await (min.core as any)['getParam'](min.instance, 'Azure Open AI Version', null, true);
|
||||
const azureOpenAIApiInstanceName = await (min.core as any)['getParam'](
|
||||
|
@ -354,7 +386,6 @@ export class GBDeployer implements IGBDeployer {
|
|||
true
|
||||
);
|
||||
|
||||
let embedding;
|
||||
if (!azureOpenAIEmbeddingModel) {
|
||||
return;
|
||||
}
|
||||
|
@ -366,6 +397,7 @@ export class GBDeployer implements IGBDeployer {
|
|||
azureOpenAIApiVersion: azureOpenAIVersion,
|
||||
azureOpenAIApiInstanceName: azureOpenAIApiInstanceName
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
vectorStore = await HNSWLib.load(min['vectorStorePath'], embedding);
|
||||
|
@ -376,7 +408,7 @@ export class GBDeployer implements IGBDeployer {
|
|||
{}, // Optional metadata
|
||||
embedding,
|
||||
{
|
||||
'space': 'cosine',
|
||||
space: 'cosine'
|
||||
} as any
|
||||
);
|
||||
const dir = path.dirname(min['vectorStorePath']);
|
||||
|
@ -494,15 +526,16 @@ export class GBDeployer implements IGBDeployer {
|
|||
} else {
|
||||
return [];
|
||||
}
|
||||
1
|
||||
1;
|
||||
await asyncPromise.eachSeries(rows, async (line: any) => {
|
||||
if (line && line.length > 0) {
|
||||
const key = line[1];
|
||||
let value = line[2];
|
||||
|
||||
|
||||
if (key && value) {
|
||||
if (value.text) { value = value.text };
|
||||
if (value.text) {
|
||||
value = value.text;
|
||||
}
|
||||
obj[key] = value;
|
||||
}
|
||||
}
|
||||
|
@ -521,7 +554,8 @@ export class GBDeployer implements IGBDeployer {
|
|||
localPath: string,
|
||||
remotePath: string,
|
||||
baseUrl: string = null,
|
||||
client = null, onlyTextFiles = false
|
||||
client = null,
|
||||
onlyTextFiles = false
|
||||
): Promise<any> {
|
||||
const storageMode = process.env.GB_MODE;
|
||||
|
||||
|
@ -531,7 +565,7 @@ export class GBDeployer implements IGBDeployer {
|
|||
port: parseInt(process.env.DRIVE_PORT || '9000', 10),
|
||||
useSSL: process.env.DRIVE_USE_SSL === 'true',
|
||||
accessKey: process.env.DRIVE_ACCESSKEY,
|
||||
secretKey: process.env.DRIVE_SECRET,
|
||||
secretKey: process.env.DRIVE_SECRET
|
||||
});
|
||||
|
||||
const bucketName = (process.env.DRIVE_ORG_PREFIX + min.botId + '.gbai').toLowerCase();
|
||||
|
@ -566,7 +600,6 @@ export class GBDeployer implements IGBDeployer {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if (download) {
|
||||
await minioClient.fGetObject(bucketName, obj.name, itemPath);
|
||||
await fs.utimes(itemPath, new Date(), new Date(obj.lastModified));
|
||||
|
@ -585,7 +618,7 @@ export class GBDeployer implements IGBDeployer {
|
|||
await fs.mkdir(pathBase, { recursive: true });
|
||||
}
|
||||
|
||||
await CollectionUtil.asyncForEach(parts, async (item) => {
|
||||
await CollectionUtil.asyncForEach(parts, async item => {
|
||||
pathBase = path.join(pathBase, item);
|
||||
if (!(await GBUtil.exists(pathBase))) {
|
||||
await fs.mkdir(pathBase, { recursive: true });
|
||||
|
@ -609,7 +642,7 @@ export class GBDeployer implements IGBDeployer {
|
|||
return null;
|
||||
}
|
||||
|
||||
await CollectionUtil.asyncForEach(documents, async (item) => {
|
||||
await CollectionUtil.asyncForEach(documents, async item => {
|
||||
const itemPath = path.join(localPath, remotePath, item.name);
|
||||
|
||||
if (item.folder) {
|
||||
|
@ -639,7 +672,6 @@ export class GBDeployer implements IGBDeployer {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -699,11 +731,9 @@ export class GBDeployer implements IGBDeployer {
|
|||
await GBUtil.copyIfNewerRecursive(filePath, packageWorkFolder, false);
|
||||
}
|
||||
} else {
|
||||
|
||||
if (packageType === '.gbdrive' || packageType === '.gbdata') {
|
||||
await this.downloadFolder(min, path.join('work', `${gbai}`), packageName, undefined, undefined, true);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
await this.downloadFolder(min, path.join('work', `${gbai}`), packageName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
\*****************************************************************************/
|
||||
|
||||
'use strict';
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { ChatAnthropic } from '@langchain/anthropic';
|
||||
import { PromptTemplate } from '@langchain/core/prompts';
|
||||
import { WikipediaQueryRun } from '@langchain/community/tools/wikipedia_query_run';
|
||||
import { HNSWLib } from '@langchain/community/vectorstores/hnswlib';
|
||||
|
@ -60,7 +60,7 @@ import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js';
|
|||
import { GBVMService } from '../../basic.gblib/services/GBVMService.js';
|
||||
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
|
||||
import { GBUtil } from '../../../src/util.js';
|
||||
import { GBConfigService } from "../../core.gbapp/services/GBConfigService.js";
|
||||
import { GBConfigService } from '../../core.gbapp/services/GBConfigService.js';
|
||||
export interface CustomOutputParserFields {}
|
||||
export type ExpectedOutput = any;
|
||||
|
||||
|
@ -135,16 +135,13 @@ export class GBLLMOutputParser extends BaseLLMOutputParser<ExpectedOutput> {
|
|||
let securityEnabled = false;
|
||||
|
||||
if (!sources) {
|
||||
|
||||
GBLogEx.verbose(this.min, `LLM JSON output sources is NULL.`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
await CollectionUtil.asyncForEach(sources, async source => {
|
||||
let found = false;
|
||||
|
||||
if (securityEnabled) {
|
||||
GBLogEx.info(this.min, `LLM JSON output security enabled.`);
|
||||
|
||||
}
|
||||
|
||||
if (source && source.file.endsWith('.pdf')) {
|
||||
|
@ -156,11 +153,14 @@ export class GBLLMOutputParser extends BaseLLMOutputParser<ExpectedOutput> {
|
|||
|
||||
if (!isNaN(this.user.userSystemId)) {
|
||||
await this.min.whatsAppDirectLine.sendFileToDevice(
|
||||
this.user.userSystemId, pngs[0].url,
|
||||
localName, null, undefined, true);
|
||||
|
||||
}
|
||||
else {
|
||||
this.user.userSystemId,
|
||||
pngs[0].url,
|
||||
localName,
|
||||
null,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
text = `
|
||||
${text}`;
|
||||
}
|
||||
|
@ -179,8 +179,6 @@ export class GBLLMOutputParser extends BaseLLMOutputParser<ExpectedOutput> {
|
|||
}
|
||||
|
||||
export class ChatServices {
|
||||
|
||||
|
||||
private static async getRelevantContext(
|
||||
vectorStore: HNSWLib,
|
||||
sanitizedQuestion: string,
|
||||
|
@ -219,16 +217,14 @@ export class ChatServices {
|
|||
page = await ChatServices.findPageForText(metadata.source, doc.pageContent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
output = `${output}\n\n\n\nUse also the following context which is coming from Source Document: ${filename} at page: ${page ? page : 'entire document'
|
||||
output = `${output}\n\n\n\nUse also the following context which is coming from Source Document: ${filename} at page: ${
|
||||
page ? page : 'entire document'
|
||||
}
|
||||
(you will fill the JSON sources collection field later),
|
||||
Use other page if this block is an index or table of contents (TOC).
|
||||
And memorize this block (if it is not an Index or TOC) among document
|
||||
information and return when you
|
||||
are refering this part of content:\n\n\n\n ${doc.pageContent
|
||||
} \n\n\n\n.`;
|
||||
are refering this part of content:\n\n\n\n ${doc.pageContent} \n\n\n\n.`;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
@ -266,24 +262,36 @@ export class ChatServices {
|
|||
public static usersMode = {};
|
||||
|
||||
private static async getModel(min: GBMinInstance) {
|
||||
const provider = await (min.core as any)['getParam'](
|
||||
min.instance,
|
||||
'LLM Provider',
|
||||
null,
|
||||
'openai'
|
||||
);
|
||||
const provider = await (min.core as any)['getParam'](min.instance, 'LLM Provider', null, 'openai');
|
||||
let model;
|
||||
if (provider === 'claude') {
|
||||
model = new ChatAnthropic({
|
||||
model: "claude-3-haiku-20240307",
|
||||
model: 'claude-3-haiku-20240307',
|
||||
temperature: 0,
|
||||
maxTokens: undefined,
|
||||
maxRetries: 2,
|
||||
maxRetries: 2
|
||||
});
|
||||
} else if (process.env.AI_MODE === 'local') {
|
||||
const azureOpenAIKey = process.env.AZURE_OPEN_AI_KEY;
|
||||
const azureOpenAILLMModel = process.env.AZURE_OPEN_AI_LLM_MODEL;
|
||||
const azureOpenAIVersion = process.env.AZURE_OPEN_AI_VERSION;
|
||||
const azureOpenAIApiInstanceName = process.env.AZURE_OPEN_AI_INSTANCE;
|
||||
const azureOpenAIEndPoint = process.env.AZURE_OPEN_AI_ENDPOINT;
|
||||
|
||||
model = new ChatOpenAI({
|
||||
model: process.env.LOCAL_LLM_MODEL,
|
||||
apiKey: 'empty',
|
||||
azureOpenAIApiDeploymentName: 'v1',
|
||||
azureOpenAIApiInstanceName: 'v1',
|
||||
azureOpenAIApiKey: 'empty',
|
||||
azureOpenAIApiVersion: 'empty',
|
||||
azureOpenAIBasePath: process.env.LOCAL_LLM_ENDPOINT,
|
||||
openAIApiKey: 'empty',
|
||||
configuration: {
|
||||
baseURL: process.env.LOCAL_LLM_ENDPOINT
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
|
||||
|
||||
const azureOpenAIKey = process.env.AZURE_OPEN_AI_KEY;
|
||||
const azureOpenAILLMModel = process.env.AZURE_OPEN_AI_LLM_MODEL;
|
||||
const azureOpenAIVersion = process.env.AZURE_OPEN_AI_VERSION;
|
||||
|
@ -296,7 +304,7 @@ export class ChatServices {
|
|||
azureOpenAIApiDeploymentName: azureOpenAILLMModel,
|
||||
azureOpenAIApiVersion: azureOpenAIVersion,
|
||||
azureOpenAIBasePath: azureOpenAIEndPoint,
|
||||
temperature: 0,
|
||||
temperature: 0
|
||||
});
|
||||
}
|
||||
return model;
|
||||
|
@ -313,7 +321,6 @@ export class ChatServices {
|
|||
|
||||
const LLMMode = (mode ?? answerMode).toLowerCase();
|
||||
|
||||
|
||||
let memory;
|
||||
if (user && !this.memoryMap[user.userSystemId]) {
|
||||
memory = new BufferWindowMemory({
|
||||
|
@ -406,7 +413,6 @@ export class ChatServices {
|
|||
Folowing answer:`)
|
||||
] as any);
|
||||
|
||||
|
||||
const jsonInformation = `
|
||||
RESPONSE FORMAT: Return only a single valid JSON object with no surrounding text. Structure:
|
||||
{{"text": "Complete response as a single string, using \\n for all line breaks, \n1. bullets and; \n2.lists.", "sources": [{{"file": "filename", "page": number}}]}}
|
||||
|
@ -431,7 +437,6 @@ export class ChatServices {
|
|||
|
||||
ERROR IF: Line breaks in JSON, text outside JSON, invalid format`;
|
||||
|
||||
|
||||
const combineDocumentsPrompt = ChatPromptTemplate.fromMessages([
|
||||
AIMessagePromptTemplate.fromTemplate(
|
||||
`
|
||||
|
@ -496,10 +501,10 @@ export class ChatServices {
|
|||
const { chat_history } = await memory.loadMemoryVariables({});
|
||||
return chat_history;
|
||||
},
|
||||
context: (async (output: string) => {
|
||||
context: async (output: string) => {
|
||||
const c = await ChatServices.getRelevantContext(min['vectorStore'], output);
|
||||
return `${systemPrompt} \n ${c ? 'Use this context to answer:\n' + c : 'answer just with user question.'}`;
|
||||
})
|
||||
}
|
||||
},
|
||||
combineDocumentsPrompt,
|
||||
model,
|
||||
|
@ -516,7 +521,12 @@ export class ChatServices {
|
|||
},
|
||||
questionGeneratorTemplate,
|
||||
modelWithTools,
|
||||
new GBLLMOutputParser(min, user, callToolChain, min['vectorStore']?.docstore?._docs.length > 0 ? combineDocumentsChain : null),
|
||||
new GBLLMOutputParser(
|
||||
min,
|
||||
user,
|
||||
callToolChain,
|
||||
min['vectorStore']?.docstore?._docs.length > 0 ? combineDocumentsChain : null
|
||||
),
|
||||
new StringOutputParser()
|
||||
] as any);
|
||||
|
||||
|
@ -544,15 +554,13 @@ export class ChatServices {
|
|||
let tables = con['storageTables'];
|
||||
tables = tables ? tables.split(';') : null;
|
||||
|
||||
const answerSource = await (min.core as any)['getParam'](min.instance,
|
||||
'Answer Source', 'server');
|
||||
const answerSource = await (min.core as any)['getParam'](min.instance, 'Answer Source', 'server');
|
||||
|
||||
GBLogEx.info(min, `Answer Source = ${answerSource}.`);
|
||||
|
||||
let dataSource;
|
||||
if (answerSource === 'cache') {
|
||||
let sqliteFilePath =
|
||||
path.join('work', GBUtil.getGBAIPath(min.botId), `${con['name']}.sqlite`);
|
||||
let sqliteFilePath = path.join('work', GBUtil.getGBAIPath(min.botId), `${con['name']}.sqlite`);
|
||||
GBLogEx.info(min, `Using data from cache: Path.basename(${sqliteFilePath}).`);
|
||||
|
||||
dataSource = new DataSource({
|
||||
|
@ -562,8 +570,6 @@ export class ChatServices {
|
|||
logging: true
|
||||
});
|
||||
} else {
|
||||
|
||||
|
||||
if (dialect === 'sqlite') {
|
||||
const storageFile = con['storageFile'];
|
||||
dataSource = new DataSource({
|
||||
|
@ -572,9 +578,7 @@ export class ChatServices {
|
|||
synchronize: false,
|
||||
logging: true
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const host = con['storageServer'];
|
||||
const port = con['storagePort'];
|
||||
const storageName = con['storageName'];
|
||||
|
@ -624,7 +628,7 @@ export class ChatServices {
|
|||
schema: async () => db.getTableInfo(tables),
|
||||
question: (input: { question: string }) => input.question,
|
||||
top_k: () => 10,
|
||||
table_info: () => 'any',
|
||||
table_info: () => 'any'
|
||||
},
|
||||
prompt,
|
||||
model,
|
||||
|
@ -672,8 +676,7 @@ export class ChatServices {
|
|||
table_names_to_use: () => tables
|
||||
},
|
||||
{
|
||||
result: finalResponsePrompt.pipe(model).pipe(
|
||||
new StringOutputParser() as any),
|
||||
result: finalResponsePrompt.pipe(model).pipe(new StringOutputParser() as any),
|
||||
|
||||
// Pipe the query through here unchanged so it gets logged alongside the result.
|
||||
sql: previousStepResult => previousStepResult.query
|
||||
|
@ -752,7 +755,7 @@ export class ChatServices {
|
|||
const packagePath = GBUtil.getGBAIPath(min.botId, 'gbdialog', null);
|
||||
const jsonFile = path.join('work', packagePath, `${script}.json`);
|
||||
|
||||
if (await GBUtil.exists(jsonFile) && script.toLowerCase() !== 'start.vbs') {
|
||||
if ((await GBUtil.exists(jsonFile)) && script.toLowerCase() !== 'start.vbs') {
|
||||
const funcJSON = JSON.parse(await fs.readFile(jsonFile, 'utf8'));
|
||||
const funcObj = funcJSON?.function;
|
||||
|
||||
|
|
1
templates/ai-search.gbai/ai-search.gbkb/docs/README.md
Normal file
1
templates/ai-search.gbai/ai-search.gbkb/docs/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
O céu é azul-lilás e pronto.
|
40
templates/marketing.gbai/marketing.gbdialog/broadcast.bas
Normal file
40
templates/marketing.gbai/marketing.gbdialog/broadcast.bas
Normal file
|
@ -0,0 +1,40 @@
|
|||
TALK "For favor, digite a mensagem que deseja enviar:"
|
||||
HEAR message
|
||||
|
||||
TALK "Analizando template ... (antes de mandar para a META)"
|
||||
report = LLM "Esta mensagem vai ser aprovada pelo WhatsApp META como Template? Tem recomendação? Se estiver OK, responda o texto: OK. Do contrário, avalie o que deve ser feito."
|
||||
|
||||
IF report <> "OK" THEN
|
||||
TALK "A mensagem não será aprovada pela Meta. " + report
|
||||
END IF
|
||||
|
||||
TALK "Envie agora o arquivo de imagem de cabefalho:"
|
||||
HEAR plan AS FILE
|
||||
|
||||
TALK "É para um arquivo ou todos?"
|
||||
HEAR in AS FILE
|
||||
|
||||
PUBLISH
|
||||
|
||||
IF in.isValid THEN
|
||||
list = FIND in.filename, "Perfil=" + grupos
|
||||
ELSE
|
||||
list = GET "broadcast"
|
||||
END IF
|
||||
|
||||
SET MAX LINES 2020
|
||||
|
||||
index = 1
|
||||
|
||||
DO WHILE index < UBOUND(list)
|
||||
row = list[index]
|
||||
|
||||
SEND TEMPLATE TO row.telefone. filename
|
||||
|
||||
WAIT 0.1
|
||||
|
||||
index = index + 1
|
||||
|
||||
LOOP
|
||||
|
||||
TALK "OK, o envio foi realizado. Para saber mais, digite /report."
|
Loading…
Add table
Reference in a new issue