← blog
JavaScript Gemini API Firebase AI

Gemini API: multi-turn conversation and dynamic system prompt

How to queue messages in the contents array to simulate a conversation with Gemini API, and build a system prompt that serializes live Firebase data on every call without extra queries.

Basic call

A single call to Gemini requires a system_instruction and a contents array with the user's message.

gemini.js — chiamata base
async function askGemini(userMessage, systemPrompt) {
  const GEMINI_KEY = '[GEMINI_API_KEY]';
  const MODEL     = 'gemini-2.5-flash-lite';
  const ENDPOINT  = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent?key=${GEMINI_KEY}`;

  const response = await fetch(ENDPOINT, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      system_instruction: { parts: [{ text: systemPrompt }] },
      contents: [{ parts: [{ text: userMessage }] }]
    })
  });

  const data = await response.json();
  if (!response.ok) throw new Error(data?.error?.message || 'Errore API');

  return data.candidates[0].content.parts[0].text;
}

Warning: never put the API key directly in the frontend of a public app. For an internal panel with restricted access it's an acceptable tradeoff; alternatively, store it as a Netlify environment variable and call it via a Netlify Function proxy.

Dynamic system prompt from Firebase state

The key is building the system prompt on the fly, serializing the current state already held in memory (synced from Firebase), without extra queries on every message.

gemini.js — system prompt dinamico
function buildSystemPrompt() {
  const mese   = getCurrentMonthLabel();   // es. "Giugno 2026"
  const utente = sessionStorage.getItem('panelUser') || 'Sconosciuto';

  // Classifica operatori con totali per categoria — già in memoria, no query
  const rankingText = Object.entries(state.operatori)
    .map(([nome, dati]) =>
      `${nome}: ${dati.totale} ordini (Cat-A: ${dati.catA}, Cat-B: ${dati.catB})`
    ).join('\n');

  return `Sei un assistente interno. Rispondi sempre in italiano, in modo diretto e professionale.

=== CONTESTO ATTUALE ===
Utente connesso: ${utente}
Mese di riferimento: ${mese}

=== OPERATORI E RISULTATI ===
${rankingText}

=== CONOSCENZA AZIENDALE ===
[Blocco statico — procedure, prodotti, glossario]
`;
}

Static vs live knowledge: the static block holds things that change rarely (product structure, core procedures, glossary) — written directly as a string in the source. The dynamic block holds everything that changes daily (who sold what, this month's leads), read from Firebase on the fly.

Multi-turn conversation: queuing messages

The Gemini API has no memory of its own. To simulate a multi-exchange conversation, each subsequent call must include in the contents array all previous messages — alternating role: "user" and role: "model".

gemini.js — multi-turn
let conversationHistory = []; // resettata all'apertura del pannello

async function sendAiMessage(userText) {
  // Aggiunge il turno utente alla storia
  conversationHistory.push({
    role: 'user',
    parts: [{ text: userText }]
  });

  const response = await fetch(ENDPOINT, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      system_instruction: { parts: [{ text: buildSystemPrompt() }] },
      contents: conversationHistory  // intera storia ad ogni chiamata
    })
  });

  const data   = await response.json();
  const aiText = data.candidates[0].content.parts[0].text;

  // Aggiunge la risposta del modello alla storia
  conversationHistory.push({
    role: 'model',
    parts: [{ text: aiText }]
  });

  return aiText;
}

Model versioning: Gemini deprecates models quickly for new accounts. Always verify the chosen model's availability before shipping to production — a valid model name today can return a 404 in a few weeks.

Related article
AI assistant in the management panel with Gemini API — dynamic system prompt from Firebase

When to use this multi-turn pattern with the Gemini API

This snippet shows how to manage a multi-turn conversation with the Gemini API in JavaScript, appending messages to the contents array and building a dynamic system prompt that serializes live Firebase data on every call.

When to use it

When NOT to use it

Alternatives

Common mistakes

Frequently asked questions about multi-turn conversations with the Gemini API

By appending every message (both the user's and the model's) to the contents array of the next request, so the model receives the full relevant history on every call.

Because live data can change between one conversation turn and the next: by serializing it on every call, the model always has access to the application's most up-to-date state, not an initial snapshot.

Yes, every model has a maximum context token limit: very long conversations need to be truncated or summarized to stay within the limit, avoiding errors or excessive costs.