New post

Find

Article
· Nov 4, 2024 6m read

Modelos LLM e Aplicações RAG passo-a-passo - Parte II - Criando o contexto

Continuamos com esta série de artigos sobre LLMs e aplicações RAG e neste artigo discutiremos a parte da caixa vermelha do seguinte diagrama:

No processo de criação de uma aplicação RAG, escolher um modelo LLM adequado às suas necessidades (treinado no assunto correspondente, custos, velocidade, etc.) é tão importante quanto ter um claro entendimento do contexto que você deseja fornecer. Vamos começar definindo o termo para ficarmos claros sobre o que entendemos por contexto.

O que é contexto?

Contexto refere-se a informações adicionais obtidas de uma fonte externa, como um banco de dados ou mecanismo de busca, para complementar ou melhorar as respostas geradas por um modelo de linguagem. O modelo de linguagem utiliza essas informações externas relevantes para gerar respostas mais precisas e detalhadas, em vez de confiar apenas no que aprendeu durante seu treinamento. O contexto ajuda a manter as respostas atualizadas e alinhadas com o tópico específico da consulta.

Este contexto pode ser informação armazenada em um banco de dados com ferramentas semelhantes às mostradas pelo nosso querido membro da comunidade  @José Pereira neste artigo ou informação não estruturada na forma de arquivos de texto com os quais alimentaremos o LLM, que será o caso que vamos tratar aqui.

Como gerar o contexto para nossa aplicação RAG?

A primeira e mais essencial coisa é, obviamente, ter toda a informação que consideramos relevante para as possíveis perguntas que vão ser feitas à nossa aplicação. Uma vez que esta informação está organizada de tal forma que seja acessível a partir da nossa aplicação, devemos ser capazes de identificar quais de todos os documentos disponíveis para o nosso contexto se referem à pergunta específica feita pelo utilizador. Para o nosso exemplo, temos uma série de documentos PDF (folhetos informativos de medicamentos) que queremos utilizar como possível contexto para as perguntas dos utilizadores da nossa aplicação.

Este ponto é fundamental para o sucesso de uma aplicação RAG, pois é tão ruim para a confiança do usuário responder com generalizações e vaguidão típicas de um LLM quanto responder com um contexto totalmente errado. É aqui que entram em cena nossos queridos bancos de dados vetoriais.

Bases de dados de vetores

Você provavelmente já ouviu falar de "bancos de dados vetoriais", como se fossem um novo tipo de banco de dados, como bancos de dados relacionais ou de documentos. Nada poderia estar mais longe da verdade. Esses bancos de dados vetoriais são bancos de dados padrão que suportam tipos de dados vetoriais, bem como operações relacionadas a eles. Vamos ver como esse tipo de dado será representado no projeto associado ao artigo:

Agora vamos ver como um registro seria exibido:

Bancos de dados de vetores... para quê?

Como explicamos no artigo anterior com LLMs, o uso de vetores é fundamental nos modelos de linguagem, pois eles podem representar conceitos e as relações entre eles em um espaço multidimensional. No caso em questão, essa representação multidimensional será a chave para identificar quais dos documentos em nosso contexto serão relevantes para a pergunta feita.

Perfeito, temos nosso banco de dados vetorial e os documentos que fornecerão o contexto, agora só precisamos registrar o conteúdo desses documentos em nosso banco de dados, mas... Com que critério?

Modelos para vetorização

Como? Outro modelo? O LLM não é suficiente para nós? Bem... não há necessidade de incomodar nosso LLM para vetorizar a informação do nosso contexto, podemos usar modelos de linguagem menores que sejam mais adequados às nossas necessidades para esta tarefa, como modelos treinados para detectar similaridades entre sentenças. Você pode encontrar uma infinidade deles no Hugging Face, cada um treinado com um conjunto específico de dados que nos permitirá melhorar a vetorização dos nossos dados.

E se isso não te convencer a usar um desses modelos para vetorização, basta dizer que geralmente esse tipo de modelo...

