Pular para o conteúdo

A Mentalidade da Engenharia de IA: O Que Muda ao Construir com LLMs

A engenharia de IA não é apenas engenharia de software com um modelo acoplado. Os ciclos de feedback, modos de falha e sinais de qualidade são fundamentalmente diferentes. Veja como pensar sobre isso.

Durval Pereira
Durval Pereira
6 min

Um tipo diferente de engenharia

A engenharia de software tradicional é determinística em sua essência. Dada a mesma entrada, você espera a mesma saída. Os testes são binários — eles passam ou falham. As implantações funcionam ou não.

A engenharia de IA quebra essa premissa. Ao integrar um LLM em um sistema de produção, você está trabalhando com um componente que é:

  • Não-determinístico: o mesmo prompt pode produzir saídas diferentes
  • Caro por chamada: ordens de magnitude mais custoso do que uma consulta de banco de dados
  • Latência-variável: os tempos de resposta variam de milissegundos a dezenas de segundos
  • Opaco: você não pode inspecionar o "raciocínio" de uma forma depurável
  • Em evolução: o comportamento do modelo muda com atualizações que você não controla

Isso não significa que a engenharia de IA seja mais difícil ou mais fácil do que a engenharia tradicional. Significa que as habilidades, padrões e instintos são diferentes.

O problema da avaliação

Em software tradicional, você escreve um teste:

expect(calculateTax(100, 'CA')).toBe(7.25)

Na engenharia de IA, o equivalente é muito mais difícil:

const response = await llm.complete('Summarize this article...')
// What does "correct" mean here?
// How do you assert quality programmatically?

Este é o desafio central. Você precisa de frameworks de avaliação que sejam rigorosos o suficiente para detectar regressões, mas flexíveis o bastante para acomodar a variação natural nas saídas do modelo.

O padrão que funciona melhor na prática é a avaliação baseada em rubrica: defina um conjunto de critérios, pontue as saídas em relação a esses critérios e acompanhe as pontuações ao longo do tempo.

interface EvaluationRubric {
  criteria: EvaluationCriterion[]
  passingScore: number
}

interface EvaluationCriterion {
  name: string
  description: string
  weight: number
  scorer: (input: string, output: string) => Promise<number>
}

const summarizationRubric: EvaluationRubric = {
  criteria: [
    {
      name: 'completeness',
      description: 'Covers all key points from the source',
      weight: 0.3,
      scorer: async (input, output) => {
        // Extract key entities from input, check coverage in output
        const keyPoints = await extractKeyPoints(input)
        const covered = keyPoints.filter((p) =>
          output.toLowerCase().includes(p.toLowerCase())
        )
        return covered.length / keyPoints.length
      },
    },
    {
      name: 'conciseness',
      description: 'Significantly shorter than the original',
      weight: 0.2,
      scorer: async (input, output) => {
        const ratio = output.length / input.length
        if (ratio < 0.2) return 1.0
        if (ratio < 0.4) return 0.7
        return 0.3
      },
    },
    {
      name: 'accuracy',
      description: 'No hallucinated facts',
      weight: 0.5,
      scorer: async (input, output) => {
        // Use a separate model call to check factual consistency
        return await checkFactualConsistency(input, output)
      },
    },
  ],
  passingScore: 0.75,
}

Essa abordagem fornece um número que você pode rastrear, alertar e usar em CI. Não é perfeita — mas é muito melhor do que a revisão manual ou nenhuma avaliação.

Prompt engineering é API design

Trate seus prompts como interfaces, não como strings. Um prompt é o contrato de API entre a lógica da sua aplicação e o modelo. Ele merece o mesmo rigor que qualquer outra API.

interface PromptTemplate<TInput, TOutput> {
  name: string
  version: string
  template: (input: TInput) => string
  parser: (raw: string) => TOutput
  examples: Array<{ input: TInput; expectedOutput: TOutput }>
}

const classifyIntent: PromptTemplate<
  { message: string },
  { intent: string; confidence: number }
> = {
  name: 'classify-intent',
  version: '2.1',
  template: ({ message }) => `Classify the user intent for the following message.

Respond with a JSON object containing "intent" and "confidence" (0-1).

Valid intents: question, feedback, complaint, request, other

Message: "${message}"

JSON response:`,
  parser: (raw) => {
    const parsed = JSON.parse(raw.trim())
    return {
      intent: parsed.intent,
      confidence: Math.min(1, Math.max(0, parsed.confidence)),
    }
  },
  examples: [
    {
      input: { message: 'How do I reset my password?' },
      expectedOutput: { intent: 'question', confidence: 0.95 },
    },
  ],
}

Versionar prompts, testá-los contra exemplos e tratar a análise da saída como uma preocupação de primeira classe são as práticas que separam os sistemas de IA de produção dos protótipos.

A equação de custos

Chamadas de LLM são caras. Um sistema que faz uma chamada de classe GPT-4 para cada interação do usuário terá custos de infraestrutura que escalam linearmente com o tráfego — diferente dos sistemas tradicionais onde os custos escalam sub-linearmente com cache e otimização.

As principais estratégias:

Faça cache agressivamente. Se dois usuários fazem perguntas semanticamente semelhantes, a resposta provavelmente é a mesma. O cache semântico — usando similaridade de embeddings para detectar entradas equivalentes — pode reduzir o volume de chamadas em 30-60%.

Use o menor modelo que funcione. GPT-4 nem sempre é necessário. Para tarefas de classificação, extração e formatação, modelos menores são mais rápidos, mais baratos e, muitas vezes, tão precisos quanto.

Processe em lote quando possível. Muitos provedores de LLM oferecem APIs de lote com descontos significativos. Se o seu caso de uso tolera alguma latência, o processamento em lote pode reduzir os custos em 50% ou mais.

Pré-calcule onde puder. Se você está usando um LLM para gerar descrições de produtos, faça isso uma vez no momento da publicação — não em cada visualização de página.

Modos de falha são diferentes

Sistemas tradicionais falham com exceções, timeouts e códigos de erro. Sistemas de IA têm um modo de falha mais sutil: eles retornam algo que parece correto, mas não é.

Isso significa que você precisa de guardrails:

async function safeLLMCall<T>(
  prompt: string,
  parser: (raw: string) => T,
  validator: (result: T) => boolean,
  retries = 2
): Promise<T | null> {
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      const raw = await llm.complete(prompt)
      const parsed = parser(raw)

      if (validator(parsed)) {
        return parsed
      }

      // Output parsed but failed validation — retry with adjusted prompt
      continue
    } catch {
      // Parse failure — retry
      continue
    }
  }

  return null // All attempts failed — fall back gracefully
}

A função validator é a chave. Ela codifica sua lógica de negócios sobre como uma saída válida deve ser. Sem ela, você está confiando completamente no modelo — e essa confiança deve ser sempre verificada.

O que isso significa para sua equipe

Se você está integrando IA em um sistema de produção, sua equipe precisa desenvolver novas habilidades:

  1. Conforto com saídas probabilísticas. Nem toda resposta será perfeita. Defina "bom o suficiente" explicitamente.
  2. Desenvolvimento orientado por avaliação. Escreva avaliações antes de escrever prompts, assim como você escreveria testes antes do código.
  3. Consciência de custos. Todo engenheiro deve entender o custo por chamada e o custo total por funcionalidade.
  4. Degradação graciosa. Toda funcionalidade alimentada por IA deve ter um fallback não-IA.

A engenharia de IA é engenharia de verdade. Apenas requer um conjunto diferente de instintos.


Próximo nesta série: construindo um sistema RAG de produção que realmente funciona.

Tagsaillmengineering-practicesproduction