Kaique Mitsuo Silva Yamamoto
Ia

Simulação de Entrevista — Engenheiro de IA (Paper Completo)

Diálogo completo entre recrutador e desenvolvedor em entrevista técnica de engenharia de IA. Inclui testes práticos de live coding, criação de modelo proprietário PT-BR, calculadora CLI em Python e TypeScript, e avaliação de requisitos técnicos.

Simulação de Entrevista — Engenheiro de IA (Paper Completo)

Este artigo simula uma entrevista técnica real para a vaga de Engenheiro de Inteligência Artificial. O diálogo mostra exatamente o que o entrevistador pergunta, como o candidato responde, e os testes práticos que são executados em tempo real.

O candidato (Kaique) demonstra:

  1. Conhecimento teórico profundo
  2. Live coding de uma calculadora CLI em Python e TypeScript
  3. Criação de um modelo proprietário fine-tuned para PT-BR + código
  4. RAG com modelo local
  5. Deploy com Docker

Cenário

ItemDetalhe
VagaEngenheiro de Inteligência Artificial — Pleno/Sênior
EmpresaTechCorp (fictícia) — SaaS B2B com IA
EntrevistadorRafael — Tech Lead de IA
CandidatoKaique — Desenvolvedor Full Stack & AI Engineer
Duração90 minutos
FormatoLive coding + perguntas técnicas + demo

PARTE 1 — Abertura e Perguntas Conceituais (20 min)

1.1 Apresentação

Rafael (entrevistador): Obrigado por comparecer, Kaique. Vamos começar com uma apresentação rápida. Conte sobre sua experiência com IA.

Kaique (candidato): Obrigado pela oportunidade, Rafael. Sou desenvolvedor full stack com foco em engenharia de IA. Tenho experiência prática com:

  • Integração de LLMs (OpenAI, Anthropic, Groq)
  • Construção de pipelines RAG com LangChain e ChromaDB
  • Fine-tuning de modelos com LoRA e QLoRA
  • Deploy de modelos em produção com FastAPI e Docker
  • Desenvolvimento de agentes autônomos com function calling

Recentemente construí um modelo proprietário fine-tuned em PT-BR que gera funções em JavaScript, TypeScript e Python a partir de descrições em português.


1.2 Pergunta: Transformers

Rafael: Me explique como funciona a arquitetura Transformer. Sem olhar nada.

Kaique: O Transformer, proposto no paper "Attention Is All You Need" (Vaswani et al., 2017), revolucionou o NLP substituindo recorrência por self-attention.

A arquitetura tem dois lados:

Encoder (usado no BERT):

  1. Input embeddings + positional encoding
  2. Multi-head self-attention — cada token "olha" para todos os outros tokens da sequência
  3. Feed-forward network
  4. Layer normalization + residual connections

Decoder (usado no GPT):

  1. Mesma coisa, mas com masked self-attention — cada token só olha para os tokens anteriores (causal)
  2. Além disso, tem cross-attention quando conectado ao encoder (usado em modelos encoder-decoder como T5)

A fórmula do self-attention é:

Attention(Q, K, V) = softmax(QK^T / √d_k) × V

Onde:

  • Q (query): "o que estou procurando?"
  • K (key): "o que eu ofereço?"
  • V (value): "o que eu entrego?"
  • √d_k: scaling factor para evitar que os valores explodam

O multi-head roda essa atenção N vezes com projeções diferentes e concatena — captura diferentes tipos de relação (sintática, semântica, posicional).

Rafael: Muito bem. E por que self-attention é melhor que RNN?

Kaique: Três razões:

  1. Paralelização — RNN processa token por token (sequencial). Self-attention processa todos os tokens de uma vez (paralelo). Isso acelera muito o treinamento.
  2. Long-range dependencies — RNN sofre com vanishing gradient em sequências longas. Self-attention conecta qualquer token a qualquer outro diretamente — distância de caminho = 1.
  3. Escalabilidade — A arquitetura escala bem com mais dados e mais parâmetros. É por isso que todos os LLMs grandes (GPT-4, Claude, Gemini) são Transformers.

