EBIKEBUILDER/ai-skills

Use esta habilidade ao construir ou atualizar assistentes de chat, codificação ou editores de documentos/espaços de trabalho com o Google Gemini (@google/genai) para usar conversas multi-turno com estado, em vez de reenviar bases de código, documentos ou contexto completos a cada solicitação.

O que é ai-skills?

Esta habilidade resolve o problema comum de assistentes de IA que tratam cada solicitação como uma requisição única e sem estado, reenviando todo o documento, base de código ou estado do espaço de trabalho a cada pergunta de acompanhamento ou edição sequencial. Isso desperdiça tokens, aumenta a latência e degrada a qualidade do raciocínio. A solução proposta é manter uma conversa multi-turno com estado, onde o contexto inicial é estabelecido no Turno 1 e os turnos subsequentes continuam a mesma conversa sem reenviar o estado inalterado. A habilidade oferece dois padrões principais: o Padrão 1 usa a API de Interações do Google (@google/genai versão >= 2.3.0), onde o Gemini mantém o contexto da conversa nos servidores do Google através de `previous_interaction_id`. O Padrão 2 é gerenciado pelo aplicativo, onde o backend armazena os turnos em seu próprio banco de dados e utiliza um array de turnos `contents` com função `user`/`model`, sincronizando o estado do espaço de trabalho com hashes SHA-256 para evitar o reenvio redundante de arquivos inalterados. Inclui também uma implementação de referência em TypeScript para construir requisições multi-turno, além de uma lista de verificação para implementação correta.

Funciona com~Claude Code~Codex CLI~Cursor✓Gemini CLI
npx skills add https://github.com/EBIKEBUILDER/ai-skills/tree/HEAD/gemini-multiturn-conversation

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Conversas Multi-Turno com Estado no Gemini (@google/genai)

Problema Central que Esta Habilidade Resolve

Muitos assistentes de IA tratam acidentalmente cada solicitação como uma requisição única e sem estado—reenviando o documento, a base de código ou o estado do espaço de trabalho completo a cada pergunta de acompanhamento ou edição sequencial, ou achatando turnos anteriores em um único bloco de texto. Isso desperdiça tokens, aumenta a latência e degrada a qualidade do raciocínio.

Em vez disso, mantenha uma conversa multi-turno com estado onde:

  1. O contexto inicial (código, documento ou estado do projeto) é estabelecido no Turno 1.
  2. Turnos subsequentes continuam a mesma conversa (previous_interaction_id ou turnos estruturados user/model em contents) sem reenviar o estado inalterado.
  3. O código completo ou estado do documento é reenviado apenas quando o usuário edita manualmente o espaço de trabalho fora do fluxo da IA (detectado via comparação de hash SHA-256).

Padrão 1: Estado Multi-Turno Gerenciado pelo Servidor (API de Interações)

Ao usar a API de Interações do Google (@google/genai >= 2.3.0), o Gemini mantém o contexto da conversa nos servidores do Google (store: true por padrão). Encadeie os turnos passando previous_interaction_id: interaction.id:

TypeScript (@google/genai)

import { GoogleGenAI } from "@google/genai";

const client = new GoogleGenAI({});

// Turno 1: Estabelece a conversa e o estado inicial do espaço de trabalho/documento
const turn1 = await client.interactions.create({
  model: "gemini-3.7-flash",
  system_instruction: "Você é um assistente de codificação especialista. Lembre-se do código ativo entre os turnos.",
  input: `Aqui está meu arquivo atual:\n\`\`\`ts\n${initialCode}\n\`\`\`\nAdicione validação de entrada.`,
});

console.log(turn1.output_text);

// Turno 2: Pergunta de acompanhamento ou edição sequencial — NÃO reenvie o arquivo se inalterado!
// O servidor do Google já mantém a entrada do Turno 1 e a saída do modelo via previous_interaction_id.
const turn2 = await client.interactions.create({
  model: "gemini-3.7-flash",
  system_instruction: "Você é um assistente de codificação especialista. Lembre-se do código ativo entre os turnos.",
  previous_interaction_id: turn1.id,
  input: "Por que você escolheu esse padrão de validação, e podemos também registrar entradas rejeitadas?",
});

console.log(turn2.output_text);

Python (google-genai)

from google import genai

client = genai.Client()

