[🦀🤖][Tutorial] Assistente virtual de terminal em Rust
Why?
Temos dois motivos:
O primeiro é que esse é parte dos meus estudo para implementação do bot para criação e correção de códigos maliciosos, dá uma olhada aqui
A segunda surgiu porque tenho TDAH e volta e meia acabo esquecendo alguns comandos específicos que preciso usar no terminal, além de precisar fazer algumas pesquisas, o que seria muito legal de ser feito sem precisar sair do terminal.
Um script em python seria muito fácil e eu gosto de um pouco de desafio, além de ver que algumas pessoas aqui já perguntaram e comentaram sobre Rust, que tal mostrar o poder de rust então para gerar o nosso assistente virtual?
Iniciando o projeto
Este tutorial leva em cosideração que você tenha um conhecimento mínimo de rust e do gerenciador de projetos cargo!
Precisamos iniciar um novo projeto para começarmos:
cargo new assistente_virtual --bin
Iniciamos um projeto de rust, assistente virtual é um nome qualquer, eu por exemplo escolhi o nome Robert para meu assistente virtual.
--bin
O --bin é para indicar que queremos um projeto binário, ou seja, um projeto que será executado no terminal.
Entre na pasta do projeto e abra o Cargo.toml, ele é o arquivo que contém as dependências do projeto, vamos adicionar as dependências que vamos usar:
[dependencies]
hyper = { version = "^0.14", features = ["full"]}
hyper-tls = "^0.5"
tokio = {version = "1", features = ["full"]}
serde = "^1"
serde_derive = "^1"
serde_json = "^1"
A primeira dependência é o hyper, que é um framework para construir servidores http, vamos usar ele para criar um servidor http que vai receber as requisições do nosso assistente virtual.
A segunda dependência é o hyper-tls, que é um wrapper para o hyper que vai nos permitir usar https.
A terceira dependência é o tokio, que é um framework para construir aplicações assíncronas, vamos usar ele para criar um servidor http assíncrono.
A quarta dependência é o serde, que é um framework para serialização e desserialização de dados, vamos usar ele para serializar e desserializar os dados que vamos receber e enviar.
Após adicionar todas as dependências vamos começar o desenvolvimento do nosso assistente virtual.
Abra o arquivo main.rs e vamos começar a escrever o código:
use hyper::body::Buf;
use hyper::{header, Body, Client, Request};
use hyper_tls::HttpsConnector;
use serde_derive::{Deserialize, Serialize};
use std::io::{stdin, stdout, Write};
Não entratei em muitos detalhes para não ser um tutorial maçante mas basicamente nós importamos todas as dependências que vamos usar.
O nosso projeto se baseia em usar o GPT-3 para nos gerar repostas para nossas perguntas, a API do GPT-3 é uma API REST, então vamos usar o hyper para criar um servidor http que vai receber as requisições e enviar as respostas.
Precisamos criar os structs que são responsáveis por serializar e desserializar os dados que vamos receber e enviar. A serialização é o processo de converter dados em uma forma que possa ser armazenada ou transmitida de uma maneira eficiente.
#[derive(Deserialize, Debug)]
struct OAIChoises {
text: String,
}
O struct OAIChoises é responsável por desserializar os dados que vamos receber da API do GPT-3, ele é responsável por desserializar o campo text que é o campo que contém a resposta que o GPT-3 gerou para a nossa pergunta.
#[derive(Deserialize, Debug)]
struct OAIResponse {
choices: Vec<OAIChoises>,
}
O struct OAIResponse é responsável por desserializar os dados que vamos receber da API do GPT-3, existem outros campos que são retornados na requisição mas não vamos usar eles, então optei por não usar pois o compilador iria ficar brigando com a gente por criar um campo que não usamos
#[derive(Serialize, Debug)]
struct OAIRequest {
prompt: String,
model: String,
max_tokens: u32,
temperature: u32,
}
O struct OAIRequest é responsável por serializar os dados que vamos enviar para a API do GPT-3, ele é responsável por serializar os campos prompt, model, max_tokens e temperature.
Que representam, respectivamente, a pergunta que vamos fazer, o modelo que vamos usar, a quantidade máxima de tokens que o GPT-3 vai gerar e a temperatura que o GPT-3 vai usar para gerar a resposta.
PS: Eu não cheguei a buscar mais informaçõe sobre essa tal "temperatura" mas acredito que seja algo relacionado a assertividade do texto.
As próximas partes serão um pouco mais complicadas, colocarei snippets do código e o código final para caso você se perca.
Vamos iniciar o nosso main
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}
Nosso método main será um método async, para isso usamos o tokio::main, que é um macro que transforma o método main em um método async.
let https = HttpsConnector::new();
let client = Client::builder().build(https);
let uri = "https://api.openai.com/v1/completions";
let preamble = "Eu sou um robô de pesquisa";
let oai_token: String = String::from("SEU-TOKEN-AQUI");
let auth_header_val = format!("Bearer {}", oai_token);
Criamos uma requisição Http e criamos os parâmetros que usaremos na construção da nossa requisição.
O parâmetro preamble
é o texto que vem antes da pergunta, ela será usada para que o GPT-3 entenda a melhor abordagem para responder a nossa pergunta, você pode alterar isso para criar um chatbot meio sarcástico, por exemplo.
oai_token
e auth_header_val
são o token da API do GPT-3 e o valor do header de autenticação que vamos usar na requisição.
println!("{esc}c", esc = 27 as char);
loop {
print!("> "); // Será usado para representar nosso input
stdout().flush().unwrap(); // Limpa o buffer do stdout
let mut user_text = String::new();
stdin().read_line(&mut user_text).expect("Failed to read line"); // Pega o input do usuário e coloca na variável user_text
println!("");
let oai_request = OAIRequest {
prompt: format!("{}{}", preamble, user_text),
model: String::from("text-curie-001"),
max_tokens: 60,
temperature: 0,
};
O parâmetro oai_request é o objeto que vamos usar para serializar os dados que vamos enviar para a API do GPT-3, observe as opções que foram selecionadas, o text-curie não é tão poderoo quanto o DaVinci mas é mais rápido e mais barato, então é uma boa opção para testes.
Caso queira brincar um pouco com o DaVinci altera o model
para text-davinci-002
, ele é realmente mais divertido porém consome bastante dos tokens, tome cuidado!
PS: Usando bastante por 1 dia eu obtive o uso total de 0.4 cents!
let body = Body::from(serde_json::to_vec(&oai_request)?);
let req = Request::post(uri)
.header(header::CONTENT_TYPE, "application/json")
.header("Authorization", &auth_header_val)
.body(body)
.unwrap();
let res = client.request(req).await?;
let body = hyper::body::aggregate(res).await?;
let json: OAIResponse = serde_json::from_reader(body.reader())?;
// Print the json
println!("🤖: {}", json.choices[0].text);
Essa parte é a mais complicada, mas vamos por partes.
O req
é a requisição em si, ele é construído a partir do objeto body e dos parâmetros que criamos anteriormente.
Res é a resposta da requisição, ela é construída a partir do objeto req
.
body
é o corpo da resposta, ele é construído a partir do objeto res.
json
é o objeto que vamos usar para desserializar os dados que recebemos da API do GPT-3, ele é construído a partir do objeto body.
E a partir do json nós imprimimos na tela a reposta do bot!
Código inteiro:
use hyper::body::Buf;
use hyper::{header, Body, Client, Request};
use hyper_tls::HttpsConnector;
use serde_derive::{Deserialize, Serialize};
use std::io::{stdin, stdout, Write};
#[derive(Deserialize, Debug)]
struct OAIChoises {
text: String,
}
#[derive(Deserialize, Debug)]
struct OAIResponse {
choices: Vec<OAIChoises>,
}
#[derive(Serialize, Debug)]
struct OAIRequest {
prompt: String,
model: String,
max_tokens: u32,
temperature: u32,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let https = HttpsConnector::new();
let client = Client::builder().build(https);
let uri = "https://api.openai.com/v1/completions";
let preamble = "Eu sou um robô de pesquisa";
let oai_token: String = String::from("SEU-TOKEN-AQUI");
let auth_header_val = format!("Bearer {}", oai_token);
println!("{esc}c", esc = 27 as char);
loop {
print!("> ");
stdout().flush().unwrap();
let mut user_text = String::new();
stdin().read_line(&mut user_text).expect("Failed to read line");
println!("");
let oai_request = OAIRequest {
prompt: format!("{}{}", preamble, user_text),
model: String::from("text-curie-001"),
max_tokens: 60,
temperature: 0,
};
let body = Body::from(serde_json::to_vec(&oai_request)?);
let req = Request::post(uri)
.header(header::CONTENT_TYPE, "application/json")
.header("Authorization", &auth_header_val)
.body(body)
.unwrap();
let res = client.request(req).await?;
let body = hyper::body::aggregate(res).await?;
let json: OAIResponse = serde_json::from_reader(body.reader())?;
// Print the json
println!("🤖: {}", json.choices[0].text);
}
}
Buildando e rodando
Para vermos o resultado no terminal basta rodar cargo run
no terminal.
> como faço para mudar de diretório no linux?
🤖: Para mudar de diretório no Linux, você precisa utilizar o comando cd (comando de diretório) para ir ao diretório que deseja mudar
> poderia me mostar um exemplo do comando ssh para me conectar a um servidor remoto?
🤖: ssh -L 8888@localhost remotepassword
Show de bola! Agora vamos gerar um binário para usarmos o bot quando precisarmos sem compilar o código toda vez.
cargo build --release
Após compilar observe que foi criada uma pasta chamada target
e dentro dela uma pasta chamada release
e dentro dela um arquivo chamado *robert_ai
.*Nesse caso vai ser o nome do seu projetinho.
Precisamos agora adicionar o arquivo ao PATH!
sudo mv target/release/robert_ai /usr/local/bin
source ~/.bashrc *
ource ~/.zshrc *
O comando source
é para atualizar o PATH do seu terminal, caso você esteja usando o bash ou o zsh, use de acordo com o seu terminal.
Após isso você pode rodar o comando robert_ai
e ver o bot funcionar! Maravillha o/
Conclusão
Esse foi um tutorial bem simples, mas que mostra como é fácil criar um bot de pesquisa usando a API do GPT-3. O código fonte do projeto não etá disponível no Github ainda porque sou preguiçoso.
O tutorial está propositalmente incompleto, pois a ideia é que vocês pesquisem e aprendam mais sobre o assunto. Alguns desafios para vocês:
- Colocar a pergunta como parâmetro para o programa Ex:
robert_ai "como faço para mudar de diretório no linux?"
- Adicionar cores no terminal
- Testar outros modelos disponíveis
- Criar um bot que responde perguntas de forma mais inteligente
- Criar um bot que não seja de pesquisa
PS:
Fui brincar um pouco de madrugada e se liga nessa resposta:
./robert_ai
> oi?
🤖: Não, sou um humano.
Eu gelei e fui dormir 😂