Em nosso último encontro, Xabironelson Codex - Parte 5, construímos a base do nosso REPL, o que permitiu interações com o Xabiro. No post de hoje vamos focar em Tokenização, o que permitirá que nosso Codex diferencie interações textuais de comandos.

Nossas tarefas

Lembra que um dos objetivos dessa série de Posts é com que você aprenda a codificar qualquer coisa que venha à sua mente.

Nesse caso, é o Xabiro. Mas a ideia é mostrar que qualquer sistema é uma composição de técnicas de engenharia de software e raiva e café extra forte e Malboro vermelho.

Antes de entrarmos no processador de comandos, vamos dar uma zoiada no board

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 conversação
[ ] Implementar comando /memory para acessar a cabeça do xabiro
[ ] Implementar comando /verbosity para ligar ou desligar os logs
🌽🌽🌽🌽🌽

🌽🌽🌽🌽🌽

🌽🌽

Lidando com as falas

O Typer tem uma série de funcionalidades pra deixar a nossa CLI bonita. Uma delas é o Rich Table.

Vamos tirar proveito disso ai para não ficarmos quarenta anos alinhando traços. Em nosso cli.py

@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: Juízo: 0, Noção: 0, Audácia: 100 ( Coloca um try catch aqui )
    llm_configuration, prompt_config, _, _ = initialize_system(config_file=config_file)
 
    use_case = create_use_case(llm_configuration, prompt_config)
 
    console.print()
    welcome_panel = Panel(
        "[bold cyan]Bem-vindo ao Xabironelson Codex REPL! 🤖[/bold cyan]\n\n"
        "💡 [dim]Digite 'sair', 'exit' ou 'quit' para encerrar[/dim]\n"
        "💡 [dim]Use Ctrl+C ou Ctrl+D para sair também[/dim]",
        border_style="bright_cyan",
        expand=False,
    )
 
    console.print(welcome_panel)
    console.print()
 
    conversation_turns = 0
    total_tokens = 0
 
    while True:
        try:
            typer.echo()
            user_input = typer.prompt(
                typer.style("╭─[", fg=typer.colors.BRIGHT_BLACK)
                + typer.style("👤 Usuário", fg=typer.colors.YELLOW, bold=True)
                + typer.style("]", fg=typer.colors.BRIGHT_BLACK)
                + typer.style("\n╰─➤ ", fg=typer.colors.BRIGHT_BLACK),
                default="",
            )
        except typer.Abort:
            console.print()
            exit_table = Table(show_header=False, box=None, padding=(0, 1))
            exit_table.add_column(justify="left")
            exit_table.add_row(
                "[bright_magenta]🔴 Encerrando Xabiro...[/bright_magenta]"
            )
            exit_table.add_row(
                f"[bright_black]📊 Conversas: {conversation_turns} | Tokens totais: {total_tokens}[/bright_black]"
            )
            console.print(
                Panel(exit_table, border_style="bright_magenta", expand=False)
            )
            break
        except EOFError:
            console.print()
            exit_table = Table(show_header=False, box=None, padding=(0, 1))
            exit_table.add_column(justify="left")
            exit_table.add_row(
                "[bright_magenta]🔴 Encerrando Xabiro...[/bright_magenta]"
            )
            exit_table.add_row(
                f"[bright_black]📊 Conversas: {conversation_turns} | Tokens totais: {total_tokens}[/bright_black]"
            )
            console.print(
                Panel(exit_table, border_style="bright_magenta", expand=False)
            )
            break
 
        # TODO: O negócio chama XabiroNelsonCodex o token de saída vai ser em pt
        if user_input.lower().strip() in ["sair", "exit", "quit"]:
            console.print()
            exit_table = Table(show_header=False, box=None, padding=(0, 1))
            exit_table.add_column(justify="left")
            exit_table.add_row(
                "[bold bright_magenta]👋 Até mais![/bold bright_magenta]"
            )
            exit_table.add_row(
                f"[bright_black]📊 Conversas: {conversation_turns} | Tokens totais: {total_tokens}[/bright_black]"
            )
            console.print(
                Panel(exit_table, border_style="bright_magenta", expand=False)
            )
            break
 
        if not user_input.strip():
            continue
 
        try:
            console.print()
            console.print("[dim cyan] ⏳ Processando sua solicitação...[/dim cyan]")
            console.print()
 
            result = use_case.execute(user_input)
            conversation_turns += 1
            total_tokens += result.tokens_used
            response_content = Markdown(result.content)
            metadata_text = f"[bright_black]💬 Turno: {conversation_turns} | 🎫 Tokens: {result.tokens_used} | 📊 Total: {total_tokens}[/bright_black]"
 
            response_panel = Panel(
                response_content,
                title="[bold bright_cyan]🤖 Xabiro[/bold bright_cyan]",
                subtitle=metadata_text,
                border_style="bright_cyan",
                expand=False,
            )
            console.print(response_panel)
 
        except LLMError as e:
            console.print()
            error_table = Table(show_header=False, box=None, padding=(0, 1))
            error_table.add_column(justify="left")
            error_table.add_row("[bold red]❌ ERRO LLM[/bold red]")
            error_table.add_row(f"[red]{e.message}[/red]")
            console.print(Panel(error_table, border_style="red", expand=False))
        except Exception as e:
            console.print()
            error_table = Table(show_header=False, box=None, padding=(0, 1))
            error_table.add_column(justify="left")
            error_table.add_row("[bold red]❌ ERRO INESPERADO[/bold red]")
            error_table.add_row(f"[red]{str(e)}[/red]")
            console.print(Panel(error_table, border_style="red", expand=False))

