Entre Tokens e RAGs: Desafios na Construção de um Assistente de Código
Gerenciamento eficiente de tokens e a integração com RAG
Me siga no X | Me siga no LinkedIn | Apoie a Newsletter | Solicite uma consultoria
Grandes empresas de tecnologia entraram em uma corrida para criar seus próprios assistentes de código alimentados por IA. O GitHub CoPilot estreou em 2021, seguido pelos lançamentos do ChatGPT e do Amazon CodeWhisperer em 2022. No início de 2023, a Replit introduziu o GhostWriter, e no início do outono do mesmo ano, o IBM Watsonx Code Assistant e o Google Project IDX foram lançados.
No entanto, construir um assistente de código alimentado por IA está longe de ser simples. Para superar essas barreiras, abordagens que não necessitam de extenso treinamento ou ajustes finos foram introduzidas. Uma dessas abordagens, conhecidas como RAG, combina técnicas baseadas em recuperação para identificar dados representativos e enriquecer o prompt do LLM. Estas técnicas baseadas em recuperação muitas vezes aprimoram as respostas geradas.
No entanto, há poucos relatos sobre produtos reais que descrevem os desafios da utilização de métodos baseados em RAG. Consequentemente, ainda é incerto, por exemplo, se o desenvolvimento de aplicações baseadas em LLM introduz desafios técnicos distintos para equipes de desenvolvimento de software.
Esse texto de revisa o artigo “Lessons from Building CodeBuddy: A Contextualized AI Coding Assistant”, que fornece uma detalhada revisão dos principais desafios ao se construir um assistente de código baseado em LLMs.
CodeBuddy
O CodeBuddy é um assistente de codificação com IA desenvolvido pela Zup Innovation, uma grande empresa de tecnologia brasileira. Aqui fornecemos uma breve visão do CodeBuddy como produto.
CodeBuddy como ChatBot
A interação entre desenvolvedores e o CodeBuddy se dá por meio de plugins disponíveis para o VS Code e o IntelliJ.
Desenvolvedores utilizam o CodeBuddy através de um campo de entrada e responde às consultas aos desenvolvedores no mesmo lado. Os desenvolvedores podem fazer várias perguntas, uma de cada vez. Ao receber uma resposta, o CodeBuddy também explica a lógica por trás do código gerado. O CodeBuddy, tenta localizar documentos representativos, chamados de “fontes de conhecimento”, enriquecendo o prompt do usuário.
Os usuários podem visualizar a fonte de conhecimento selecionada na interface do plugin, aumentando a transparência e o seu entendimento. Se o desenvolvedor achar que o trecho de código proposto é preciso, pode aceitar a sugestão usando itens de atalho no IDE, permitindo que o CodeBuddy insira o código gerado em um arquivo existente.
Componentes Principais do CodeBuddy
O CodeBuddy, construído utilizando o modelo GPT da OpenAI como base, possui quatro componentes principais:
O Componente de Fonte de Conhecimento, que lida com fontes de dados diversas;
O Componente RAG, que combina as forças dos componentes de recuperação e geração;
O Componente GPT, que implementa o motor de geração de respostas do agente
O Componente de Loop de Feedback, que coleta interações e feedback dos usuários.
O CodeBuddy foi projetado pensando na escalabilidade, com componentes contêinerizados para fácil implantação e gerenciamento. Como solução proprietária, seu código fonte é privado. No entanto, usuários interessados podem fazer download da ferramenta através do site oficial do produto.
Lições aprendidas
Através de perguntas feitas para os desenvolvedores do produto, o artigo coletou e curou uma série de desafios e lições aprendidas na construção do CodeBuddy. Nesse texto, apresentando algumas das mais representativas. Para a listagem completa, confira o artigo na integra.
Gerenciamento Cuidadoso de Tokens
É importante gerenciar efetivamente as limitações de tokens em respostas geradas por LLMs devido às restrições inerentes com as quais os LLMs operam.
Notavelmente, o número total de tokens utilizados em uma solicitação engloba tanto o prompt do usuário quanto a resposta subsequente do LLM, calculado como total_tokens = tokens(prompt) + tokens(LLM_response)
. Antecipar a contagem exata de tokens com antecedência é desafiador, pois definir um limite estrito para os tokens de resposta do LLM é inviável. Mesmo instruindo o LLM a fornecer uma resposta dentro de um limite específico de palavras não garante resultados determinísticos. Além disso, o tamanho do prompt expande naturalmente à medida que mantemos um histórico das últimas mensagens.
Pode-se imaginar que novos LLMs com janelas maiores de tokens poderiam simplificar essa questão. No entanto, isso nem sempre é o caso com a arquitetura Transformer, porque quanto mais longo o contexto, maior a probabilidade do LLM gerar respostas incorretas.
Além disso, cada linguagem de programação tem elementos de sintaxe diferentes que podem complicar o gerenciamento de tokens. Em linguagens como Python, o espaço em branco é parte da sintaxe da linguagem. No entanto, cada caractere de espaço em branco também conta como um token. Consequentemente, estratégias de otimização aplicáveis em algumas linguagens, como eliminar todos os espaços em branco, não podem ser aplicadas universalmente em todas as linguagens de programação. As nuances únicas de sintaxe e tokenização de cada linguagem exigem estratégias sob medida, garantindo consideração cuidadosa antes de implementar otimizações.
Dividir Fontes de Conhecimento em Partes
Uma Fonte de Conhecimento pode assumir várias formas, desde código-fonte até catálogos de API e itens personalizados escritos em linguagem natural. No entanto, devido às limitações no tamanho do componente gerativo (já detalhado acima), não é viável incluir todas as fontes de conhecimento disponíveis. Por exemplo, se utilizarmos o modelo gpt-3.5-4k
, estamos limitados a 4k tokens.
Diversos desafios foram entrados para dividir os dados de forma eficaz enquanto ainda recuperava informações pertinentes. Esta tarefa se mostrou intrincada devido à natureza diversa das potenciais fontes de conhecimento. Embora existam técnicas estabelecidas para dividir dados textuais (como divisão por parágrafo ou abordagens baseadas em frases), existem menos opções estabelecidas para APIs ou trechos de código.
Isso acontece devido à variedade que se pode dividir; no caso de trechos de código, pode-se dividir dados com base em linhas de código, funções fechadas, classes, arquivos ou subárvores da Árvore de Sintaxe Abstrata (AST) encapsuladas. Cada abordagem apresenta vantagens e desvantagens potenciais, adicionando complexidade ao processo de tomada de decisão.
Buscar Fontes de Conhecimento Representativas
Dividir e armazenar fontes de conhecimento constitui apenas uma faceta do desafio; recuperar o pedaço de conhecimento mais pertinente com base nos prompts do usuário apresenta outro.
A complexidade dessas tarefas surge da necessidade do CodeBuddy localizar fontes de conhecimento, em vários formatos, relacionadas a consultas de usuários escritas em linguagem natural. Diversas abordagens de busca semântica foram exploradas, incluindo distância euclidiana, distância de cosseno e a max inner product.
Outra abordagem explorada foi transformar fontes de conhecimento armazenadas em linguagem não natural em um formato de linguagem natural. Embora os resultados observados não tenham mostrado uma diferença discernível na qualidade da resposta, a abordagem permitiu uma redução na contagem total de tokens. Esta redução foi possível devido à eliminação de elementos como colchetes, parênteses ou espaços em branco, comuns quando se utiliza trechos de código como fonte de conhecimento.
Garantir Boas Respostas
Desenvolver uma metodologia que gere consistentemente respostas de alta qualidade a partir de um LLM é um desafio contínuo. Note que isso é mais complexo do que a engenharia de prompts, que se concentra no processo de estruturar palavras que podem ser interpretadas e compreendidas pelo modelo de IA.
Criar respostas que se alinhem com as consultas, contextos e intenções dos usuários requer um equilíbrio delicado de requinte linguístico e precisão técnica. Esta abordagem multifacetada envolve experimentação contínua e um entendimento profundo da linguagem natural. O objetivo é aprimorar não apenas a geração, mas também a naturalidade e relevância das respostas geradas, garantindo uma experiência de usuário mais fluida e intuitiva.
Conclusão
Nesse texto, apresentamos alguns dos desafios de construção do CodeBuddy, agente de código desenvolvido pela Zup Innovation.
Alguns desses desafios refletem o trabalho enfrentado nesse campo, como gerenciamento eficiente de tokens e a integração de técnicas de Geração Aumentada por Recuperação (RAG). Estes desafios sublinham a dificuldade de alinhar respostas de IA com as necessidades específicas dos usuários, mantendo precisão técnica e naturalidade linguística.
Este campo emergente promete transformar a interação com a programação, mas ainda está em evolução, buscando melhores práticas para otimizar a eficácia dessas ferramentas.