1.3 Pergunta: RAG

Rafael: Como você implementaria um sistema RAG para um chatbot corporativo que precisa responder sobre documentos internos?

Kaique: O pipeline de RAG tem 4 estágios:

1. Ingestão:

  • Carregar documentos (PDF, DOCX, TXT, MDX)
  • Chunking — dividir em pedaços de 500-1000 tokens com overlap de 10-20%
  • Embedding — transformar cada chunk em vetor (usando text-embedding-3-small ou all-MiniLM-L6-v2)
  • Indexar no vector DB (ChromaDB para protótipo, Qdrant ou Pinecone para produção)

2. Retrieval:

  • Receber a pergunta do usuário
  • Embeddar a pergunta
  • Buscar os top-k chunks mais similares (cosine similarity)
  • Opcional: re-ranking com cross-encoder para melhorar precisão

3. Generation:

  • Montar o prompt com os chunks recuperados como contexto
  • Chamar o LLM (GPT-4o, Claude, ou modelo local)
  • Instruir o modelo a responder apenas com base no contexto fornecido

4. Evaluation:

  • Medir faithfulness (a resposta se sustenta nos documentos?)
  • Medir relevância (a resposta responde à pergunta?)
  • Usar Ragas framework para avaliação automatizada

Trade-offs importantes:

  • Chunk size muito pequeno → perde contexto
  • Chunk size muito grande → ruído no retrieval
  • Top-k muito baixo → não tem informação suficiente
  • Top-k muito alto → contexto do LLM fica cheio e caro

1.4 Pergunta: Alucinações

Rafael: Como você evita que o modelo alucine?

Kaique: Cinco estratégias, em ordem de eficácia:

  1. RAG com grounding — O modelo responde apenas com base em documentos recuperados. Se não tem informação, diz "Não encontrei nos documentos."
  2. System prompt restritivo — Instruir explicitamente: "Responda apenas com base no contexto fornecido. Se não souber, diga que não sabe."
  3. Re-ranking — Usar cross-encoder para filtrar chunks irrelevantes antes de enviar ao LLM.
  4. Faithfulness scoring — Usar LLM-as-judge para avaliar se a resposta se sustenta nos documentos.
  5. Citações — Forçar o modelo a citar a fonte de cada afirmação (chunk_id + trecho).

Na prática, a combinação de RAG + system prompt + faithfulness scoring reduz alucinações para menos de 5%.


PARTE 2 — Live Coding: Calculadora CLI (25 min)

2.1 O Desafio

Rafael: Agora vamos ao teste prático. Preciso que você construa uma calculadora via linha de comando que:

  • Aceite expressões matemáticas via argumento ou modo interativo
  • Suporte operações básicas (+, -, *, /), potência e raiz quadrada
  • Tenha versões em Python e TypeScript
  • Trate erros (divisão por zero, expressão inválida)
  • Tenha histórico de operações

Tempo: 15 minutos para cada linguagem. Pode começar.


2.2 Solução em Python

Kaique (codando):

#!/usr/bin/env python3
"""
calculadora.py — Calculadora CLI em Python
Uso:
  python calculadora.py "2 + 3 * 4"
  python calculadora.py --interativo
  python calculadora.py --historico
"""
import sys
import math
import json
import os
from pathlib import Path

HISTORY_FILE = Path.home() / ".calc_history.json"

def load_history() -> list[dict]:
    if HISTORY_FILE.exists():
        return json.loads(HISTORY_FILE.read_text())
    return []

def save_history(history: list[dict]):
    HISTORY_FILE.write_text(json.dumps(history, indent=2, ensure_ascii=False))

def evaluate(expression: str) -> float:
    """Avalia expressão matemática de forma segura."""
    # Permitir apenas caracteres seguros
    allowed = set("0123456789+-*/.()^ ")
    if not all(c in allowed for c in expression.replace("sqrt(", "").replace(")", "")):
        raise ValueError(f"Caractere não permitido na expressão: {expression}")

    # Substituir operadores
    expr = expression.replace("^", "**")
    expr = expr.replace("sqrt(", "math.sqrt(")

    # Avaliar com escopo restrito
    result = eval(expr, {"__builtins__": {}, "math": math}, {})
    return float(result)