# Turno 1: Estabelece o contexto inicial
turn1 = client.interactions.create(
    model="gemini-3.7-flash",
    system_instruction="Você é um assistente de codificação especialista. Lembre-se do código ativo entre os turnos.",
    input=f"Aqui está meu arquivo atual:\n```py\n{initial_code}\n```\nRefatore isso em uma classe.",
)

# Turno 2: Encadeia com previous_interaction_id sem reenviar o arquivo
turn2 = client.interactions.create(
    model="gemini-3.7-flash",
    system_instruction="Você é um assistente de codificação especialista. Lembre-se do código ativo entre os turnos.",
    previous_interaction_id=turn1.id,
    input="Agora adicione um método de teste unitário para casos extremos.",
)

Regras importantes para interactions.create:

  • system_instruction, tools e generation_config têm escopo de interação—reespecifique-os a cada turno.
  • Mantenha store: true (o padrão); definir store: false desabilita previous_interaction_id.

Padrão 2: contents Multi-Turno Gerenciado pelo Aplicativo com Estado do Espaço de Trabalho Sincronizado por Hash

Quando seu backend armazena turnos de conversa em seu próprio banco de dados (ou usa ai.models.generateContent / ai.chats.create com esquemas JSON estruturados e fluxos de trabalho de diff/aplicação), siga estas cinco regras para nunca reenviar arquivos ou seções do espaço de trabalho inalterados de forma redundante:

1. Use Arrays de Turnos Nativos role: 'user' | 'model'

Nunca achate turnos anteriores em uma única string dentro de uma mensagem de usuário. Sempre construa um array contents multi-turno adequado:

type ContentTurn = {
  role: "user" | "model";
  parts: Array<{ text: string }>;
};

2. Rastreie a Sincronização do Espaço de Trabalho por SHA-256 Hash

Mantenha um registro de sincronização por conversa no cliente:

interface SyncedConversationState {
  activeDocumentSha256: string | null;
  secondaryContextHash: string | null; // ex.: outras abas abertas, cenas ou resumo da árvore do projeto
}
  • Quando um turno ask (P&R) é concluído: activeDocumentSha256 permanece o SHA-256 do documento que foi enviado.
  • Quando um turno edit é concluído com uma proposta: activeDocumentSha256 é atualizado para proposal.sha256 (porque o turno model no histórico da conversa agora contém o código proposto).
  • Quando o usuário clica em Aplicar: activeDocumentSha256 corresponde ao proposal.sha256 aplicado.
  • Quando o usuário clica em Descartar ou Desfazer: activeDocumentSha256 reverte para o base.sha256 anterior à proposta.

3. Omita Código do Espaço de Trabalho Inalterado em Turnos de Acompanhamento (sourceSynchronized: true)

Antes de enviar um novo turno, compare sha256(currentEditorCode) com syncedState.activeDocumentSha256:

  • Se sha256(currentEditorCode) === syncedState.activeDocumentSha256 (sourceSynchronized: true) :
    • No modo Ask (P&R) : Não envie o arquivo (source: null). O modelo já tem o código ativo exato no histórico da conversa.
    • No modo Edit : Não duplique currentSource dentro do novo turno user enviado ao Gemini ({ mode: "edit", prompt, sourceSynchronized: true }). Mantenha o snapshot base local apenas para diff/Desfazer no lado do cliente e validação no backend.
  • Se sha256(currentEditorCode) !== syncedState.activeDocumentSha256 (sourceSynchronized: false) :
    • O usuário editou manualmente o arquivo ou mudou de arquivo. Anexe o currentSource atualizado neste turno para ressincronizar a conversa e atualize activeDocumentSha256.

4. Mantenha a Fonte Completa Apenas Uma Vez no Histórico (Turnos Obsoletos Compactados)

Em uma sessão de edição de 10 turnos, não mantenha 10 cópias completas do arquivo em todos os 10 turnos históricos.

  • Mantenha o código completo apenas no turno mais recente que introduziu ou modificou o código ativo (seja o turno model que propôs a edição ativa mais recente, ou o turno user que anexou o código editado manualmente pela última vez).
  • Para todos os turnos mais antigos cujo código foi substituído, substitua a string de código inline por metadados compactos ({ supersededSourceSha256: "..." }). Isso mantém o uso de tokens estável em sessões longas, preservando ao mesmo tempo o encadeamento completo do raciocínio conversacional.

5. Desduplique Contexto Secundário Somente Leitura (Outros Arquivos / Abas / Seções)

