Gerenciamento de Sessões na Prompt API — Contexto e Tokens
Na Prompt API, toda interação com o Gemini Nano acontece dentro de uma sessão. Sessões mantêm o contexto da conversa, controlam o uso de tokens e gerenciam o ciclo de vida da inferência. Se você não entender como elas funcionam, vai acabar criando sessões à toa, estourando contexto e desperdiçando recursos.
O que é uma sessão
Uma sessão é o objeto retornado por LanguageModel.create(). Ela encapsula:
- O contexto conversacional (histórico de prompts e respostas)
- A janela de contexto (limite máximo de tokens)
- Os prompts iniciais (system prompt e contexto prévio)
- As configurações de modalidade (texto, imagem, áudio)
const session = await LanguageModel.create({
initialPrompts: [
{ role: 'system', content: 'You are a helpful assistant.' }
],
expectedInputs: [{ type: 'text', languages: ['en'] }],
expectedOutputs: [{ type: 'text', languages: ['en'] }]
});
Context Window: contextUsage e contextWindow
Cada sessão tem um limite de tokens que consegue processar. Monitore o uso com duas propriedades:
// Total de tokens disponíveis na sessão
console.log(session.contextWindow); // ex: 4096
// Tokens já consumidos (prompts + respostas anteriores)
console.log(session.contextUsage); // ex: 512
// Tokens restantes
const disponivel = session.contextWindow - session.contextUsage;
console.log(`Disponível: ${disponivel} tokens`);
| Propriedade | Tipo | Descrição |
|---|---|---|
session.contextWindow |
number |
Capacidade total da janela de contexto |
session.contextUsage |
number |
Tokens consumidos até o momento |
Monitorando uso em tempo real
async function promptComMonitoramento(session, texto) {
const antes = session.contextUsage;
const resposta = await session.prompt(texto);
const depois = session.contextUsage;
console.log(`Tokens usados neste turno: ${depois - antes}`);
console.log(`Capacidade restante: ${session.contextWindow - depois}`);
return resposta;
}
Context Overflow
Quando a janela de contexto fica cheia e um novo prompt chega, o Chrome remove automaticamente pares de prompt+resposta antigos (do início da conversa) pra liberar espaço. A exceção é o system prompt — esse nunca é removido.
Detectando overflow
Use o evento contextoverflow para reagir quando tokens antigos são descartados:
session.addEventListener('contextoverflow', () => {
console.warn('Context overflow! Mensagens antigas foram removidas.');
// Opções de reação:
// 1. Avisar o usuário
mostrarAviso('Parte do histórico foi descartada para continuar a conversa.');
// 2. Criar nova sessão com resumo
criarNovaSessaoComResumo();
// 3. Simplesmente continuar (comportamento padrão)
});
QuotaExceededError
Se não for possível liberar tokens suficientes (por exemplo, um único prompt é maior que a janela), um QuotaExceededError é lançado:
try {
const resposta = await session.prompt(textoMuitoLongo);
} catch (error) {
if (error.name === 'QuotaExceededError') {
console.error(`Prompt muito longo!`);
console.error(`Tokens do input: ${error.requested}`);
console.error(`Tokens disponíveis: ${error.contextWindow}`);
// Dividir o texto ou usar resumo
const textoCurto = textoMuitoLongo.substring(0, 2000);
const resposta = await session.prompt(textoCurto);
}
}
initialPrompts: configurando o contexto inicial
O initialPrompts define o contexto da sessão no momento da criação. Aceita um array de mensagens com roles system, user e assistant:
const session = await LanguageModel.create({
initialPrompts: [
// System prompt: define comportamento (nunca é removido por overflow)
{
role: 'system',
content: 'You are a senior JavaScript developer. Answer concisely with code examples.'
},
// Contexto de conversa prévia (opcional)
{ role: 'user', content: 'What is a closure?' },
{
role: 'assistant',
content: 'A closure is a function that captures variables from its lexical scope...'
},
// O próximo prompt continuará a partir deste contexto
{ role: 'user', content: 'Can you show a practical example?' }
]
});
// O modelo já tem todo o contexto e responderá sobre closures
const resposta = await session.prompt('What about memory leaks with closures?');
Caso de uso: Restaurar sessão após reinício do navegador
// Salvar contexto antes de fechar
function salvarContexto(historico) {
localStorage.setItem('chat_historico', JSON.stringify(historico));
}
// Restaurar ao reabrir
async function restaurarSessao() {
const historico = JSON.parse(localStorage.getItem('chat_historico') || '[]');
const session = await LanguageModel.create({
initialPrompts: [
{ role: 'system', content: 'You are a helpful assistant.' },
...historico
]
});
return session;
}
append(): adicionando contexto depois da criação
O método append() permite adicionar mensagens ao contexto da sessão após a criação, sem solicitar uma resposta do modelo. Isso é útil pra:
- Pré-processar inputs multimodais enquanto o usuário interage
- Adicionar contexto incremental antes de pedir inferência
- Preparar o modelo com dados que chegam aos poucos
const session = await LanguageModel.create({
initialPrompts: [
{
role: 'system',
content: 'You are an image analyst. Compare images when asked.'
}
],
expectedInputs: [{ type: 'image' }, { type: 'text', languages: ['en'] }]
});
// Usuário faz upload da primeira imagem
fileInput1.onchange = async () => {
await session.append([
{
role: 'user',
content: [
{ type: 'text', value: 'Here is the first image:' },
{ type: 'image', value: fileInput1.files[0] }
]
}
]);
console.log('Primeira imagem processada');
};
// Usuário faz upload da segunda imagem
fileInput2.onchange = async () => {
await session.append([
{
role: 'user',
content: [
{ type: 'text', value: 'Here is the second image:' },
{ type: 'image', value: fileInput2.files[0] }
]
}
]);
console.log('Segunda imagem processada');
};
// Quando pronto, solicita a comparação
btnComparar.onclick = async () => {
const analise = await session.prompt('Compare both images in detail.');
document.getElementById('resultado').textContent = analise;
};
A Promise retornada por append() resolve quando a mensagem foi validada e processada. Ela rejeita se a mensagem não puder ser adicionada.
clone(): bifurcando uma sessão
O clone() cria uma cópia da sessão com todo o contexto e configurações preservados. Na minha experiência, os melhores usos são:
- Testar continuações diferentes de uma conversa
- Preservar um “checkpoint” antes de uma interação pesada
- Criar ramificações de conversa
const session = await LanguageModel.create({
initialPrompts: [
{ role: 'system', content: 'You are a creative writing assistant.' }
]
});
// Desenvolver uma história
await session.prompt('Start a mystery story set in a library.');
// Clonar antes de continuar — preserva o ponto atual
const sessaoAlternativa = await session.clone();
// Caminho A: continua na sessão original
const caminhoA = await session.prompt('The detective finds a hidden door.');
// Caminho B: continua na sessão clonada
const caminhoB = await sessaoAlternativa.prompt('The lights suddenly go out.');
Clone com AbortSignal
const controller = new AbortController();
const clonada = await session.clone({
signal: controller.signal
});
// Pode cancelar a sessão clonada independentemente
controller.abort();
destroy(): liberando recursos
Quando uma sessão não é mais necessária, destrua pra liberar memória e recursos de GPU:
const session = await LanguageModel.create();
const resposta = await session.prompt('Quick question: what is 2+2?');
// Liberar recursos
session.destroy();
// Qualquer uso posterior lança erro
try {
await session.prompt('Another question'); // ERRO!
} catch (error) {
console.error('Sessão destruída:', error.message);
}
Quando destruir
- Quando o componente/página que usa a sessão é desmontado
- Após completar uma tarefa isolada (classificação batch, por exemplo)
- Quando o usuário navega para outra seção da aplicação
- Em caso de erro irrecuperável
Quando NÃO destruir
- Se o usuário pode voltar e continuar a conversa
- Se você pretende reutilizar a sessão em breve (criar sessão tem custo)
- Em componentes persistentes (sidebar de chat, por exemplo)
Estratégias de gerenciamento
Estratégia 1: sessão por feature
Crie uma sessão dedicada pra cada funcionalidade da aplicação:
class AIFeatures {
constructor() {
this.sessions = {};
}
async getSessao(feature) {
if (!this.sessions[feature]) {
const configs = {
classificador: {
initialPrompts: [
{ role: 'system', content: 'Classify content. Answer: positive, negative, or neutral.' }
]
},
resumidor: {
initialPrompts: [
{ role: 'system', content: 'Summarize text in 2-3 sentences.' }
]
},
extrator: {
initialPrompts: [
{ role: 'system', content: 'Extract structured data from text.' }
]
}
};
this.sessions[feature] = await LanguageModel.create(configs[feature]);
}
return this.sessions[feature];
}
destruirTodas() {
Object.values(this.sessions).forEach(s => s.destroy());
this.sessions = {};
}
}
Estratégia 2: rotação de sessão por contexto
Quando o contexto fica cheio, crie uma nova sessão com resumo do que veio antes:
async function promptComRotacao(session, texto, historico) {
try {
return await session.prompt(texto);
} catch (error) {
if (error.name === 'QuotaExceededError') {
// Criar nova sessão com resumo do contexto
const resumo = historico.slice(-5).map(h =>
`${h.role}: ${h.content.substring(0, 100)}`
).join('\n');
session.destroy();
const novaSessao = await LanguageModel.create({
initialPrompts: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: `Previous context summary:\n${resumo}` },
{ role: 'assistant', content: 'Understood. I have the context. How can I help?' }
]
});
return { sessao: novaSessao, resposta: await novaSessao.prompt(texto) };
}
throw error;
}
}
Estratégia 3: pool de sessões
Pra aplicações com uso intensivo, manter um pool funciona bem:
class SessionPool {
constructor(maxSessions = 3) {
this.max = maxSessions;
this.available = [];
this.inUse = new Set();
}
async acquire() {
if (this.available.length > 0) {
const session = this.available.pop();
this.inUse.add(session);
return session;
}
if (this.inUse.size < this.max) {
const session = await LanguageModel.create();
this.inUse.add(session);
return session;
}
// Esperar uma sessão ficar disponível
return new Promise(resolve => {
const check = setInterval(() => {
if (this.available.length > 0) {
clearInterval(check);
resolve(this.acquire());
}
}, 100);
});
}
release(session) {
this.inUse.delete(session);
this.available.push(session);
}
}
measureContextUsage: medindo antes de enviar
Use session.measureContextUsage() pra saber quanto um prompt vai consumir antes de mandá-lo:
const uso = await session.measureContextUsage('A very long prompt...');
console.log(`Este prompt consumirá: ${uso} tokens`);
if (session.contextUsage + uso > session.contextWindow) {
console.warn('Prompt não cabe no contexto atual');
}
Session compacting: preservando conversas longas
O overflow é reativo: o Chrome descarta pares antigos de prompt+resposta até caber. A compactação é a alternativa proativa — você resume o histórico, destrói a sessão e cria uma nova semeada com os resumos. A conversa continua de onde parou, com custo de tokens bem menor.
O truque que torna isso possível: o browser nunca remove initialPrompts durante overflow de runtime. Então os resumos ficam ancorados permanentemente no contexto.
Os passos:
- Monitore
contextUsagevscontextWindowe mostre ao usuário - Escute
contextoverflowcomo alerta antecipado - Resuma cada mensagem com a Summarizer API (detectando idioma com a Language Detector API)
- Destrua a sessão antiga e crie uma nova com os resumos em
initialPrompts - Mantenha uma cópia do histórico completo para recuperação em caso de erro
Resumindo o histórico
A Summarizer API é a peça central. Primeiro detecte o idioma de cada mensagem para configurar o summarizer corretamente:
async function detectLanguage(text, threshold = 0.7) {
const detector = await LanguageDetector.create();
const results = await detector.detect(text);
if (results.length > 0 && results[0].confidence >= threshold) {
return results[0].detectedLanguage;
}
return null; // confiança baixa — o caller faz fallback para navigator.language
}
Depois crie um summarizer pro idioma detectado. Prefira preference: 'speed' e caia para 'auto' se o modelo rápido não suportar o idioma:
const summarizers = {}; // cache por `${format}:${lang}`
async function getSummarizer(format, lang) {
const key = `${format}:${lang}`;
if (summarizers[key]) return summarizers[key];
const baseOptions = {
type: 'tldr',
format,
length: 'short',
expectedInputLanguages: [lang],
expectedContextLanguages: [lang],
outputLanguage: lang,
};
let options = { ...baseOptions, preference: 'speed' };
let avail = await Summarizer.availability(options);
if (avail === 'unavailable') {
options = { ...baseOptions, preference: 'auto' };
avail = await Summarizer.availability(options);
}
if (avail === 'unavailable') {
throw new Error('Summarizer API indisponível neste dispositivo.');
}
summarizers[key] = await Summarizer.create(options);
return summarizers[key];
}
O format vem do próprio conteúdo — passar 'markdown' para prosa introduce formatação indesejada, e 'plain-text' para Markdown remove code fences:
function looksLikeMarkdown(text) {
return /(?:^#{1,6} |^[-*+] |\d+\. |\*\*|__|\[.+?\]\(|^> |^```)/m.test(text);
}
Com idioma e formato resolvidos, resuma cada mensagem passando um context para o modelo entender que está comprimindo um turno de conversa:
const compacted = [];
for (const msg of history) {
const lang = (await detectLanguage(msg.content)) ?? navigator.language;
const format = looksLikeMarkdown(msg.content) ? 'markdown' : 'plain-text';
const summarizer = await getSummarizer(format, lang);
const summary = await summarizer.summarize(msg.content.trim(), {
context: `This is a ${msg.role} turn from a chat conversation. ` +
`Preserve its key meaning as concisely as possible.`,
});
// Só usa o resumo se ele for realmente mais curto
compacted.push({
role: msg.role,
content: summary.trim().length < msg.content.length
? summary.trim()
: msg.content,
});
}
Destruindo e recriando
session.destroy();
session = null;
// Cria a nova sessão semeada com os resumos
const sessionLangs = confidentLangs.size > 0 ? [...confidentLangs] : [navigator.language];
session = await LanguageModel.create({
expectedInputs: [{ type: 'text', languages: sessionLangs }],
expectedOutputs: [{ type: 'text', languages: sessionLangs }],
initialPrompts: compacted,
});
// Re-registra o handler de overflow na nova sessão
session.addEventListener('contextoverflow', () => {
/* ... */
});
A nova sessão começa com contextUsage bem menor, e o modelo tem os resumos como contexto prévio — então responde sobre tópicos anteriores normalmente.
Tratando erros
Se a sumarização ou a criação da sessão falhar depois da sessão antiga já ter sido destruída, o usuário perde a possibilidade de conversar. Mantenha um array fullHistory que nunca é sobrescrito:
const history = []; // visão da sessão atual, substituída a cada compactação
const fullHistory = []; // todas as mensagens originais, nunca sobrescrita
// No catch:
if (!session) {
session = await LanguageModel.create({
initialPrompts: fullHistory.map(({ role, content }) => ({ role, content })),
});
session.addEventListener('contextoverflow', () => { /* ... */ });
}
Recuperar do fullHistory pode deixar o contexto perto da capacidade de novo, mas pelo menos o usuário volta a um estado funcional e pode compactar de imediato.
Preservando blocos que não podem ser compactados
Partes críticas de uma mensagem — samples de código, por exemplo — podem ser processadas separadamente. O exemplo abaixo divide a mensagem em segmentos alternados de prosa e code fence, resumindo só a prosa:
// Divide o texto em segmentos de prosa e code, alternados
// Retorna [{ type: 'prose'|'code', content }, …]
function splitByCodeFences(text) {
const parts = [];
const re = /^```[^\n]*\n[\s\S]*?^```[ \t]*$/gm;
let lastIndex = 0;
let match;
while ((match = re.exec(text)) !== null) {
if (match.index > lastIndex) {
parts.push({
type: 'prose',
content: text.slice(lastIndex, match.index),
});
}
parts.push({ type: 'code', content: match[0] });
lastIndex = match.index + match[0].length;
}
if (lastIndex < text.length) {
parts.push({ type: 'prose', content: text.slice(lastIndex) });
}
return parts;
}
Perguntas frequentes
Quantas sessões posso criar ao mesmo tempo?
Não tem um limite documentado, mas cada sessão consome memória. Na prática, mantenha o mínimo necessário e destrua as que não estão em uso.
O system prompt é contado na contextWindow?
Sim. O system prompt consome tokens da janela de contexto. Mantenha-o conciso. A diferença é que ele nunca é removido durante overflow — tudo mais é removido antes dele.
Posso alterar o system prompt depois de criar a sessão?
Não. O initialPrompts é imutável após a criação. Pra mudar o system prompt, destrua a sessão e crie uma nova.
O que acontece se eu chamar prompt() numa sessão destruída?
A Promise é rejeitada com um erro. Sempre verifique se a sessão tá ativa antes de usar.
clone() consome memória adicional?
Sim. Uma sessão clonada é independente e consome recursos próprios. Destrua clones que não são mais necessários.