Gabriela, o meu clone do SimSimi

Publicado às 25/03/2018 11:07

This content is not available in your language... So if you don't understand the language... well, at least you can appreciate the pictures of the post, right?

Gabriela, a amiga da Loritta

Lembra da história que o SimSimi estava me ameaçando de atividade ilegal e que eu deveria parar de fazer engenheria reversa da API deles? É, bons tempos...

Infelizmente as pessoas não estão gostando muito do +cleverbot da Loritta, já que ele é muito... "seco", já que ele normalmente não tem muitas respostas em português e ele não tem muitas coisas engraçadas, comparadas com o nosso amiguinho SimSimi.

Como eu não posso usar a API "não pública" deles (porque eu ainda estou bloqueado (mesmo que seja possível burlar o bloqueio apenas trocando o nome do usuário usando o SimSimi... mas mesmo assim eu teria que usar vários proxies só para ficar burlando o bloqueio)) eu irei tentar criar o meu próprio clone do SimSimi. Não querem que eu use o SimSimi? Okay então, EU MESMO irei criar o meu próprio SimSimi!

Mas, será que é fácil criar o meu próprio clone do SimSimi?

Como o SimSimi funciona?

Antes de começar a fazer o meu lindo clone do SimSimi, eu preciso descobrir como o SimSimi funciona para ter uma ideia de como montar o meu clone, então vamos analisar alguns pontos importantes sobre o SimSimi.

SimSimi NÃO É uma inteligência artificial

Você pode estar surpreso com isto, como se toda a sua vida fosse uma mentira... mas sim, ele não é uma inteligência artificial, o SimSimi aprende a responder as frases com as coisas que as pessoas ensinam para ele.

SimSimi NÃO se importa com o CaSiNG da pergunta

Perguntar Nilce ou nilce vai dar as mesmas respostas.

O SimSimi não lembra o contexto de conversas passadas

  • SimSimi, qual é a sua comida favorita?
  • Bolo!
  • SimSimi, qual é a sua comida favorita?
  • Arroz!
  • Mas não era arroz até cinco segundos atrás?
  • De quem você tá falando seu %!@!@#$ do !%@¨#%¨@# vem X1 no Minecraft seu safado.
  • ...que?
  • Olá amigo, como vai você?

O SimSimi não responde exatamente o que você deseja

Pelo aplicativo do SimSimi para o celular você consegue ver quem ensinou cada frase para ele, se você perguntar Você conhece o Pyrope do Undertale ele irá responder Sim, mesmo que na verdade a resposta que ensinaram para ele era Você conhece Undertale?!

Outro exemplo é quando você pergunta Você conhece a Nilce do Leon?, que faz ele responder a pergunta Você conhece a Nilce e o Leon?

Um dos jeitos de nós fazermos isto seria utilizando o levenshtein distance, mas nós não podemos usar isto no MongoDB sem ter que carregar todas as respostas na memória do bot (que irá dar problemas caso o chat bot conheça várias respostas) ou executando usando JavaScript no MongoDB (que mesmo assim é mais lerdo que uma query normal)

Ou seja, o SimSimi te lubridiou durante todos esses anos!

SimSimi altera erros ortográficos

Se você perguntar vc gosta do leon é a mesma coisa que perguntar você gosta do leon

Chega de aprender, hora de colocar a mão na massa! ...ou no código, eu acho

Começando a criar o meu próprio chat bot

Após escrever tudo que o nosso chat bot precisa, é hora de começar a desenvolver ele! Como o meu chat bot será para a Loritta, eu irei usar Kotlin + MongoDB nele.

Enquanto o chat bot será para a Loritta, eu irei primeiro criar em Kotlin para depois migrar para o Discord, para que seja mais fácil testar e modificar o chat bot antes de colocar para o público.

package com.mrpowergamerbr.loritta.utils.gabriela

object Gabriela {
	@JvmStatic
	fun main(args: Array<String>) {
		println("Olá mundo!")
	}
}