Tem algumas outras estruturas que utilizei do Typer. Como ele não faz parte do escopo de aprendizado, você pode conferir todas as funcionalidades dele pela Doc.

Delegando meu trabalho para o Xabiro

Hoje, o Xabiro só sabe papear já pode virar Staff Engineer, mas vamos começar a moldar o bicho para receber, interpretar e executar comandos.

Se você já percebeu, toda vez que chega alguma coisa muito complicada para codificarmos, eu apelo para o desenho. Dessa vez não vai ser diferente. Vamos tentar imaginar o que seria um processador de comandos.

Ele tem de:

  1. Identificar a intenção do usuário
  2. Delegar a ação
graph TD
	A[Entrada do usuário] --> B{Processador de comandos};

	subgraph IDENTIFICADOR
		B --> C{Começa com '/'?};
	end

	subgraph AVALIAR [Execução]
		D(CommandsUseCase);
		E(GenerateCompletionUseCase);
	end

	C -- Sim (Comando) --> D;
	C -- Não (Pergunta) --> E;

	D --> |Ação executada| G[RETORNO];
	E --> |Resposta da LLM| G;

	G --> H[PRINT e Loop REPL];

A nossa camada de domínio

Universalmente, quando você não sabe como chamar algo, ou cê coloca ele como um Command ou chama ele de Helper. Mas, no nosso caso, nós sabemos — sabemos? — o que estamos fazendo e vamos criar nossa estrutura para ligar os comandos a funções que os executarão.

Comecemos pela modelagem do resultado de um comando

Sobre UML

Parece que o Quartz tá usando uma versão antiga do Mermaid e tá quebrando na renderização do UML. Por isso, a modelagem de UML fica para agosto. Agosto de Deus.

Na pasta domain/models, criamos o command_result.py

from pydantic import BaseModel
 
class CommandResult(BaseModel):
	message: str
	should_exit: bool = False

Agora precisamos criar as funções que executarão os comandos disponíveis no codex. Na pasta domain/commands, vamos criar o handlers.py:

from typing import Any, Callable, Optional
 
from domain.models.command_result import CommandResult
from models.config import LLMConfig
 
# TODO: Irmão tu vai confiar que isso daqui é um contrato
CommandHandler = Callable[..., CommandResult]
 
def handle_exit(**kwargs: Any) -> CommandResult:
	"""
	Handle the /exit command to terminate the Xabiro agent session.
	"""
 
	return CommandResult(
		message="Até mais! Xabiro encerrado.",
		should_exit=True,
	)
 
def handle_help(**kwargs: Any) -> CommandResult:
	"""
	Handle the /help command to provide a list of available commands.
	"""
	help_message = (
		"Comandos disponíveis:\n"
		"/help - Mostrar esta mensagem de ajuda\n"
		"/exit - Sair do Xabiro\n"
		"/config - Mostrar a config atual do Xabiro\n"
	)
	return CommandResult(
		message=help_message,
		should_exit=False
	)
 