def interactive_mode():
    """Modo interativo com REPL."""
    print("=== Calculadora CLI (Python) ===")
    print("Digite 'sair' para sair, 'historico' para ver o histórico")
    print("Operações: +, -, *, /, ^ (potência), sqrt() (raiz quadrada)")
    print()

    history = load_history()

    while True:
        try:
            expr = input("calc> ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\nSaindo...")
            break

        if not expr:
            continue
        if expr.lower() in ("sair", "exit", "quit"):
            print("Até mais!")
            break
        if expr.lower() == "historico":
            print("\n--- Histórico ---")
            for i, item in enumerate(history[-10:], 1):
                print(f"  {i}. {item['expressao']} = {item['resultado']}")
            print()
            continue

        try:
            result = evaluate(expr)
            print(f"  = {result}")

            history.append({"expressao": expr, "resultado": result})
            save_history(history)
        except ZeroDivisionError:
            print("  Erro: divisão por zero")
        except ValueError as e:
            print(f"  Erro: {e}")
        except Exception as e:
            print(f"  Erro: expressão inválida ({e})")

def main():
    if len(sys.argv) < 2 or sys.argv[1] == "--interativo":
        interactive_mode()
        return

    if sys.argv[1] == "--historico":
        history = load_history()
        print("--- Histórico ---")
        for i, item in enumerate(history[-20:], 1):
            print(f"  {i}. {item['expressao']} = {item['resultado']}")
        return

    # Modo direto: python calculadora.py "2 + 3"
    expr = " ".join(sys.argv[1:])
    try:
        result = evaluate(expr)
        print(f"{expr} = {result}")

        history = load_history()
        history.append({"expressao": expr, "resultado": result})
        save_history(history)
    except Exception as e:
        print(f"Erro: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()

Kaique: Pronto. Vou testar:

$ python calculadora.py "2 + 3 * 4"
2 + 3 * 4 = 14.0

$ python calculadora.py "sqrt(16) + 2^3"
sqrt(16) + 2^3 = 12.0

$ python calculadora.py --interativo
=== Calculadora CLI (Python) ===
calc> 100 / 0
  Erro: divisão por zero
calc> 2 ^ 10
  = 1024.0
calc> historico
  1. 2 ^ 10 = 1024.0
calc> sair

Rafael: Excelente. Agora a versão TypeScript.


2.3 Solução em TypeScript

Kaique (codando):

#!/usr/bin/env ts-node
/**
 * calculadora.ts — Calculadora CLI em TypeScript
 * Uso:
 *   npx ts-node calculadora.ts "2 + 3 * 4"
 *   npx ts-node calculadora.ts --interativo
 *   npx ts-node calculadora.ts --historico
 */

import * as readline from "readline";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";

const HISTORY_FILE = path.join(os.homedir(), ".calc_history.json");

interface HistoryItem {
  expressao: string;
  resultado: number;
  timestamp: string;
}

function loadHistory(): HistoryItem[] {
  try {
    if (fs.existsSync(HISTORY_FILE)) {
      return JSON.parse(fs.readFileSync(HISTORY_FILE, "utf-8"));
    }
  } catch {}
  return [];
}

function saveHistory(history: HistoryItem[]): void {
  fs.writeFileSync(HISTORY_FILE, JSON.stringify(history, null, 2));
}

function evaluate(expression: string): number {
  // Substituir operadores
  let expr = expression.replace(/\^/g, "**");
  expr = expr.replace(/sqrt\(/g, "Math.sqrt(");

  // Validar caracteres
  const allowed = /^[\d+\-*/.()**\sMath.sqrt]+$/;
  if (!allowed.test(expr)) {
    throw new Error(`Expressão inválida: ${expression}`);
  }

  // Avaliar de forma relativamente segura
  const result = Function(`"use strict"; return (${expr})`)();

  if (typeof result !== "number" || isNaN(result) || !isFinite(result)) {
    throw new Error("Resultado inválido");
  }

  return result;
}

function addToHistory(expressao: string, resultado: number): void {
  const history = loadHistory();
  history.push({
    expressao,
    resultado,
    timestamp: new Date().toISOString(),
  });
  saveHistory(history);
}

function showHistory(): void {
  const history = loadHistory();
  console.log("\n--- Histórico ---");
  history.slice(-20).forEach((item, i) => {
    console.log(`  ${i + 1}. ${item.expressao} = ${item.resultado}`);
  });
  console.log();
}

function interactiveMode(): void {
  console.log("=== Calculadora CLI (TypeScript) ===");
  console.log("Digite 'sair' para sair, 'historico' para ver o histórico");
  console.log("Operações: +, -, *, /, ^ (potência), sqrt() (raiz quadrada)");
  console.log();

  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    prompt: "calc> ",
  });

  rl.prompt();

  rl.on("line", (line) => {
    const expr = line.trim();

    if (!expr) {
      rl.prompt();
      return;
    }

    if (["sair", "exit", "quit"].includes(expr.toLowerCase())) {
      console.log("Até mais!");
      rl.close();
      return;
    }

    if (expr.toLowerCase() === "historico") {
      showHistory();
      rl.prompt();
      return;
    }

    try {
      const result = evaluate(expr);
      console.log(`  = ${result}`);
      addToHistory(expr, result);
    } catch (e: any) {
      if (e.message.includes("division by zero") || e.message.includes("Infinity")) {
        console.log("  Erro: divisão por zero");
      } else {
        console.log(`  Erro: ${e.message}`);
      }
    }

    rl.prompt();
  });

  rl.on("close", () => process.exit(0));
}

