As aplicações de inteligência artificial desenvolvidas em Rust estão gradualmente passando dos experimentos iniciais para modelos mais práticos, de acordo com uma apresentação ao vivo organizada pela JetBrains em colaboração com a Rust Foundation. A primeira sessão concentrou-se na biblioteca Rig de código aberto e em como usá-la para criar aplicações baseadas em grandes modelos de linguagem e agentes capazes de chamar ferramentas e executar tarefas dentro da aplicação.
Orhun Parmaksız, Developer Advocate, participou de uma demonstração de um pequeno agente de programação que criou usando Rig e Ratatui, enquanto Stephen Korzeniewski, principal mantenedor do Rig na 0xPlaygrounds, explicou a arquitetura interna do projeto e as escolhas por trás de suas interfaces de programação.
Uma interface unificada para provedores de modelos
O Rig aborda um problema prático comum em aplicações de modelos de linguagem: OpenAI, Anthropic, Gemini e outros têm interfaces de programação diferentes, mesmo quando alguns serviços afirmam ser compatíveis com a interface da OpenAI. Em vez de vincular diretamente partes da aplicação à interface de um único provedor, o Rig oferece uma interface Rust unificada, por meio da qual é possível trocar de provedor sem reescrever as partes relacionadas ao modelo.
A biblioteca organiza a aplicação em torno de vários componentes básicos, incluindo o cliente do provedor, o modelo de conclusão, o agente e as ferramentas disponíveis para ele. O agente funciona como uma camada superior à solicitação direta ao modelo; ele adiciona as instruções, os limites de tokens e os recursos necessários para executar tarefas que vão além do fluxo de texto de entrada e texto de saída.
As instruções do agente são passadas no que o Rig chama de preamble, instruções que desempenham o papel de uma solicitação de sistema e são inseridas no início de cada solicitação. Já as comunicações de rede com os provedores de modelos são gerenciadas por meio de interfaces assíncronas, mantendo muitos detalhes do trabalho assíncrono dentro da biblioteca para que o código da aplicação se concentre na configuração e no comportamento desejados.
As ferramentas tornam o agente parte da aplicação
O Rig define uma ferramenta por meio da interface Tool trait do Rust. A definição da ferramenta inclui seu nome, os tipos de entrada, saída e erro, além da função executada quando ela é chamada. Sua definição também descreve os argumentos esperados usando JSON Schema, para que o modelo receba uma descrição estruturada dos dados que pode fornecer.
Depois que a ferramenta é registrada no agente, um modelo compatível com chamadas de ferramentas pode determinar quando usá-la e integrar seu resultado à resposta. A função pode executar uma operação de cálculo, consultar um banco de dados, recuperar informações ou conectar-se a outro serviço. A ferramenta também pode receber um contexto que preserve estados como o número de chamadas ou os resultados armazenados em cache. A ideia se estende aos próprios agentes: um agente pode ser disponibilizado como ferramenta para outro agente, permitindo delegar tarefas entre eles.
Da demonstração experimental a uma aplicação testável
O projeto Rat Code reuniu esses componentes em um pequeno agente de programação executado no terminal usando Rig e Ratatui. O arquivo main.rs inicializa o cliente do provedor, o modelo e o agente, enquanto os recursos de leitura e gravação de arquivos e de execução de comandos de shell são definidos separadamente e depois registrados no agente por meio da interface de ferramentas do Rig.
A aplicação também usa a interface de streaming do Rig para processar a resposta em partes e atualizar a interface do terminal enquanto o modelo gera a resposta. A apresentação também abordou o sistema de hooks, o mecanismo de recuperação de chamadas de ferramentas e as escolhas de design por trás das abstrações oferecidas pela biblioteca. A demonstração do código completo começa na gravação aos 27:28.
RAG, modelos locais e testes
O Rig não se limita a provedores de modelos hospedados. Ele oferece suporte a aplicações de geração aumentada por recuperação, nas quais documentos e consultas dos usuários são transformados em embeddings vetoriais; em seguida, os documentos semanticamente mais próximos são pesquisados para serem adicionados ao contexto do modelo. A biblioteca fornece integrações com bancos de dados e abstrações para armazenamentos vetoriais, com a possibilidade de implementar uma interface de armazenamento vetorial ao usar um banco de dados que não seja diretamente compatível.
Também é possível executar modelos locais por meio do Ollama e do llama.cpp, ou usar a integração rig-candle para executar a inferência diretamente em uma aplicação Rust. Essa opção permite incluir os pesos do modelo na aplicação e executar modelos compatíveis via WebAssembly, sem depender de uma interface de modelo hospedada ou de um servidor de inferência local separado.
A importância dessas opções aparece no local onde os modelos e os dados são executados, mas elas também trazem o desafio de testar as integrações. O Rig depende principalmente de um sistema de gravação: os testes são executados com os provedores reais e o tráfego HTTP é salvo em arquivos YAML; depois, um servidor simulado reproduz as solicitações e respostas nos testes de integração contínua. O projeto reúne cerca de 1.700 interações registradas, que são reproduzidas a cada pull request em poucos segundos.
Esses testes verificam se a integração de software continua funcionando, mas não medem a qualidade das saídas do modelo, que pode mudar quando o provedor atualiza seu modelo. Por isso, a apresentação mencionou testes agendados em modelos ativos como uma abordagem separada para aplicações que dependem da qualidade da resposta.
Leitura editorial: o que importa para os desenvolvedores?
O valor prático do Rig não está apenas em adicionar um novo provedor, mas em separar a camada da aplicação dos detalhes das interfaces dos modelos, mantendo ferramentas, recuperação e inferência local em uma única arquitetura Rust. Isso pode reduzir o custo de trocar de provedor ou de método de execução, mas não elimina as diferenças comportamentais entre os modelos nem resolve, por si só, o problema de avaliar a qualidade das saídas. O mecanismo de testes do projeto mostra que garantir o funcionamento da conexão é uma coisa; certificar-se de que o modelo produz o resultado desejado é outra, que exige testes ativos e critérios de avaliação independentes.