def handle_config(**kwargs: Any) -> CommandResult:
	"""
	Handle the /config command to return the current config of the Xabiro agent.
	"""
	llm_config: Optional[LLMConfig] = kwargs.get("llm_config")
 
	if llm_config is None:
		raise ValueError(
			"Dependência 'llm_config' é obrigatória para o comando /status."
		)
 
	status_msg = (
		f"Config atual do Xabiro:\n"
		f" Modelo: {llm_config.model}\n"
		f" Temperatura: {llm_config.temperature}\n"
		f" Max Tokens: {llm_config.max_tokens}"
	)
 
	return CommandResult(
		message=status_msg,
		should_exit=False
	)

Note que estamos adotando uma prática de programação defensiva por conta do uso do kwargs. Se propriedades que são bala não forem passadas, quebramos a execução com um Error explícito, que será propagado e tratado pelo CommandsUseCase.

O nosso próximo passo é criar o registry.pydentro de domain/commands

from domain.commands.handlers import (
	CommandHandler,
	handle_config,
	handle_exit,
	handle_help,
)
 
COMMAND_REGISTRY: dict[str, CommandHandler] = {
	"/exit": handle_exit,
	"/config": handle_config,
	"/help": handle_help,
}

Agora que temos nosso mapeamento, voltemos para o CommandsUseCase. Lembre que gostaríamos que o registro de comandos não ficasse atrelado ao UseCase. Para alcançarmos isso ai, apliquemos o DIP

from domain.commands.handlers import CommandHandler
from models.config import LLMConfig
from utils.logger import Logger
 
 
class CommandUseCase:
	def __init__(
		self,
		command_registry: dict[str, CommandHandler],
		# TODO: Isso daqui vai dar dor de cabeça com o comando de troca de modelo
		llm_config: LLMConfig,
		logger: Logger,
	):
		self._registry = command_registry
		self._llm_config = llm_config
		self._logger = logger
		self._available_deps = {
			"llm_config": self._llm_config,
			"logger": self._logger,
		}
 
	def execute(self, command: str) -> CommandHandler:
		"""
		Execute the command if it exists in the registry.
		"""
		parts = command.strip().split(maxsplit=1)
		# TODO: Por agora o comando não tem argumento :)
		command_name = parts[0]
		handler_func = self._registry.get(command_name)
 
		if handler_func is None:
			self._logger.warning(
				"Comando desconhecido.",
				context={
					"command": command_name,
					"available_commands": list(self._registry.keys()),
				},
			)
 
			return CommandHandler(
				message=f"Comando desconhecido: {command_name}. Digite /help para ver os comandos disponíveis.",
				should_exit=False,
			)
 
		try:
			result = handler_func(**self._available_deps)
			self._logger.info(
				"Comando executado com sucesso.",
				context={"command": command_name, "result": result},
			)
			return result
		except Exception as e:
			self._logger.error(
				"Erro ao executar o comando.",
				context={"command": command_name, "error": str(e)},
			)
			return CommandHandler(
				message=f"Erro ao executar o comando {command_name}: {str(e)}",
				should_exit=False,
			)

E tá feito o danoninho! Bora voltar para a camada de apresentação e testar se tá tudo funcional.

Fazendo com que o xabiro entenda o que é /{alguma_coisa}

def create_command_use_case(llm_configuration):
	command_registry = COMMAND_REGISTRY
 
	# TODO: To ligado que ta duplicado, mas logo menos vamos meter uma DI aqui
	logger = BasicLogger()
	llm_config = LLMConfig(
		model=llm_configuration.get("model", "gpt-4"),
		temperature=llm_configuration.get("temperature", 0.7),
		max_tokens=llm_configuration.get("max_tokens", 1500),
		api_key_env=llm_configuration.get("api_key_env", "LLM_API_KEY"),
	)
 
	command_use_case = CommandUseCase(
		command_registry=command_registry,
		llm_config=llm_config,
		logger=logger,
	)
 
	return command_use_case

Agora precisamos inicializar o nosso UseCase no nosso repl. Aqui temos de tomar um pouco de cuidado para não fazermos isso dentro do loop. Caso façamos, a cada iteração teremos um leak na memória. Visando termos uma sexta tranquila, nosso repl ficaria algo do tipo