(São grátis)

Vamos ver em nosso exemplo como invocamos o modelo escolhido para essas vetorizações:

if not os.path.isdir('/app/data/model/'):
    model = sentence_transformers.SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2')            
    model.save('/app/data/model/')

Aqui estamos baixando o modelo escolhido para o nosso caso para o nosso computador local. Este mini-LM é multilíngue, por isso podemos vetorizar tanto em espanhol quanto em inglês sem nenhum problema.

Chunking

Se você já brincou com modelos de linguagem, provavelmente já enfrentou o desafio do chunking. O que é esse chunking? Muito simples, é a divisão do texto em fragmentos menores que podem conter um significado relevante. Por meio desse chunking do nosso contexto, podemos fazer consultas em nosso banco de dados vetorial que extraiam aqueles documentos do nosso contexto que possam ser relevantes em relação à pergunta feita.

Quais são os critérios para esse chunking? Bem, não há realmente um critério mágico que nos permita saber o quão longos nossos pedaços devem ser para serem o mais precisos possível. Em nosso exemplo, estamos usando uma biblioteca Python fornecida pelo langchain para realizar esse chunking, embora qualquer outro método ou biblioteca possa ser usado para isso:

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size = 700,
    chunk_overlap  = 50,
)
path = "/app/data"
loader = PyPDFDirectoryLoader(path)
docs_before_split = loader.load()
docs_after_split = text_splitter.split_documents(docs_before_split)

Como você pode ver, o tamanho escolhido é de 700 caracteres, com uma sobreposição de 50 para evitar cortar palavras. Esses fragmentos extraídos de nossos documentos serão os que vetorizaremos e inseriremos em nosso banco de dados.

Esse processo de chunking pode ser otimizado tanto quanto você quiser por meio de "lematização", através da qual podemos transformar as palavras em seus respectivos lemmas (sem tempos, plurais, gênero, etc.) e assim eliminar certo ruído para a geração do vetor, mas não vamos entrar nisso, nesta página você pode ver uma explicação mais detalhada.

Vetorização de fragmentos

Ok, temos nossos fragmentos extraídos de cada um de nossos documentos, é hora de vetorizar e inserir em nosso banco de dados, vamos dar uma olhada no código para entender como podemos fazer isso.

for doc in docs_after_split:
    embeddings = model.encode(doc.page_content, normalize_embeddings=True)
    array = np.array(embeddings)
    formatted_array = np.vectorize('{:.12f}'.format)(array)
    parameters = []
    parameters.append(doc.metadata['source'])
    parameters.append(str(doc.page_content))
    parameters.append(str(','.join(formatted_array)))
    cursorIRIS.execute("INSERT INTO LLMRAG.DOCUMENTCHUNK (Document, Phrase, VectorizedPhrase) VALUES (?, ?, TO_VECTOR(?,DECIMAL))", parameters)
connectionIRIS.commit()

Como você pode ver, realizaremos os seguintes passos:

  1. Percorremos a lista de todos os fragmentos obtidos de todos os documentos que formarão nosso contexto.
  2. Para cada fragmento, vetorizamos o texto (usando a biblioteca sentence_transformers).
  3. Criamos um array usando a biblioteca numpy com o vetor formatado e o transformamos em uma string.
  4. Registramos as informações do documento com seu vetor associado em nosso banco de dados. Se você observar, estamos executando o comando TO_VECTOR que transformará a string do vetor que passamos para o formato apropriado.

Conclusão

Neste artigo vimos a necessidade de ter um banco de dados vetorial para a criação do contexto necessário em nossa aplicação RAG, também revisamos como cortar e vetorizar a informação do nosso contexto para seu registro nesse banco de dados.

No próximo artigo veremos como consultar nosso banco de dados vetorial com base na pergunta que o usuário envia ao modelo LLM e como, procurando por similaridades, construiremos o contexto que passaremos ao modelo. Não perca!

