Gemini API Fails to Follow Prompt Instructions Consistently

I’m experiencing significant issues with the Gemini API not following explicit prompt instructions, making it unreliable for production use.

The Problem

I’m trying to re-write the title tag from web pages using URL context. My requirements are simple:

  • Return ONLY the new page title

  • No additional commentary or reasoning

  • Consistent output format

However, Gemini returns the correct output only ~66% of the time. In roughly 1 out of 3 cases, it adds unwanted reasoning or explanations despite explicit instructions not to.

What I’ve Tried

  1. Explicit output format instructions: I clearly specified in the prompt to return only the title tag

  2. Examples in the prompt: I provided clear examples of the expected output format

  3. Negative instructions: I explicitly stated “do not provide anything other than the title”

  4. Setting thinking_budget=0: This parameter seems to be ignored - the model still adds reasoning occasionally

The Constraint

I’m using URL context (via file_data with URLs), which means I cannot use response_mime_type="application/json" to enforce structured output. According to the Gemini API documentation, these two features are mutually exclusive.

Comparison with Other APIs

Both Claude and GPT APIs handle the exact same prompt perfectly, consistently returning only the requested title without additional commentary. This makes Gemini the outlier here.

Why This Matters

This inconsistency makes Gemini API unsuitable for production environments where:

  • Predictable output format is critical

  • Downstream parsing depends on consistent structure

  • You need to process responses at scale without manual verification

Question for the Community

  1. Has anyone else experienced similar issues with prompt adherence in Gemini API?

  2. Are there undocumented parameters or techniques to enforce stricter output compliance when using URL context?

  3. Is there a way to use structured output (JSON) with URL context that I’m missing?

This behavior suggests that Gemini API still has significant maturity gaps compared to competing LLM APIs, particularly in production scenarios requiring reliable, structured responses.

Any insights or workarounds would be greatly appreciated!

1 Like

Hi @G_S2 ,

Welcome to the Forum!!

Could you please let us know which Gemini Model you are using?

1 Like
import os
import time
import logging
import pandas as pd
#from google import genai
#import google.generativeai as genai
from google import genai
from google.genai import types
from google.genai.types import Tool, GenerateContentConfig, GoogleSearch

from dotenv import load_dotenv
from datetime import datetime