Tá pronto gente! Meu chat bot já responde Olá mundo!, vamos embora galerinha, dê joinha neste vídeo, favorite este vídeo, se inscreva-se no canal se você é um powerzinho de verdade, compre minhas camisetas e venha ao meu show em São Paulo! Não mora em São Paulo? Se fudeu seu trouxa.

...okay, vamos agora tentar implementar tudo que o SimSimi possui, ou pelo ou menos o básico dele.

package com.mrpowergamerbr.loritta.utils.gabriela

import com.mongodb.MongoClient
import com.mongodb.MongoClientOptions
import com.mongodb.client.model.Filters
import com.mrpowergamerbr.loritta.Loritta.Companion.RANDOM
import com.mrpowergamerbr.loritta.userdata.LorittaProfile
import com.mrpowergamerbr.loritta.userdata.ServerConfig
import com.mrpowergamerbr.loritta.utils.eventlog.StoredMessage
import org.bson.codecs.configuration.CodecRegistries
import org.bson.codecs.pojo.PojoCodecProvider
import org.bson.codecs.pojo.annotations.BsonCreator
import org.bson.codecs.pojo.annotations.BsonProperty

object Gabriela {
	@JvmStatic
	fun main(args: Array<String>) {
		println("Olá mundo!")

		val pojoCodecRegistry = CodecRegistries.fromRegistries(MongoClient.getDefaultCodecRegistry(),
				CodecRegistries.fromProviders(PojoCodecProvider.builder().automatic(true).build()))

		val mongoBuilder = MongoClientOptions.Builder().apply {
			codecRegistry(pojoCodecRegistry)
		}

		val options = mongoBuilder
				.maxConnectionIdleTime(10000)
				.maxConnectionLifeTime(10000)
				.connectionsPerHost(750)
				.build()

		val mongo = MongoClient("127.0.0.1:27017", options) // Hora de iniciar o MongoClient

		val db = mongo.getDatabase("loritta")

		val dbCodec = db.withCodecRegistry(pojoCodecRegistry)

		val gabrielaMessages = dbCodec.getCollection("gabriela", GabrielaMessage::class.java)

		while (true) {
			val pergunta = readLine()!!.toLowerCase() // Já que nós não ligamos se o cara escreve "Nilce" ou "nilce"

			println("Você: $pergunta")

			val document = gabrielaMessages.find(
					Filters.eq("_id", pergunta)
			).firstOrNull()

			if (document != null) {
				println(document.answers[RANDOM.nextInt(document.answers.size)])
			} else {
				println("Eu não sei uma resposta para esta pergunta! :(")
			}
		}
	}

	class GabrielaMessage @BsonCreator constructor(
			@BsonProperty("_id")
			_question: String // Guild ID
	) {
		@BsonProperty("_id")
		val question = _question
		var answers = mutableListOf<String>()
	}
}

Pronto, agora já deve funcionar! Yay!

/assets/img/gabriela-simsimi/new.png

Bem... ainda nós precisamos fazer algum jeito para o usuário poder ensinar novidades interessantes para o nosso chat bot, não é mesmo? No SimSimi, você pode ensinar novas palavras para o SimSimi ao escrever algo que ele não conhece, ele irá perguntar se você quer ensinar o que ele deve responder quando escrevem isto e, depois, você escreve o que ele deverá responder ao perguntarem aquilo.

Como o nosso chat bot será para o Discord, quando o bot não saber a resposta, ele irá reagir com 👍 e, se o usuário reagir também, ele poderá ensinar uma novidade para o chat bot! Mas, por enquanto, como é um aplicativo que roda no console, irei fazer que quando o usuário usar y, ele possa ensinar a resposta para aquela pergunta.

var ultimaPergunta = "???"
var nextCanBeLearn = false

