No nosso último encontro, fizemos o Xabiro falar! Má há algo de estranho na nossa interação com ele: parece que estamos em um monólogo.

No Xabironelson Codex - Parte 2, havíamos discutido sobre REPL. Essa sigla bonita quer dizer Read-Eval-Print Loop (REPL). Parece ser algo puramente teórico, e um pouco distante do nosso cotidiano? Pode até parecer, mas é o conceito que faz com que seu shell esteja aí.

Pela descrição REPL é um fluxo de Ler → Avaliar → Imprimir. Modelando isso teríamos algo do tipo:

graph TD
    A[Usuário/Terminal] -->|Comando/Código| B{Ler};
    B --> C[Avaliar];
    C --> D[Imprimir];
    D -->|Resultado/Saída| A;
    D --> E{Loop/Próxima entrada?};
    E -- Sim --> B;
    E -- Não --> F[Encerrar];

Note que vamos ter de encaixar o Xabiro em algum momento do grafo. Lembra que estamos tratando a LLM como uma caixa preta e para nós ela é apenas uma classe que dado uma entrada, vai retornar uma saída. Isso casa bem com a responsabilidade do nó Avaliar.

Má e dai?

Não sei se vocês perceberam até agora, mas codar é a parte “fácil”.

Codar é a parte fácil

Te falar que eu era horrível codando na faculdade. Quando entrei em Engenharia de Computação, já tinha mexido com Lua por conta de servidor de Ragnarok. Má, eu era terrível codando o que era requerido nas matérias.

Anos depois, na indústria, eu aprendi que não era terrível, era que eu não tinha parado para aprender a base.

Então, se tu tá rodando aí, codando coisa de produto, bicho, toma vergonha e começa do zero. E não seja que nem o Caíque de 2012, que tentava codar o problema de uma vez. Quebre em problemas menores e, aí, resolva-os.

O que precisamos ter é a habilidade de pensar em como as partes de um sistema, ou sistemas, irão interagir entre si.

Vamos lembrar que adotamos três camadas e, no Xabironelson Codex - Parte 4, passamos a maior parte do tempo na construção das camadas de domínio e dados. Como o que estamos fazendo está relacionado à interação entre usuário e sistema, passaremos, hoje, a maior parte do tempo na camada de apresentação.

Vamos criar um novo comando no Typer, o repl, que tirará proveito do que já temos. Esse comando será responsável por fazer:

graph TD
    A[Inicializar sistema]
    A --> B{Exibir mensagem de boas-vindas};

    subgraph Inicialização
        A --> A1(Carregar configurações);
        A --> A2(Configurar dependências);
    end

    B --> Loop;

    subgraph Loop Principal
        Loop --> C{Ler entrada do usuário};
        C --> D{Analisar entrada};
        D -- Comando? --> D1[Executar comando];
        D -- Pergunta? --> D2[Gerar resposta LLM];
        D1 --> E[Exibir resultado];
        D2 --> E;
        E --> F{Continuar?};
    end

    F -- Sim --> C;
    F -- Não --> G[Sair];

Caaaaalma meu patrão, não vai sair codando antes de ter um plano.

I Have A Plan GIFs | Tenor

O nosso plano, ou o meu.

Um dos intuitos do Xabiro é que você aprenda a resolver problemas de uma forma diferente daquela que lhe foi ensinada. Ou é só fazer com que o nosso nelson nasça e eu pare de trabalhar, afinal, será que não foi ele que escreveu tudo isso?

Essa etapa aqui vai ser importante para entender como sair de um plano para a ação. Tudo bem que temos uma caralhada de caixinha ali e, para piorar, cheia de setas.

Vamos interpretar cada caixa como sendo uma função. Algumas já temos, como a parte da inicialização do sistema e a configuração das dependências. Outras não temos. Dado isso, teríamos:

EtapaTarefasTamanho do sabugo
Estruturando o REPL[ ] Implementar o loop de entrada com condição de saída
[ ] Tratar KeyboardInterrupt (Ctrl+C)
[ ] Tratar EOF (Ctrl+D)
[ ] Identificação das falas
🌽/2

🌽
🌽
🌽
Processamento de dados[ ] Implementar processador de comandos🌽
Integração com a nossa maritaca[ ] Implementar interação com o GenerateCompletionUseCase
[ ] Adicionar indicadores de digitação ou estados de carregamento
[ ] Remover diálogo de erro, já que sem diálogo não tem erro
[ ] Tratar erros da LLM, sem sair do REPL
🌽

🌽🌽

😈

🌽🌽
Para agosto, de Deus.[ ] Implementar comando /model para troca de modelo durante conversacao
[ ] Implementar comando /memory para acessar a cabeça do xabiro
🌽🌽🌽🌽🌽

🌽🌽🌽🌽🌽

Estruturando o REPL

Eu acho que já falei REPL umas quarenta vezes neste texto, mas prometo que não vai ser a última.

Bom, vamos começar criando um novo comando no Typer que é o repl

Vamos começar criando um novo comando no Typer, que é o repl.

Estamos a zero dias sem falar REPL.

No nosso cli.py, criaremos a seguinte função:

@app.command()
def repl(
	config_file: Path = typer.Option(
		CONFIG_FILE_NAME,
		"--config",
		"-c",
		exists=True,
		file_okay=True,
		dir_okay=False,
		writable=False,
		help="Caminho para o arquivo de configuração.",
	),
):
	# TODO: Delegar a oportunidade para alguém