def get_improved_titles_from_gemini(pvt_title_df, config, gpt_language):
    """
    Uses Google's Gemini models to improve tag titles based on top queries.

    Args:
        pvt_title_df (pd.DataFrame): DataFrame containing 'page', 'tag title', and 'top_queries'.
        config: Configuration object with settings like 'counter_max'.
        gpt_language (str): The language to use for the prompt.

    Returns:
        pd.DataFrame: The input DataFrame with an added 'new tag title' column.
    """
    if "new tag title" not in pvt_title_df.columns:
        pvt_title_df["new tag title"] = None

    dotenv_path = os.path.join(os.path.dirname(__file__), 'credenziali', '.env')
    load_dotenv(dotenv_path)
    api_key = os.getenv('GEMINI_API_KEY')
    
    if not api_key:
        logging.error("GEMINI_API_KEY not found in .env file or environment variables. Skipping title improvement.")
        return pvt_title_df

    try:
        client = genai.Client(api_key=api_key)
        logging.info('Gemini client created successfully.')
    except Exception as e:
        logging.error(f"Failed to create Gemini client: {e}")
        return pvt_title_df

    gemini_responses = {}
    llm_api_calls = 0
    # update 20250827
    total_tokens_used = 0

    def improve_tag_title(row, gemini_responses):
        nonlocal total_tokens_used, llm_api_calls
        page_url = row["page"]
        tag_title = row["tag title"]
        top_queries = row['top_queries']

        if page_url in gemini_responses:
            return gemini_responses[page_url], False

        prompt = f"""Sei un copywriter SEO e un analista di search intent di livello mondiale, con una profonda esperienza nell'ottimizzare elementi on-page per i motori di ricerca come Google.

#### OBIETTIVO
Il tuo obiettivo è creare il miglior tag title SEO possibile per la pagina web fornita, massimizzando il potenziale di ranking organico e il Click-Through Rate (CTR).

#### CONTESTO
- URL da aprire ed **Analizzare:** '{page_url}'
    - **Tag Title Attuale (da migliorare):** '{tag_title}'
    - **Query Principali (in ordine di importanza):** '{top_queries}'
    - **Devi scrivere il tag title in Lingua:** '{gpt_language}'

#### **PROCESSO DI ANALISI E CREAZIONE**

1.  **Analisi Approfondita del Contenuto e dell'Intento:**
    * Leggi e analizza l'intero contenuto della pagina all'URL fornito.
    * Identifica l'argomento principale, gli argomenti secondari, e soprattutto, l'**intento di ricerca primario** che la pagina soddisfa (es. informativo "come fare", commerciale "migliori x", transazionale "compra y", di navigazione "login pagina z").
    * Comprendi il valore unico o il beneficio chiave che la pagina offre al lettore.

2.  **Estrazione e Prioritizzazione delle Parole Chiave:**
    * Estrai le parole chiave più pertinenti direttamente dal testo, dai titoli (H1, H2, etc.), e dalla struttura generale della pagina.
    * Determina la **query principale** (la più importante e rappresentativa) e le **query secondarie** (che aggiungono contesto e specificità).

3.  **Creazione del Tag Title:**
    * Basandoti sull'analisi, formula un tag title che:
        * Includa la query principale all'inizio, o il più vicino possibile.
        * Integri in modo naturale una o più query secondarie, se lo spazio lo consente e aumenta la pertinenza.
        * Sia **persuasivo**, generando curiosità o comunicando un beneficio chiaro per invogliare al clic.

#### **REGOLE INDEROGABILI**

* **Lunghezza:** Massimo **68 caratteri**, spazi inclusi.
* **Brand:** **Non includere MAi il nome del brand**, a meno che non sia l'argomento centrale della pagina (es. una recensione di un prodotto specifico).
* **Caratteri Speciali:** Usa solo lettere, numeri, e se necessario, il trattino `-` o i due punti `:` come separatori logici. Assolutamente vietati emoji, simboli come ©, ®, ™.
* **Virgolette:** Non racchiudere il NUOVO tag title tra virgolette.
* **Keyword Stuffing:** Evita la ripetizione forzata e innaturale delle parole chiave.
* **Focus:** Il tag title deve riflettere fedelmente e in modo accattivante l'intento ed il contenuto reale della pagina.

#### **OUTPUT FINALE**
* **Caso di Successo:** Restituisci **SOLO ED ESCLUSIVAMENTE** il nuovo tag title ottimizzato. Non scrivere NIENTE altro. Nessuna introduzione, nessuna spiegazione, nessun commento, nessuna etichetta come "Output:".
* **Caso di Errore:** Se non riesci ad accedere o analizzare l'URL per qualsiasi motivo, restituisci **SOLO ED ESCLUSIVAMENTE**: "ERRORE". Non scrivere NIENTE altro.
* **Esempio Output di Successo:** Guida completa alla compilazione del Modulo 730: istruzioni e scadenze
* **Esempio Output di Errore:** Impossibile accedere alla pagina
"""



        tools = []
        tools.append(Tool(url_context=types.UrlContext))
        tools.append(Tool(google_search=types.GoogleSearch))

        try:
            # Usa il client con parametri di configurazione
            response = client.models.generate_content(
                # MODELS
                # https://ai.google.dev/gemini-api/docs/models/
                model="gemini-2.5-flash", 
                # gemini-2.5-pro
                # gemini-2.5-flash
                contents=prompt,
                # 2. Passare lo strumento nella configurazione
                config=types.GenerateContentConfig(
                    tools=tools,
                    temperature=0.5,               # Controllo casualità (0.0-2.0)
                    top_p=0.9,                    # Nucleus sampling (0.0-1.0)
                    top_k=40,                     # Top-k sampling (integer)
                    max_output_tokens=150,       # Token massimi
                    #presence_penalty=0.2,         # Penalità presenza (-2.0 a +2.0)
                    #frequency_penalty=0.0,        # Penalità frequenza (-2.0 a +2.0)
                    #stop_sequences=["STOP"],      # Sequenze di stop (max 5)
                    candidate_count=1,            # Numero candidati (1-8)
                    seed=42,                      # Seed per determinismo
                    response_mime_type="text/plain",  # Formato risposta
                    # Per Gemini 2.5:
                    thinking_config=types.ThinkingConfig(
                        thinking_budget=0  # Disabilita thinking per velocità
                    )
                )
            )
            llm_api_calls += 1

            #total_tokens_used += response.usage_metadata.thoughts_token_count +  response.usage_metadata.candidates_token_count
            total_tokens_used += response.usage_metadata.total_token_count

            improved_tag_title = response.text
            gemini_responses[page_url] = improved_tag_title
            time.sleep(1)
            return improved_tag_title, True

        except Exception as e:
            logging.error(f"Error occurred while calling Gemini API: {e}")
            return None, False

    pvt_title_df.reset_index(drop=True, inplace=True)
    titoli_inseriti = 0
    processed_rows = 0
    skipped_rows = 0

    for index, row in pvt_title_df.iterrows():
        if titoli_inseriti >= config.counter_max:
            logging.info(f"Raggiunto il limite massimo di {config.counter_max} chiamate API. Interrompo l'elaborazione.")
            break
            
        tag_title = row["tag title"]
        if pd.isna(tag_title) or tag_title == "":
            skipped_rows += 1
            continue

        new_tag_title, is_new = improve_tag_title(row, gemini_responses)
        
        if new_tag_title:
            pvt_title_df.at[index, "new tag title"] = new_tag_title
            if is_new:
                titoli_inseriti += 1
            processed_rows += 1

            logging.info(f"URL: {row['page']}")
            logging.info(f"Tag title: {tag_title}")
            logging.info(f"Top queries: {row['top_queries']}")
            logging.info(f'Nuovo tt: {new_tag_title}')
            logging.info(f"API calls {titoli_inseriti} | {total_tokens_used} tokens.")
            logging.info(f"Processed rows: {processed_rows}/{config.counter_max} | {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            logging.info('')

    logging.warning(f"Gemini API calls made: {llm_api_calls}")
    logging.warning(f"Tag title inseriti: {titoli_inseriti}")
    logging.warning(f"Rows skipped due to missing tag title: {skipped_rows}")
    # 20250827 UPDATE
    # ...
    logging.warning(f"Total tokens used: {total_tokens_used}")
   
    return pvt_title_df, llm_api_calls, total_tokens_used

I tested pro and flash, same stuff

1 Like