function main(): void {
  const args = process.argv.slice(2);

  if (args.length === 0 || args[0] === "--interativo") {
    interactiveMode();
    return;
  }

  if (args[0] === "--historico") {
    showHistory();
    return;
  }

  // Modo direto: npx ts-node calculadora.ts "2 + 3"
  const expr = args.join(" ");
  try {
    const result = evaluate(expr);
    console.log(`${expr} = ${result}`);
    addToHistory(expr, result);
  } catch (e: any) {
    console.error(`Erro: ${e.message}`);
    process.exit(1);
  }
}

main();

Kaique (testando):

$ npx ts-node calculadora.ts "2 + 3 * 4"
2 + 3 * 4 = 14

$ npx ts-node calculadora.ts "sqrt(16) + 2^3"
sqrt(16) + 2^3 = 12

$ npx ts-node calculadora.ts --historico
--- Histórico ---
  1. 2 + 3 * 4 = 14
  2. sqrt(16) + 2^3 = 12

Rafael: Perfeito. Funcionalidades idênticas em ambas as linguagens, histórico persistente, tratamento de erro. Passou no teste.


PARTE 3 — Modelo Proprietário Fine-tuned (25 min)

3.1 O Desafio

Rafael: Agora o teste principal. Preciso que você demonstre um modelo proprietário que:

  • Entenda descrições em português brasileiro
  • Retorne funções em JavaScript, TypeScript ou Python
  • Não precisa ser perfeito — precisa funcionar para casos básicos

Mostre o código de treinamento e uma demo ao vivo.


3.2 Preparação do Dataset

Kaique (explicando): Vou criar um dataset de pares (descrição em PT-BR → código) e fine-tunar o Phi-2 com LoRA.

#!/usr/bin/env python3
"""
01_preparar_dataset.py
Cria o dataset de treinamento: descrição em PT-BR → código.
"""
import json

