Putz isso vai ser extremamente dificil kk
Exemplo: ‘Quanto custa 1kg do seu produto?’ ou ‘Quanto custa 1g do seu produto?’.
Já trabalhei com NLP, sem IA, a sugestão que eu vou dar é baseado no que eu já fiz no passado.
Basicamente tu pode fazer o mesmo que a gente faz com parsing de linguagem de programação (coisa que já fiz também), quebra o texto em tokens, por exemplo:
[str("Quanto"), str("custa"), number("1"), str("kg"), str("do"), str("seu"), str("produto"), str("?")]
Aqui tem várias decisões que você pode fazer também, você pode já no processo de criação de tokens determinar algumas keywords, como as linguagens fazem, e ai tua palavra kg
já poderia vir com um tipo diferente:
[str("Quanto"), str("custa"), number("1"), mass_unit(KG, str("kg")), str("do"), str("seu"), str("produto"), str("?")]
ai você pode tirar também as stopwords para simplificar:
[str("Quanto"), str("custa"), number("1"), mass_unit(KG, str("kg")), str("produto"), str("?")]
Detalhe, na hora de armazenar os tokens, a gente não armazena as substrings, só os ranges, isso é crucial para qualquer lexer e parser feito na mão. Exemplo, a palavra
custa
fica armazenada comoToken(range: [7..12], type: Str)
, emass_unit
ficaria:Token(range: [14..16], type: MassUnit)
, sem nenhuma string sendo armazenada.Isso traz muitos ganhos de desempenho, por n ter q copiar string, e pouco custo de memória, pois você está armazenado ali por volta de 3 valores de 64-bit — pelo menos no exemplo que eu forneci com range tendo
start
eend
, e o campotype
(no total 3).Caso queira armazenar um "ponteiro" para essa String (ou string slice), ai seriam 4 campos de 64-bit, mas vai depender se a linguagem copia a String ou apenas referencia ela, ou se suporta ponteiros. Mas isso é só uma conveniência, alguns lexers preferem ter um método
get(str)
e ai eles retornam a substring a partir daquela string que foi passada ao metodo, ou seja, eles esquecem completamente o input e só trabalham com ranges.
Depois tu meio que vai ter duas opções, você pode fazer um matching na mão com lookahead e lookbehind (era o que a gente fazia), ou já pode progredir para um sistema de regras e criar uma estrutura composta (ou uma Syntax Tree simplificada), um exemplo de regra seria (em pseudo-EBNF e bem simplificada mesmo):
texts = text+ ;
text = mass | massUnit | str ;
mass = NUMBER, massUnit ;
massUnit = KG | G ;
str = ALPHA
KG = "kg" | "kilograma" | "quilograma";
G = "g" | "grama";
NUMBER = [0-9]+;
ALPHA = [A-Za-z]+;
E ai no final você teria uma estrutura de dados tipo assim:
texts([text(str("Quanto")), text(str("custa")), mass(number(1), mass_unit(KG)), text(str("produto")), text(str("?"))])
Com isso você já pode fazer muita coisa, ai deixo para sua imaginação kkkk, não conheço o Pinecone e nem se seria possível fazer ele não comparar trechos do texto, outra opção seria fazer obfuscação com um hash dos trechos com unidades de medida, por exemplo:
Quanto custa LztgJ2EO8YcNEcjipENcYA do seu produto?
E ai qualquer pequena mudança nesse trecho já afetaria a distância de forma perceptível.
Só que o que eu acho que é mais dificil é na verdade outras coisas, até aritmética daria para fazer esse tricky que eu comentei, mas teriam muitos corner cases, você teria que fazer a mesma ideia com estados, tipo SP
e SC
são muito próximos, depois água
e águia
, e assim por diante. Seria possível minar todos os casos e classificar? Não, mas com o tempo conseguiria diminuir bastante os corner cases e deixa-los mais raros. E teria q ter uma forma de fazer isso com agilidade e de capturar quando isso ocorre também (Levenshtein distance caberia aqui para você logar os casos), e levaria muito tempo — os mais comuns seriam faceis, o resto teria que ser na base do uso e descoberta.