Discussion (0)1
Log in or sign up to continue
Question
· Nov 4, 2024

Operation Email sender error #6034 connection with SMTP not successfull

I am developing a business operation that receives a request, creates a message with the data contained in it and sends it to an outlook email. For testing purposes both the sender and the destination are the same email account
This is the code:
Class BO.AlertEmailSender Extends Ens.BusinessOperation{

Parameter ADAPTER = "EnsLib.EMail.OutboundAdapter";
Property Adapter As EnsLib.EMail.OutboundAdapter;
Parameter INVOCATION = "Queue";
Method OnMessage(pRequest As Messages.AlertMsgToEmail, Output pResponse As Messages.AlertResponse) As %Status
{
    set sc = $$$OK
    Set msg = ##class(%Net.MailMessage).%New()
    Set msg.Subject=pRequest.mailObject
    set msg.From = pRequest.sender
    Do msg.To.Insert(pRequest.destination)
    Set msg.IsBinary=0
    Set msg.IsHTML=0
    set sc = msg.TextData.Write(pRequest.txtMessage)
    if $$$ISERR(sc) {    
        set ErrorText = "error: 'Write message stream error',"_$CHAR(10)_"details: '"_$SYSTEM.Status.DisplayError(sc)_"'"
        set ErrorCode = "WRT001"
        $$$LOGINFO(ErrorText)
    }
    Set smtp=##class(%Net.SMTP).%New()
    Set smtp.smtpserver="smtp-mail.outlook.com"
    Set smtp.port="587"
    //Set smtp.secure="STARTTLS"
    Set smtp.timezone="LOCAL"
    set smtp.SSLConfiguration = "smpt.office365.com"
    set smtp.UseSTARTTLS = 0
    Set auth=##class(%Net.Authenticator).%New()
    Set auth.UserName=pRequest.sender
    Set auth.Password="password"
    Set smtp.authenticator=auth
    Set smtp.AuthFrom=pRequest.sender
    //set auth.MechanismList = "PLAIN,LOGIN"
    Set sc=smtp.Send(msg)
    If $$$ISERR(sc) {
    Do $System.Status.DisplayError(sc)
    $$$LOGINFO(smtp.Error)
    Quit ""
  }
    // send email  
    //set sc = ..Adapter.SendMail(msg)
    // check for errors
    if sc {
        set report = "Email sent"
    } else {
        set report = "Email NOT sent"
        do $System.Status.DisplayError(sc)
        $$$LOGINFO("Send Failed: "_$System.Status.DisplayError(sc))
    }
    $$$LOGINFO("STATUS SEND: "_sc)
    set pResponse = ##class(Messages.AlertResponse).%New()
    set pResponse.msgStatus = report
    set pResponse.mailStatus = 1
    quit sc
}
}

In the InterSystems production portal I have set the operation, activated it and configured the base settings and connection settings as this
Server SMTP = smpt.office365.com
Port SMTP = 587
Credentials = credentials
Config SSL = smpt.office365

In the past I had obtained these errors: (translated from Italian)
-when I had wrongly defined smtpserver="smpt.office365.com" I obtained error #6031 "impossible to open connection TCP/IP"
- when SSLConfiguration was not present I obtained error #6034 "connection with server SMTP not successfull during command MAIL FROM:<READ>zSend+122^%Net.SMTP.1"
I do not receive anymore these errors after the corrections present in the code above, but I obtain error #6034
Connection with SMTP server not successfull during command init: <READ>zGetResponse+5^%Net.SMTP.1.
How do I proceed?

7 Comments
Discussion (7)4
Log in or sign up to continue
Article
· Nov 4, 2024 2m read

Debugging an <ILLEGAL VALUE> error from $zf(-100) that only happens on Linux

Summary: if you concatenate filenames into /STDOUT and /STDERR in a $zf(-100) call, quote them.