dataset = [
    # Python
    {
        "linguagem": "python",
        "descricao": "criar uma função que soma dois números",
        "codigo": "def somar(a: float, b: float) -> float:\n    return a + b"
    },
    {
        "linguagem": "python",
        "descricao": "função que verifica se um número é par",
        "codigo": "def eh_par(n: int) -> bool:\n    return n % 2 == 0"
    },
    {
        "linguagem": "python",
        "descricao": "função que inverte uma string",
        "codigo": "def inverter_string(texto: str) -> str:\n    return texto[::-1]"
    },
    {
        "linguagem": "python",
        "descricao": "função que calcula a média de uma lista de números",
        "codigo": "def media(numeros: list[float]) -> float:\n    if not numeros:\n        return 0.0\n    return sum(numeros) / len(numeros)"
    },
    {
        "linguagem": "python",
        "descricao": "função que conta quantas vezes um elemento aparece em uma lista",
        "codigo": "def contar_elemento(lista: list, elemento) -> int:\n    return lista.count(elemento)"
    },
    {
        "linguagem": "python",
        "descricao": "função que remove caracteres duplicados de uma string",
        "codigo": "def remover_duplicados(texto: str) -> str:\n    resultado = ''\n    for char in texto:\n        if char not in resultado:\n            resultado += char\n    return resultado"
    },
    {
        "linguagem": "python",
        "descricao": "função que converte temperatura de celsius para fahrenheit",
        "codigo": "def celsius_para_fahrenheit(celsius: float) -> float:\n    return (celsius * 9/5) + 32"
    },
    {
        "linguagem": "python",
        "descricao": "função que encontra o maior número em uma lista",
        "codigo": "def maior_numero(numeros: list[float]) -> float:\n    if not numeros:\n        raise ValueError('Lista vazia')\n    return max(numeros)"
    },
    {
        "linguagem": "python",
        "descricao": "função que verifica se uma string é palíndromo",
        "codigo": "def eh_palindromo(texto: str) -> bool:\n    texto_limpo = texto.lower().replace(' ', '')\n    return texto_limpo == texto_limpo[::-1]"
    },
    {
        "linguagem": "python",
        "descricao": "função que retorna os números pares de uma lista",
        "codigo": "def filtrar_pares(numeros: list[int]) -> list[int]:\n    return [n for n in numeros if n % 2 == 0]"
    },
    # JavaScript
    {
        "linguagem": "javascript",
        "descricao": "criar uma função que soma dois números",
        "codigo": "function somar(a, b) {\n  return a + b;\n}"
    },
    {
        "linguagem": "javascript",
        "descricao": "função que verifica se um número é par",
        "codigo": "function ehPar(n) {\n  return n % 2 === 0;\n}"
    },
    {
        "linguagem": "javascript",
        "descricao": "função que inverte uma string",
        "codigo": "function inverterString(texto) {\n  return texto.split('').reverse().join('');\n}"
    },
    {
        "linguagem": "javascript",
        "descricao": "função que calcula a média de uma lista de números",
        "codigo": "function media(numeros) {\n  if (numeros.length === 0) return 0;\n  return numeros.reduce((a, b) => a + b, 0) / numeros.length;\n}"
    },
    {
        "linguagem": "javascript",
        "descricao": "função que remove duplicados de um array",
        "codigo": "function removerDuplicados(arr) {\n  return [...new Set(arr)];\n}"
    },
    {
        "linguagem": "javascript",
        "descricao": "função que converte temperatura de celsius para fahrenheit",
        "codigo": "function celsiusParaFahrenheit(celsius) {\n  return (celsius * 9/5) + 32;\n}"
    },
    {
        "linguagem": "javascript",
        "descricao": "função que verifica se uma string é palíndromo",
        "codigo": "function ehPalindromo(texto) {\n  const limpo = texto.toLowerCase().replace(/\\s/g, '');\n  return limpo === limpo.split('').reverse().join('');\n}"
    },
    {
        "linguagem": "javascript",
        "descricao": "função que agrupa array de objetos por propriedade",
        "codigo": "function agruparPor(arr, chave) {\n  return arr.reduce((acc, item) => {\n    const grupo = item[chave];\n    acc[grupo] = acc[grupo] || [];\n    acc[grupo].push(item);\n    return acc;\n  }, {});\n}"
    },
    # TypeScript
    {
        "linguagem": "typescript",
        "descricao": "criar uma função que soma dois números",
        "codigo": "function somar(a: number, b: number): number {\n  return a + b;\n}"
    },
    {
        "linguagem": "typescript",
        "descricao": "função que verifica se um número é par",
        "codigo": "function ehPar(n: number): boolean {\n  return n % 2 === 0;\n}"
    },
    {
        "linguagem": "typescript",
        "descricao": "função genérica que filtra itens de um array por predicado",
        "codigo": "function filtrar<T>(arr: T[], predicado: (item: T) => boolean): T[] {\n  return arr.filter(predicado);\n}"
    },
    {
        "linguagem": "typescript",
        "descricao": "função que faz debounce de outra função",
        "codigo": "function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {\n  let timer: ReturnType<typeof setTimeout>;\n  return (...args: Parameters<T>) => {\n    clearTimeout(timer);\n    timer = setTimeout(() => fn(...args), delay);\n  };\n}"
    },
    {
        "linguagem": "typescript",
        "descricao": "função que converte temperatura de celsius para fahrenheit com tipos",
        "codigo": "function celsiusParaFahrenheit(celsius: number): number {\n  return (celsius * 9/5) + 32;\n}"
    },
    {
        "linguagem": "typescript",
        "descricao": "interface e função para resposta de API",
        "codigo": "interface ApiResponse<T> {\n  data: T;\n  status: number;\n  message: string;\n}\n\nfunction criarResposta<T>(data: T, status: number = 200, message: string = 'OK'): ApiResponse<T> {\n  return { data, status, message };\n}"
    },
]

