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
Etapa | Tarefas | Tamanho 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 [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:
- Identificar a intenção do usuário
- 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.py
dentro 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
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:
Etapa | Tarefas | Tamanho 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 [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!