Se seu aplicativo inclui um resumo somente leitura de arquivos ou seções não ativos:

  • Envie esse resumo no Turno 1 da conversa.
  • No Turno 2+, reenvie o resumo apenas se seu hash mudou (secondaryContextHash !== currentSecondaryContextHash).

Implementação de Referência: Construtor de Requisição Multi-Turno (TypeScript)

interface HistoryTurn {
  prompt: string;
  response: string;
  mode: "ask" | "edit";
  baseSource?: string | null;
  baseSha256?: string | null;
  proposalSource?: string | null;
  proposalSha256?: string | null;
  outcomeStatus?: "none" | "proposed" | "applied" | "discarded" | "undone";
}

export function buildMultiTurnContents(params: {
  history: HistoryTurn[];
  mode: "ask" | "edit";
  prompt: string;
  currentSource: string | null;
  sourceSynchronized: boolean;
}) {
  const { history, mode, prompt, currentSource, sourceSynchronized } = params;

  // Identifica qual turno histórico contém a fonte atualmente ativa
  let latestActiveSourceIndex = -1;
  if (sourceSynchronized || currentSource === null) {
    for (let i = history.length - 1; i >= 0; i--) {
      const item = history[i];
      const hasValidProposal =
        item.mode === "edit" &&
        item.proposalSource &&
        item.outcomeStatus !== "discarded" &&
        item.outcomeStatus !== "undone";
      if (hasValidProposal || item.baseSource) {
        latestActiveSourceIndex = i;
        break;
      }
    }
  }

  const contents: Array<{ role: "user" | "model"; parts: Array<{ text: string }> }> = [];

  for (let i = 0; i < history.length; i++) {
    const item = history[i];
    const isLatestSourceTurn = i === latestActiveSourceIndex;
    const hasActiveProposal =
      item.mode === "edit" &&
      Boolean(item.proposalSource) &&
      item.outcomeStatus !== "discarded" &&
      item.outcomeStatus !== "undone";

    // Turno do usuário no histórico
    const userPayload: Record<string, unknown> = {
      mode: item.mode ?? "ask",
      prompt: item.prompt,
    };
    if (isLatestSourceTurn && !hasActiveProposal && item.baseSource) {
      userPayload.currentSource = item.baseSource;
    } else if (item.baseSha256) {
      userPayload.supersededSourceSha256 = item.baseSha256;
    }
    contents.push({ role: "user", parts: [{ text: JSON.stringify(userPayload) }] });

    // Turno do modelo no histórico
    const modelPayload: Record<string, unknown> = {
      kind: item.mode === "edit" ? "code_proposal" : "answer",
      explanation: item.response,
    };
    if (isLatestSourceTurn && hasActiveProposal && item.proposalSource) {
      modelPayload.proposedSource = item.proposalSource;
    } else if (item.proposalSha256) {
      modelPayload.supersededProposalSha256 = item.proposalSha256;
    }
    contents.push({ role: "model", parts: [{ text: JSON.stringify(modelPayload) }] });
  }

  // Turno final (atual) do usuário
  const omitDuplicateSource = sourceSynchronized && latestActiveSourceIndex !== -1;
  const currentTurnPayload: Record<string, unknown> = { mode, prompt };
  if (omitDuplicateSource) {
    currentTurnPayload.sourceSynchronized = true;
  } else {
    currentTurnPayload.currentSource = currentSource;
  }

  contents.push({ role: "user", parts: [{ text: JSON.stringify(currentTurnPayload) }] });
  return contents;
}

Lista de Verificação ao Revisar ou Implementar IA Multi-Turno

  • Estamos usando @google/genai (GoogleGenAI) e nunca o obsoleto @google/generative-ai?
  • O Turno 2+ preserva funções multi-turno user / model (ou previous_interaction_id) em vez de achatar o histórico em uma única string de solicitação?
  • Em perguntas de acompanhamento (modo Ask) quando o código do editor não mudou, omitimos o reenvio do código do cliente?
  • Em edições sequenciais de IA (modo Edit) quando o código do editor corresponde ao último turno, omitimos o currentSource duplicado do novo prompt do usuário em contents?
  • Versões de código obsoletas mais antigas são compactadas no histórico, de modo que apenas a versão ativa do código é enviada por completo?
  • Se o usuário editar manualmente o documento ou desfizer uma alteração, a incompatibilidade de SHA-256 aciona automaticamente a ressincronização no próximo turno?

Habilidades Relacionadas