# Formatar para fine-tuning
formatted = []
for item in dataset:
    prompt = f"Linguagem: {item['linguagem']}\nDescrição: {item['descricao']}\nCódigo:"
    response = f" {item['codigo']}"
    formatted.append({"text": f"{prompt}{response}"})

# Salvar
with open("dataset_ptbr_code.jsonl", "w", encoding="utf-8") as f:
    for item in formatted:
        f.write(json.dumps(item, ensure_ascii=False) + "\n")

print(f"Dataset criado: {len(formatted)} exemplos")
print(f"Python: {sum(1 for d in dataset if d['linguagem'] == 'python')}")
print(f"JavaScript: {sum(1 for d in dataset if d['linguagem'] == 'javascript')}")
print(f"TypeScript: {sum(1 for d in dataset if d['linguagem'] == 'typescript')}")

3.3 Fine-tuning com LoRA

#!/usr/bin/env python3
"""
02_finetune_ptbr_code.py
Fine-tuning do Phi-2 para gerar código a partir de descrições em PT-BR.
"""
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model, TaskType
from trl import SFTTrainer
import torch

MODEL_NAME = "microsoft/phi-2"
DATASET_PATH = "dataset_ptbr_code.jsonl"
OUTPUT_DIR = "./modelo-ptbr-code"

# Carregar modelo e tokenizer
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    torch_dtype=torch.float16,
    trust_remote_code=True,
    low_cpu_mem_usage=True,
)

# LoRA config — treina apenas 0.15% dos parâmetros
lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    target_modules=["q_proj", "k_proj", "v_proj", "dense"],
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

# Carregar dataset
dataset = load_dataset("json", data_files=DATASET_PATH, split="train")

# Configurar treinamento
training_args = TrainingArguments(
    output_dir=OUTPUT_DIR,
    num_train_epochs=5,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=2,
    learning_rate=2e-4,
    warmup_steps=50,
    logging_steps=5,
    save_strategy="epoch",
    fp16=True,
    optim="adamw_torch",
    report_to="none",
)

# Trainer
trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    dataset_text_field="text",
    max_seq_length=256,
    tokenizer=tokenizer,
)

print("Iniciando fine-tuning...")
trainer.train()

model.save_pretrained(OUTPUT_DIR)
tokenizer.save_pretrained(OUTPUT_DIR)
print(f"Modelo salvo em {OUTPUT_DIR}")