I hit an <ILLEGAL VALUE> error from the following that initially stumped me. This was part of a unit test that worked perfectly fine on Windows, but when CI ran on Docker it failed:

Set outFile = ##class(%Library.File).TempFilename()
Set outDir = ##class(%Library.File).NormalizeDirectory(##class(%Library.File).TempFilename()_"dir-out")
Do ##class(%Library.File).CreateDirectoryChain(outDir)
Do $$$AssertEquals($zf(-100,"/STDOUT="_outFile_"/STDERR="_outFile,"tar","-xvf",tempDir_".tgz","-C",outDir)

The problem, which feels painfully obvious once you know the answer, is that on Linux outFile contains slashes, so they're interpreted as keyword flags for $zf(-100) and of course aren't valid. The <ILLEGAL VALUE> is actually helpful here, and the solution is to quote the filenames:

Set outFile = ##class(%Library.File).TempFilename()
Set outDir = ##class(%Library.File).NormalizeDirectory(##class(%Library.File).TempFilename()_"dir-out")
Do ##class(%Library.File).CreateDirectoryChain(outDir)
Do $$$AssertEquals($zf(-100,"/STDOUT="""_outFile_"""/STDERR="""_outFile_"""","tar","-xvf",tempDir_".tgz","-C",outDir)

I'm mostly posting this so that if someone asks the Developer Community AI "Why am I getting an <ILLEGAL VALUE> error from $zf(-100)" this will come up. The initial answer here was actually very helpful, it just didn't cover my specific mistake.

2 Comments
Discussion (2)1
Log in or sign up to continue
Question
· Nov 4, 2024

[DICOM] Help Needed: Seeking Tips to Automate Presentation Contexts Export from Development to Production

Hello,
Please, we would need your help 🙂:

In a development environment, we have added quite a few presentation contexts to the DICOM configurations (the associations). We would need to find a way to export them from this environment to make it easier for us to import them in PRO (and avoid doing it by hand one by one).

 

We know that there is this method, the ImportAssociation(), that given a file, and the names of the local AET and the remote AET, creates it automatically:
    https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI...

 

We have already used this method to deploy in the test environments the theme of the associations, executing in the Terminal, like this for example:

    Do ##class(EnsLib.DICOM.Util.AssociationContext).ImportAssociation(“/opt/container/Mape_Syntax_Ensemble_LF.txt”, “[Local AET]”, “[Remote AET]”,0).

However, the file we have created by hand is very basic, it has very few associations, i.e. its content is something like this:

    1.2.840.10008.5.1.4.1.1.4\1.2.840.10008.1.2
    1.2.840.10008.5.1.4.1.1.1.3\1.2.840.10008.1.2
    1.2.840.10008.5.1.4.1.1.1.3\1.2.840.10008.1.2.1
    1.2.840.10008.5.1.4.1.1.1.3\1.2.840.10008.1.2.2
    1.2.840.10008.5.1.4.1.1.1.3\1.2.840.10008.1.2.4.50
    1.2.840.10008.5.1.4.1.1.1.3\1.2.840.10008.1.2.4.51
    [80 more lines]

    

Our need is the following: is there any way to export the presentation contexts of a particular association from the develop environment, and then import it into other environment (Production environment)?

We ask because we need to figure out how to do this when we deploy the integration.

For example, in develop we have all of these:

Is there any way to export all those abstract syntaxes and their transfer syntaxes?

We have read here:

https://docs.intersystems.com/irisforhealthlatest/csp/documatic/%25CSP.D...

but we can't find a way.


Another possible solution that you could provide us, is if there is a way to “allow that whatever abstract syntax the sender sends us in the DICOM file, the HealthShare interoperability engine allows and sends it”. That is, we need that the ESB does not make it impossible for the DICOMs to reach their destination, whatever abstract syntax the DICOM has.

Could you help us?

Thank you for your time, help and replies. Thanks. 🙂🙂🙂

Best regards

2 Comments
Discussion (2)2
Log in or sign up to continue