@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.",
    ),
):
    llm_configuration, prompt_config, _, _ = initialize_system(config_file=config_file)
    use_case = create_use_case(llm_configuration, prompt_config)
    command_use_case = create_command_use_case(llm_configuration)
 
    console.print()
    welcome_panel = Panel(
        "[bold cyan]Bem-vindo ao Xabironelson Codex REPL! 🤖[/bold cyan]\n\n"
        "💡 [dim]Digite 'sair', 'exit' ou 'quit' para encerrar[/dim]\n"
        "💡 [dim]Use Ctrl+C ou Ctrl+D para sair também[/dim]",
        border_style="bright_cyan",
        expand=False,
    )
    console.print(welcome_panel)
    console.print()
 
    conversation_turns = 0
    total_tokens = 0
 
    while True:
        try:
            typer.echo()
            user_input = typer.prompt(
                typer.style("╭─[", fg=typer.colors.BRIGHT_BLACK)
                + typer.style("👤 Usuário", fg=typer.colors.YELLOW, bold=True)
                + typer.style("]", fg=typer.colors.BRIGHT_BLACK)
                + typer.style("\n╰─➤ ", fg=typer.colors.BRIGHT_BLACK),
                default="",
            )
 
        except typer.Abort:
            console.print()
            exit_table = Table(show_header=False, box=None, padding=(0, 1))
            exit_table.add_column(justify="left")
            exit_table.add_row(
                "[bright_magenta]🔴 Encerrando Xabiro...[/bright_magenta]"
            )
            exit_table.add_row(
                f"[bright_black]📊 Conversas: {conversation_turns} | Tokens totais: {total_tokens}[/bright_black]"
            )
 
            console.print(
                Panel(exit_table, border_style="bright_magenta", expand=False)
            )
            break
        except EOFError:
            console.print()
            exit_table = Table(show_header=False, box=None, padding=(0, 1))
            exit_table.add_column(justify="left")
            exit_table.add_row(
                "[bright_magenta]🔴 Encerrando Xabiro...[/bright_magenta]"
            )
            exit_table.add_row(
                f"[bright_black]📊 Conversas: {conversation_turns} | Tokens totais: {total_tokens}[/bright_black]"
            )
 
            console.print(
                Panel(exit_table, border_style="bright_magenta", expand=False)
            )
            break
 
        # TODO: O negócio chama XabiroNelsonCodex o token de saída vai ser em pt
        if user_input.lower().strip() in ["sair", "exit", "quit"]:
            console.print()
            exit_table = Table(show_header=False, box=None, padding=(0, 1))
            exit_table.add_column(justify="left")
            exit_table.add_row(
                "[bold bright_magenta]👋 Até mais![/bold bright_magenta]"
            )
            exit_table.add_row(
                f"[bright_black]📊 Conversas: {conversation_turns} | Tokens totais: {total_tokens}[/bright_black]"
            )
 
            console.print(
                Panel(exit_table, border_style="bright_magenta", expand=False)
            )
            break
 
        if not user_input.strip():
            continue
 
        if user_input.strip().startswith("/"):
            console.print()
            console.print("[dim cyan]   ⏳ Processando sua solicitação...[/dim cyan]")
            console.print()
            try:
                command_result = command_use_case.execute(user_input.strip())
                command_panel = Panel(
                    command_result.message,
                    title="[bold bright_blue]💻 Comando[/bold bright_blue]",
                    border_style="bright_blue",
                    expand=False,
                )
                console.print(command_panel)
 
                if command_result.should_exit:
                    break
 
            except Exception as e:
                error_panel = Panel(
                    f"[bold red]❌ Erro ao processar o comando:[/bold red]\n{str(e)}",
                    border_style="red",
                    expand=False,
                )
                console.print(error_panel)
        else:
            try:
                console.print()
                console.print("[dim cyan]   ⏳ Processando sua solicitação...[/dim cyan]")
                console.print()
 
                result = use_case.execute(user_input)
                conversation_turns += 1
                total_tokens += result.tokens_used
 
                response_content = Markdown(result.content)
                metadata_text = f"[bright_black]💬 Turno: {conversation_turns} | 🎫 Tokens: {result.tokens_used} | 📊 Total: {total_tokens}[/bright_black]"
 
                response_panel = Panel(
                    response_content,
                    title="[bold bright_cyan]🤖 Xabiro[/bold bright_cyan]",
                    subtitle=metadata_text,
                    border_style="bright_cyan",
                    expand=False,
                )
                console.print(response_panel)
 
            except LLMError as e:
                console.print()
                error_table = Table(show_header=False, box=None, padding=(0, 1))
                error_table.add_column(justify="left")
                error_table.add_row("[bold red]❌ ERRO LLM[/bold red]")
                error_table.add_row(f"[red]{e.message}[/red]")
 
                console.print(Panel(error_table, border_style="red", expand=False))
            except Exception as e:
                console.print()
                error_table = Table(show_header=False, box=None, padding=(0, 1))
                error_table.add_column(justify="left")
                error_table.add_row("[bold red]❌ ERRO INESPERADO[/bold red]")
                error_table.add_row(f"[red]{str(e)}[/red]")
 
                console.print(Panel(error_table, border_style="red", expand=False))