3.4 Inferência com o Modelo Fine-tuned

#!/usr/bin/env python3
"""
03_modelo_ptbr_code.py
Usa o modelo fine-tuned para gerar código a partir de descrições em PT-BR.
"""
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
import torch

BASE_MODEL = "microsoft/phi-2"
ADAPTER_DIR = "./modelo-ptbr-code"

def carregar_modelo():
    """Carrega modelo base + adapters LoRA."""
    tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True)

    base_model = AutoModelForCausalLM.from_pretrained(
        BASE_MODEL,
        torch_dtype=torch.float16,
        trust_remote_code=True,
        low_cpu_mem_usage=True,
    )

    model = PeftModel.from_pretrained(base_model, ADAPTER_DIR)
    model = model.merge_and_unload()

    return model, tokenizer

def gerar_codigo(model, tokenizer, linguagem: str, descricao: str, max_tokens: int = 200) -> str:
    """Gera código a partir de descrição em PT-BR."""
    prompt = f"Linguagem: {linguagem}\nDescrição: {descricao}\nCódigo:"

    inputs = tokenizer(prompt, return_tensors="pt")
    with torch.no_grad():
        outputs = model.generate(
            **inputs,
            max_new_tokens=max_tokens,
            temperature=0.3,
            do_sample=True,
            top_p=0.9,
            pad_token_id=tokenizer.eos_token_id,
            repetition_penalty=1.1,
        )

    resultado = tokenizer.decode(
        outputs[0][inputs["input_ids"].shape[1]:],
        skip_special_tokens=True,
    )
    return resultado.strip()

def main():
    print("Carregando modelo PT-BR Code...")
    model, tokenizer = carregar_modelo()
    print("Modelo carregado!\n")

    # Testes
    testes = [
        ("python", "função que calcula o fatorial de um número"),
        ("javascript", "função que ordena um array de objetos por idade"),
        ("typescript", "função genérica que faz merge de dois objetos"),
        ("python", "função que lê um arquivo CSV e retorna uma lista de dicionários"),
        ("javascript", "função que faz requisição HTTP com retry"),
    ]

    for linguagem, descricao in testes:
        print(f"{'='*60}")
        print(f"Linguagem: {linguagem}")
        print(f"Descrição: {descricao}")
        print(f"Código gerado:")
        codigo = gerar_codigo(model, tokenizer, linguagem, descricao)
        print(codigo)
        print()

if __name__ == "__main__":
    main()

3.5 Demo ao Vivo na Entrevista

Kaique (rodando o modelo):

$ python 03_modelo_ptbr_code.py

Carregando modelo PT-BR Code...
Modelo carregado!

============================================================
Linguagem: python
Descrição: função que calcula o fatorial de um número
Código gerado:
def fatorial(n: int) -> int:
    if n < 0:
        raise ValueError("Número negativo")
    if n <= 1:
        return 1
    return n * fatorial(n - 1)

============================================================
Linguagem: javascript
Descrição: função que ordena um array de objetos por idade
Código gerado:
function ordenarPorIdade(arr) {
  return arr.sort((a, b) => a.idade - b.idade);
}

============================================================
Linguagem: typescript
Descrição: função genérica que faz merge de dois objetos
Código gerado:
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

Rafael (impressionado): O modelo gera código com tipos em TypeScript a partir de uma descrição em português. Quantos exemplos você usou para treinar?

Kaique: 24 exemplos — 10 em Python, 8 em JavaScript e 6 em TypeScript. Com LoRA, treinei apenas 4 milhões de parâmetros (0.15% do modelo de 2.7B). O treinamento levou 8 minutos em uma GPU A100. Em CPU, levaria cerca de 2 horas.

Rafael: E a qualidade? O código gerado é sempre correto?

Kaique: Não sempre. Para casos básicos (funções puras, operações simples), a taxa de acerto é de cerca de 85%. Para casos complexos (async/await, generics avançados), cai para 50-60%. Mas para um modelo de 2.7B com 24 exemplos, é surpreendente. Com 200-500 exemplos e mais épocas de treinamento, a qualidade melhora significativamente.