while (true) {
	val pergunta = readLine()!!.toLowerCase() // Já que nós não ligamos se o cara escreve "Nilce" ou "nilce"

	if (nextCanBeLearn && pergunta == "y") {
		println("Quando alguém perguntar \"${ultimaPergunta}\", o que eu devo responder?")
		val deveResponder = readLine()!!.toLowerCase()

		// Nós agora iremos pegar se a Gabriela já aprendeu alguma resposta para esta frase, se não, nós iremos criar uma
		val document = gabrielaMessages.find(
				Filters.eq("_id", pergunta)
		).firstOrNull() ?: GabrielaMessage(ultimaPergunta)

		document.answers.add(deveResponder)

		println("Obrigada por me ensinar!")

		// upsert = Se já existe, apenas dê replace, se não existe, insira
		val updateOptions = UpdateOptions().upsert(true)
		gabrielaMessages.replaceOne(
				Filters.eq("_id", ultimaPergunta),
				document,
				updateOptions
		)
		continue
	}

	println("Você: $pergunta")

	val document = gabrielaMessages.find(
			Filters.eq("_id", pergunta)
	).firstOrNull()

	if (document != null) {
		println("Gabriela: " + document.answers[RANDOM.nextInt(document.answers.size)])
	} else {
		println("Gabriela: Eu não sei uma resposta para esta pergunta! :( Se você quer me ensinar, responda com y!")
		ultimaPergunta = pergunta
		nextCanBeLearn = true
	}
}

owo, agora o programa está bem maior que antes! E agora nós podemos ensinar coisas para a Gabriela!

/assets/img/gabriela-simsimi/questions_n_answers.png

Pronto, nosso sistema de chat bot básico está feito, e funciona até que... bem!

Mas ainda está longe de ser um SimSimi, nós precisamos aplicar algumas mudanças nas nossas perguntas... Como remover acentos (para que você vire voce, para que, se alguém perguntar voce ela também responda se alguém tenha ensinado você), aplicar "correções de vc para você", etc.

Para remover os acentos é fácil, só usar o StringUtils#stripAccents do Apache Commons.

/assets/img/gabriela-simsimi/no_accents.png

níçé.

E agora nós precisamos trocar os erros ortográficos para as versões "corretas" das palavras, como eu sou perfeccionista (bem, eu acho que eu sou) eu já tinha feito uma lista de corretores ortográficos para o SparklyPower muitos anos atrás.

    - "(dima)---diamante"
    - "(SparklyBot)---§dSparklyBot§f"
    - "(b(e)?l(e)?z(a)?)---beleza"
    - "(vem ca)---vem cá"
    - "(n(ã|a)(o|u)(m|n)?)---não"
    - "\\b(v(o)?c(e|ê)?)\\b---você"
    - "\\b(v(o)?c(e|ê)?(i)?s)\\b---vocês"
    - "(v(a)?l(e)?(u|w))---valeu"
    - "\\b((e|i)zz(e|i))\\b---delícia S2"
    - "\\b(f(a)?l(o)?(u|w))\\b---falou"
    - "(cabe(c|ç|ss)a)---cabeça"
    - "\\b(al(g|q)(u)?(e|é)m)\\b---alguém"
    - "\\b(al(g|q)m)\\b---alguém"
    - "\\b(ola)\\b---olá"
    - "\\b(ta)\\b---tá"
    - "\\b(n)\\b---não"
    - "\\b(eh)\\b---é"
...

Agora só precisa deletar tudo que é inútil para a gente e usar o poder do replace para transformar em uma map que o Kotlin aceite!