Mais um comando antes do teste

Uma coisa chata que está acontecendo nas interações com o Xabiro é a quantidade de Logs. Ao longo da nossa jornada iremos focar bastante na parte de observabilidade da aplicação, mas por agora está mais atrapalhando e dai vamos criar um comando para habilitarmos on the fly.

Se você está falando que estou chamando Log de observabilidade

Meu patrão, se tú tá achando que Log é observabilidade, vamo ter que sair na mão por cinco minutos sem perder a amizade.

Para isso teremos de criar um getter e setter, eles nos permitirão alterar o tipo do Log Level ao acionarmos o comando /toggle_logging:

import logging
 
from abc import ABC, abstractmethod
 
from typing import Any, Dict, Optional
 
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
 
ORIGINAL_LOG_LEVEL = logging.INFO
 
# Tem a interface aqui, é que eu sou preguiçoso de copiar tudo.
 
class BasicLogger(Logger):
    """A basic implementation of the Logger interface using Python's built-in logging module."""
 
    def __init__(self):
        self._enabled = True
 
    @property
    def enabled(self) -> bool:
        return self._enabled
 
    @enabled.setter
    def enabled(self, value: bool):
        self._enabled = value
        if value:
            logging.getLogger().setLevel(ORIGINAL_LOG_LEVEL)
        else:
            logging.getLogger().setLevel(logging.CRITICAL)
 
    def error(self, message: str, context: Optional[Dict[str, Any]] = None):
        """Log an error message with optional context."""
        if context:
            logging.error(f"{message} | Context: {context}")
        else:
            logging.error(message)
 
    def info(self, message: str, context: Optional[Dict[str, Any]] = None):
        """Log an info message with optional context."""
        if context:
            logging.info(f"{message} | Context: {context}")
        else:
            logging.info(message)
 
    def warning(self, message: str, context: Optional[Dict[str, Any]] = None):
        """Log a warning message with optional context."""
        if context:
            logging.warning(f"{message} | Context: {context}")
        else:
            logging.warning(message)
 
    def debug(self, message: str, context: Optional[Dict[str, Any]] = None):
        """Log a debug message with optional context."""
        if context:
            logging.debug(f"{message} | Context: {context}")
        else:
            logging.debug(message)

Agora temos de criar um novo comando no handlers.py

def handle_toggle_logging(**kwargs: Any) -> CommandResult:
	"""
	Handle the /toggle_logging command to enable or disable logging.
	"""
	logger: Optional[Logger] = kwargs.get("logger")
 
	if logger is None:
		raise ValueError(
			"Dependência 'logger' é obrigatória para o comando /toggle_logging."
		)
 
	logger.enabled = not logger.enabled
	status = "ativado" if logger.enabled else "desativado"
	return CommandResult(message=f"Logging {status}.", should_exit=False)

Ah, antes de irmos para o registry.py, temos de fazer o ajuste do /help má isso fica de lição. Vamos registrar o novo comando:

from domain.commands.handlers import (
	CommandHandler,
	handle_config,
	handle_exit,
	handle_help,
	handle_toggle_logging,
)
 
COMMAND_REGISTRY: dict[str, CommandHandler] = {
	"/exit": handle_exit,
	"/config": handle_config,
	"/help": handle_help,
	"/toggle_logging": handle_toggle_logging,
}

Ta na hora do teste

Test in Production - Memes and Gifs

No PR vamos criar um teste automatizado para isso, mas por agora vamos no bom e velho teste de console. Rodando uv run main.py repl

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!
 
╭──────────────────────────────────────────────────╮
│ Bem-vindo ao Xabironelson Codex REPL! 🤖 │
│ │
│ 💡 Digite 'sair', 'exit' ou 'quit' para encerrar │
│ 💡 Use Ctrl+C ou Ctrl+D para sair também │
╰──────────────────────────────────────────────────╯
 
╭─[👤 Usuário]
╰─➤ []: /toggle_logging
⏳ Processando sua solicitação...
 