Se você chegou até aqui, entendeu que estamos codando um while(True) que consome alguns comandos ou mensagens e só sai de lá quando um token em específico é digitado.

Dessa forma, a nossa função passará a ser:

@app.command()
def repl(
config_file: Path = typer.Option(
	CONFIG_FILE_NAME,
	"--config",
	"-c",
	exists=True,
	file_okay=True,
	dir_okay=False,
	writable=False,
	help="Caminho para o arquivo de configuração.",
	),
):
	# TODO: Aqui tem coragem, mas recomendo que faça a inicialização com um try catch
	llm_configuration, prompt_config, _, _ = initialize_system(config_file=config_file)
	use_case = create_use_case(llm_configuration, prompt_config)
 
	while True:
		try:
			user_input = typer.prompt(typer.style("\nVocê", fg=typer.colors.YELLOW), default="")
		except typer.Abort:
			typer.secho("\nEncerrando Xabiro...", fg=typer.colors.BRIGHT_MAGENTA)
			break
		except EOFError:
			typer.secho("\nEncerrando Xabiro...", fg=typer.colors.BRIGHT_MAGENTA)
			break
 
		# TODO: O negócio chama XabiroNelsonCodex o token de saída vai ser em pt
		if user_input.lower() in ["sair"]:
			typer.secho("Até mais!", fg=typer.colors.BRIGHT_MAGENTA)
			break
 
		if not user_input.strip():
			continue
 
		try:
			typer.secho("Processando...", fg=typer.colors.CYAN)
			result = use_case.execute(user_input)
			typer.echo(typer.style("Xabiro", fg=typer.colors.BRIGHT_CYAN))
			typer.secho(result.content, fg=typer.colors.GREEN)
			typer.echo(f"\nTokens utilizados: {result.tokens_used}")
		except LLMError as e:
			typer.secho(f"\n[ERRO LLM] {e.message}", fg=typer.colors.RED)
		except Exception as e:
			typer.secho(f"\n[ERRO INESPERADO] Um erro ocorreu: {str(e)}", fg=typer.colors.RED)

Fica a cargo do leitor conferir o PR e como as funções initialize_system e create_use_case foram modularizadas.

Se você conhece essa frase, parte 2.

Ah, o Boulos! Não, não estou falando do Boulos político. Aqui me refiro ao autor do livro de Geometria Analítica que já tirou o sono de alguns leitores do livro no primeiro período de algum curso de Exatas, com as frases que começavam com “É fácil perceber…” ou “Fica a cargo do leitor…”.

Se tudo deu certo, além de termos uma interface conversacional, a memória estará bala. Logo menos, discutiremos sobre estrutura de dados e se faz sentido termos um vetor lá. Mas, deixa o menino falar!

Carregando variáveis de ambiente...
Carregando configurações de: xabiro.yaml
 
[CONFIGURAÇÕES CARREGADAS]
 Modelo: gemini/gemini-2.5-flash
 Temperatura: 0.7
 Max Tokens: 1500
 Chave API carregada de: LLM_API_KEY (OK)
 Modo Verbose: False
 
[INICIALIZANDO SISTEMA]
Configurando dependências manualmente...
Sistema inicializado com sucesso!
 
Você []: Oi
Xabiro: Oi! Como posso ajudar?
 
Tokens utilizados: 384
 
Você []: Caralho tio! Você ta falando
Xabiro: Hahaha! Pois é, sempre por aqui, pronto pra bater um papo!
 
O que te surpreendeu tanto? E o que posso fazer por você hoje? 😉
 
Tokens utilizados: 1206
 
Você []: Manda um abraço pra galera que ta lendo ai!
Xabiro: Claro! vai:
 
**E aí, galera! Tudo tranquilo por aí? Um grande abraço virtual para todo mundo! 😉**
 
Espero que estejam tendo um dia excelente!
 
Tokens utilizados: 375
 
Você []: Ctrl + C
Encerrando Xabiro...

Mas e agora?

Dando uma zoiada no que fizemos até agora

EtapaTarefasTamanho do sabugo
Estruturando o REPL[X] Implementar o loop de entrada com condição de saída
[X] Tratar KeyboardInterrupt (Ctrl+C)
[X] Tratar EOF (Ctrl+D)
[ ] Identificação das falas
🌽/2

🌽
🌽
🌽
Processamento de dados[ ] Implementar processador de comandos🌽
Integração com a nossa maritaca[X] Implementar interação com o GenerateCompletionUseCase
[ ] Adicionar indicadores de digitação ou estados de carregamento
[ ] Remover diálogo de erro, já que sem diálogo não tem erro
[X] Tratar erros da LLM, sem sair do REPL
🌽

🌽🌽

😈

🌽🌽
Para agosto, de Deus.[ ] Implementar comando /model para troca de modelo durante conversacao
[ ] Implementar comando /memory para acessar a cabeça do xabiro
🌽🌽🌽🌽🌽

🌽🌽🌽🌽🌽

Cê pode conferir todo o código numa paulada no PR Amanhã eu apareço por aqui novamente para codificarmos:

  • Identificação das falas
  • Implementar processador de comandos
  • Adicionar indicadores de digitação ou estados de carregamento
  • Implementar comando /model
  • Implementar comando /memory
  • Implementar comando /verbosity

E depois de tudo isso, vamos escolher uma estrutura de dados para modelarmos a memória de curto prazo e darmos ferramentas à nossa maritaca. Má, isso é papo para outra hora!

Inté!