"(dima)" to "diamante"
"(b(e)?l(e)?z(a)?)" to "beleza"
"(vem ca)" to "vem cá"
"(n(ã|a)(o|u)(m|n)?)" to "não"
"\\b(v(o)?c(e|ê)?)\\b" to "você"
"\\b(v(o)?c(e|ê)?(i)?s)\\b" to "vocês"
"(v(a)?l(e)?(u|w))" to "valeu"
"\\b(f(a)?l(o)?(u|w))\\b" to "falou"
"(cabe(c|ç|ss)a)" to "cabeça"
"\\b(al(g|q)(u)?(e|é)m)\\b" to "alguém"
"\\b(al(g|q)m)\\b" to "alguém"
"\\b(ola)\\b" to "olá"
"\\b(ta)\\b" to "tá"
"\\b(n)\\b" to "não"
"\\b(eh)\\b" to "é"
...

Bem melhor!

/assets/img/gabriela-simsimi/replacers.png

Que lista grande... Mas agora nós precisamos usar ela no nosso código!

var pergunta = readLine()!!.toLowerCase() // Já que nós não ligamos se o cara escreve "Nilce" ou "nilce"

for ((regex, replace) in corretores) {
	pergunta = pergunta.replace(Regex(regex), replace)
}
			
pergunta = StringUtils.stripAccents(pergunta)

/assets/img/gabriela-simsimi/replaced.png

Pronto! Agora as nossas mensagens estão sendo corrigidas ortograficamente (e depois todos os acentos são removidos)

Agora uma coisa que nós também precisamos fazer é remover punctuation marks (como ?, !, etc), é possível fazer isto utilizando pergunta = pergunta.replace(Regex("\\p{P}"), "") (viva ao poder do RegEx!)

/assets/img/gabriela-simsimi/regex_power.png

Como é possível perceber a Gabriela automaticamente corrige nossa mensagem, remove todas as punctuation marks e depois remove todos os acentos!

/assets/img/gabriela-simsimi/example.png

Nós já temos o básico, mas lembra da parte que "O SimSimi não responde exatamente o que você perguntou"? Então, nós precisamos fazer isto também.

Isto já complica um pouco, mas não é tão difícil assim, mas será necessário separar a nossa palavra em várias partes, por exemplo:

Você gosta do Jerry do Undertale?

Você, gosta, do, Jerry, do, Undertale?

E depois juntar a frase de várias formas

Você gosta do Jerry do Undertale?

Você gosta do Jerry do

Você gosta do Jerry

...

Isto quer dizer que o chat bot vai responder coisas nada a ver para perguntas nada a ver? Sim! Mas é assim que o SimSimi funciona e as pessoas gostam!

val split = pergunta.split(" ")

val perguntas = mutableSetOf<String>()

for (n in split.size downTo 1) {
	perguntas.add(split.joinToString(" ", limit = n, truncated = "").trim())
}

perguntas.addAll(split)

println("SEPARADO EM PERGUNTAS: ${perguntas}")

val document = gabrielaMessages.find(
		Filters.`in`("_id", perguntas)
).firstOrNull()

/assets/img/gabriela-simsimi/fuzzy_matcher.png

Sim, isto irá fazer o chat bot responder coisas nada a ver para perguntas nada a ver, mas pelo ou menos irá responder alguma coisa, sendo possível criar respostas mais "corretas" futuramente.

Neste ponto eu já decidi implementar o chat bot na Loritta, mas é claro, eu precisei fazer algumas pequenas alterações..

  • Cada "Resposta" possui um locale diferente, já que a Loritta possui várias linguagens diferentes então é necessário fazer que ela possa suportar várias linguagens diferentes.
  • Lembra da ideia de colocar um "joinha" para ensinar ela? Bem, eu resolvi mudar para uma lâmpada.

E no final ficou assim! /assets/img/gabriela-simsimi/example_gif.gif

Ufa, todo esse trabalho para criar um chat bot, agora a única coisa que falta é pessoas ensinando coisas legais (ou não tão legais assim...) para a Gabriela!

E é isto, Gabriela vs O bichinho amarelinho que não gosta de mim, os dois a 80km, quem é mais rápido?

Talvez em breve eu faça que possa conversar com a Gabriela pelo próprio navegador, sem que precise usar a Loritta... quem mais para frente... :3