PARTE 4 — Perguntas Finais (20 min)

4.1 Deploy e Produção

Rafael: Como você colocaria esse modelo em produção?

Kaique: Três opções, dependendo do orçamento:

Opção 1 — API local (custo zero):

  • FastAPI servindo o modelo
  • Docker containerizado
  • Bom para MVP e demonstração

Opção 2 — API gerenciada (custo médio):

  • Deploy em GPU instance (AWS g5.xlarge ou similar)
  • vLLM para serving otimizado (batching, quantização)
  • Auto-scaling baseado em latência

Opção 3 — Quantização + edge (custo baixo):

  • GGUF quantization (4-bit) via llama.cpp
  • Roda em CPU comuns
  • Ideal para mobile ou IoT

Trade-offs:

  • Opção 1: custo zero, latência alta (~2-5s), sem escala
  • Opção 2: custo ~$1/hora, latência baixa (~200ms), escala horizontal
  • Opção 3: custo mínimo, latência média (~1-2s), escala vertical

4.2 Avaliação do Modelo

Rafael: Como você mediria a qualidade do modelo em produção?

Kaique: Métricas em três níveis:

Nível 1 — Automático:

  • Exact Match: o código gerado é idêntico ao esperado?
  • BLEU/ROUGE: similaridade de tokens
  • Compilação: o código gerado compila sem erro? (Python: compile(), JS/TS: tsc --noEmit)

Nível 2 — LLM-as-Judge:

  • Prompt para GPT-4 avaliar: "O código abaixo é uma implementação correta da descrição X?"
  • Escala 1-5 em: correção, legibilidade, cobertura de edge cases

Nível 3 — Humano:

  • Amostragem de 5% das gerações para revisão humana
  • Taxa de aceitação: % de código que o desenvolvedor aceitaria usar

4.3 Pergunta Final

Rafael: Por que deveríamos contratar você?

Kaique: Porque eu não sou apenas um desenvolvedor que usa APIs de IA — eu construo sistemas de IA. Eu sei treinar modelos, sei avaliar modelos, sei colocar modelos em produção. E eu sei explicar o porquê de cada decisão técnica.

Nesta entrevista, eu demonstrei:

  1. Conhecimento teórico profundo (Transformers, RAG, LoRA)
  2. Habilidade prática (calculadora CLI em Python e TypeScript)
  3. Capacidade de criar modelos proprietários (fine-tuning com LoRA)
  4. Visão de produção (deploy, avaliação, monitoramento)
  5. Comunicação clara (expliquei cada decisão)

E o mais importante: eu estou sempre aprendendo. O modelo que mostrei hoje com 24 exemplos e 2.7B parâmetros, na próxima semana já pode ter 500 exemplos e 7B parâmetros. Eu não paro.


Resumo da Avaliação

CompetênciaAvaliaçãoNota
Teoria (Transformers, RAG)Explicou self-attention, pipeline RAG, alucinações✅ Excelente
Live coding PythonCalculadora CLI completa com histórico✅ Passou
Live coding TypeScriptCalculadora CLI completa com tipos✅ Passou
Modelo proprietárioFine-tuned Phi-2 com LoRA para PT-BR + código✅ Passou
Deploy e produçãoAPI com FastAPI, Docker, vLLM, quantização✅ Excelente
Avaliação de modelosMétricas automáticas, LLM-as-judge, humano✅ Excelente
ComunicaçãoClaro, conciso, técnico sem ser pedante✅ Excelente

Resultado: APROVADO


Dica Final

Se você chegou até aqui, tem tudo o que precisa para passar em qualquer entrevista de engenharia de IA. O segredo não é saber tudo — é saber o suficiente bem e demonstrar na prática.

Código que funciona vale mais que teoria decorada. Modelo que roda vale mais que slide bonito. E explicação clara vale mais que jargão técnico.

Boa sorte na sua entrevista.

On this page