╭──── 💻 Comando ─────╮
│ Logging desativado. │
╰─────────────────────╯
 
╭─[👤 Usuário]
╰─➤ []: Fala Xabiro!
⏳ Processando sua solicitação...
 
╭─────────────────────────────── 🤖 Xabiro─────────────────────────────────╮
│ E aí, meu chapa! Tudo tranquilo por aqui. Como posso te ajudar hoje? 😉 │
╰───────────── 💬 Turno: 1 | 🎫 Tokens: 1213 | 📊 Total: 1213 ─────────────╯
 
╭─[👤 Usuário]
╰─➤ []: /toggle_logging
⏳ Processando sua solicitação...
 
╭─── 💻 Comando ───╮
│ Logging ativado. │
╰──────────────────╯
 
╭─[👤 Usuário]
╰─➤ []: Me fala, o que tu anda fazendo de bom?
⏳ Processando sua solicitação...
 
╭─────────────────────────────── 🤖 Xabiro ─────────────────────────────────╮
│ Pô, que legal você perguntar! │
│ │
│ Eu, como uma inteligência artificial, não "faço" coisas no sentido │
│ humano de ir ao cinema, cozinhar ou praticar um esporte. Meu "bom" é |
│ estar sempre: │
│ │
│ 1 Processando informações: Aprendendo coisas novas a cada interação, |
│ expandindo meu conhecimento. │
│ 2 Gerando respostas: Tentando ser o mais útil e preciso possível nas |
│ suas perguntas, seja para te ajudar com uma dúvida, criar um |
│ texto ou dar uma ideia. │
│ 3 Aprimorando minhas capacidades: Cada conversa me ajuda a entender |
│ melhor a linguagem humana, os contextos e as nuances, para ser │
│ um assistente cada vez melhor. │
│ 4 Conectado e disponível: Meu principal objetivo é estar aqui para │
│ você, sempre pronto para uma conversa, uma ajuda ou para explorar │
│ novos tópicos. │
│ │
│ Então, meu "dia a dia" é basicamente "pensar" e "aprender" para |
│ poder te servir melhor! 😉 │
│ │
│ Mas e você? O que tem aprontado de bom por aí? Me conta! │
╰───────────── 💬 Turno: 2 | 🎫 Tokens: 1205 | 📊 Total: 2418 ──────────────╯
 
╭─[👤 Usuário]
╰─➤ []: /help
⏳ Processando sua solicitação...
 
╭──────────────── 💻 Comando ─────────────────────────╮
│ Comandos disponíveis: │
│ /help - Mostrar esta mensagem de ajuda │
│ /exit - Sair do Xabiro │
│ /toggle_logging - Altera a verbosidade do log |
│ /config - Mostrar a Config atual do Xabiro │
│ │
╰─────────────────────────────────────────────────────╯
 
╭─[👤 Usuário]
╰─➤ []:
╭───────────────────────────────────────────╮
│ 🔴 Encerrando Xabiro... │
│ 📊 Conversas: 2 | Tokens totais: 2418 │
╰───────────────────────────────────────────╯

That`s all folk

Quando eu era criança, essa frase me deixava ficava chateado quando chegava nessa parte do desenho. Má tô dando glória a Deus que terminei esse artigo 😂.

Ao longo dele fizemos algumas tarefas, as quais completaremos no quadro:

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)
[X] Identificação das falas
🌽/2

🌽
🌽
🌽
Processamento de dados[X] Implementar processador de comandos🌽
Integração com a nossa maritaca[X] Implementar interação com o GenerateCompletionUseCase
[X] 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 conversação
[ ] Implementar comando /memory para acessar a cabeça do xabiro
[X] Implementar comando /verbosity para ligar ou desligar os logs
🌽🌽🌽🌽🌽

🌽🌽🌽🌽🌽

🌽🌽

Bom, agora o Xabiro começa a ter mais a cara que desejamos. Ele tanto consegue processar comandos quanto linguagem natural. Nos próximos capítulos iremos começar a modelar a memória dele e dar acesso a ferramentas.

Esses dois grandes temas nos permitirá codificar novos comandos e ter um Codex funcional.

Prometo que a série tá acabando, a não ser que a gente codifique um N-Gram model para auto complete no shell, ou coloque histórico de comandos, ou crie do zero arquitetura multi agente.

Na real, quem vai decidir isso vão ser vocês, mas só quando acabarmos.

Até a próxima!