Salta al contenuto principale

Prompt Engineering per la generazione di codice: tecniche e best practice

Guida pratica alle tecniche di prompt engineering per generare codice affidabile con modelli LLM. Few-shot, chain-of-thought, role prompting, structured output, iterazione e valutazione. Esempi reali con Ollama on-prem.

Cosa ne pensi di questo articolo?

Prompt Engineering per la generazione di codice: tecniche e best practice
AI EngineeringFoto:RDNE Stock projectPexels

In Breve

Guida pratica alle tecniche di prompt engineering per generare codice affidabile con modelli LLM. Few-shot, chain-of-thought, role prompting, structured output, iterazione e valutazione. Esempi reali con Ollama on-prem.

Il prompt engineering per la generazione di codice è l'arte di formulare richieste precise a un modello LLM (Large Language Model) come llama3.1:8b su Ollama, in modo da ottenere codice affidabile, manutenibile e direttamente utilizzabile. A differenza della generazione di testo libero, il prompt engineering per il codice richiede rigore: specificità dell'intent, format strutturato, esempi concreti (few-shot), e ragionamento passo dopo passo (chain-of-thought). Questa guida copre le tecniche provate in produzione sul backend di Federico Calò—dalle basi del prompt structure fino alla valutazione automatica dell'output e all'iterazione intelligente. Useremo Ollama on-prem locale e Python per integrare il flusso end-to-end, evitando dipendenze da provider cloud.

Introduzione: Perché il Prompt Engineering per il Codice Importa

Nel 2026, la generazione di codice via LLM è un dato di fatto: feature flag implementation, boilerplate reduction, refactoring scaffolding. Ma la qualità del codice generato è direttamente proporzionale alla qualità del prompt. Un prompt vago genera codice vago; un prompt preciso, con esempi e vincoli chiari, genera codice production-ready.

La sfida: i modelli LLM on-prem come llama3.1:8b hanno vincoli di context window (8k token per Ollama standard) e non hanno visto l'intero codebase del tuo progetto. Quindi il prompt deve essere auto-contenuto e sufficientemente specifico da disambiguare il task.

Questa guida insegna i 6 pilastri del prompt engineering per il codice:

  • Spec-first prompting: dichiarare intent e output format prima della richiesta
  • Few-shot learning: fornire esempi concreti nel prompt stesso
  • Chain-of-thought reasoning: guidare il modello a ragionare passo dopo passo
  • Role prompting: assegnare un ruolo al modello per contest awareness
  • Structured output: forzare il modello a output JSON, codice, o format specifici
  • Iterazione e feedback: refine i prompt in base ai risultati reali

Pilastro 1: Spec-First Prompting — Il Foundation

Cosa significa Spec-First

Spec-first prompting significa scrivere il prompt come se fosse una specifica tecnica breve: intent chiarissimo, input/output formato definito, edge case nominati. Non è prosa naturale; è una specifica che il modello esegue.


import requests
import json

# Endpoint Ollama locale
OLLAMA_URL = "http://localhost:11434/api/generate"
MODEL = "llama3.1:8b"

def ollama_generate(prompt: str, temperature: float = 0.3) -> str:
    """Chiama Ollama on-prem e ritorna il testo generato."""
    response = requests.post(
        OLLAMA_URL,
        json={
            "model": MODEL,
            "prompt": prompt,
            "stream": False,
            "temperature": temperature,
            "num_predict": 500
        }
    )
    return response.json()["response"]

# Prompt vago (ANTI-PATTERN)
vague_prompt = """
Scrivi una funzione Python che valida un email.
"""

# Spec-first prompt (PATTERN CORRETTO)
spec_first_prompt = """You are a Python function expert. Generate a Python function with:
INPUT:
- function name: validate_email
- parameter: email (string)
OUTPUT:
- Return: boolean (True if valid, False otherwise)
- Validation rules:
  1. Must contain exactly one '@'
  2. Local part (before @) must be 1-64 alphanumeric/dots/hyphens
  3. Domain (after @) must have at least one dot
  4. TLD must be 2-6 characters
- No external libraries (use re module only)
- Include 3 docstring examples

FUNCTION:
"""

result = ollama_generate(spec_first_prompt)
print(result)
  

La differenza è drammatica. Il prompt specifico genera una funzione con type hints, docstring, e validazione corretta. Il prompt vago genera spesso pseudo-codice o logica incompleta.

Pilastro 2: Few-Shot Learning — Insegnare via Esempi

Cos'è Few-Shot

Few-shot learning significa fornire 1-3 esempi completi di input→output nel prompt, prima di chiedere il vero task. Il modello impara dal pattern e lo replica per la nuova richiesta.


# Few-shot prompt per generare API endpoint
few_shot_prompt = """Generate a FastAPI endpoint from the description. Follow the pattern:

EXAMPLE 1:
Description: Get user by ID
Response Format: JSON with id, name, email
Expected endpoint: GET /users/{user_id}

Code:
@app.get("/users/{user_id}")
async def get_user(user_id: int):
    user = await db.fetch_one("SELECT * FROM users WHERE id = $1", user_id)
    if not user:
        raise HTTPException(status_code=404)
    return {"id": user.id, "name": user.name, "email": user.email}

---

EXAMPLE 2:
Description: Create a new article with title and content
Response Format: JSON with id, title, created_at
Expected endpoint: POST /articles

Code:
@app.post("/articles")
async def create_article(article: ArticleRequest):
    article_id = await db.fetch_val(
        "INSERT INTO articles (title, content) VALUES ($1, $2) RETURNING id",
        article.title, article.content
    )
    return {"id": article_id, "title": article.title, "created_at": datetime.now()}

---

NOW:
Description: Delete an article by ID, return success status
Response Format: JSON with status and message
Expected endpoint: DELETE /articles/{article_id}

Code:
"""

result = ollama_generate(few_shot_prompt, temperature=0.2)
print(result)
  

Few-shot è potente perché il modello non deve inferire il pattern da zero; lo vede negli esempi e lo replica. Temperature bassa (0.2-0.3) rende il modello deterministico e fedele al pattern.

Pilastro 3: Chain-of-Thought Reasoning

Guidare il Modello a Ragionare Passo dopo Passo

Chain-of-thought prompting chiede al modello di scrivere il suo ragionamento prima di generare il codice. Questo riduce i bug logici.


# Chain-of-thought prompt per un algoritmo complesso
cot_prompt = """Task: Generate a Python function to find the longest substring without repeating characters.

Before writing code, think through the approach:
1. Problem statement: Return the length of longest substring without repeating chars
2. Algorithm: Use a sliding window with a set to track chars in current window
3. Data structure: dict to store last seen index of each char
4. Edge cases:
   - Empty string → return 0
   - All unique chars → return length
   - All same chars → return 1
5. Complexity: O(n) time, O(min(n, charset_size)) space

Now generate the code:

def longest_substring_without_repeating(s: str) -> int:
"""

result = ollama_generate(cot_prompt, temperature=0.2)
print(result)
  

Chiedere il ragionamento "fuori dal codice" rende il modello più preciso. È come se stessi facendo rubber duck debugging con l'AI.

Pilastro 4: Role Prompting — Assegnare un Ruolo

Contesto e Persona

Role prompting assegna un ruolo al modello ("Sei un senior backend engineer di Python", "Sei un expert di query SQL ottimizzate"). Migliora la qualità e la tone of voice del codice.


# Role prompting per SQL optimization
role_prompt = """You are a senior SQL database architect at Google Cloud.
Your job: optimize slow queries for PostgreSQL 16 with 1M+ rows.

Task: Given a slow query, rewrite it for performance.

Slow query:
SELECT u.id, u.name, COUNT(a.id) as article_count
FROM users u
LEFT JOIN articles a ON u.id = a.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id
ORDER BY article_count DESC
LIMIT 10;

Optimize for:
1. Add missing indexes
2. Avoid full table scans
3. Use EXPLAIN ANALYZE to verify
4. Include index creation DDL

OPTIMIZED VERSION:
"""

result = ollama_generate(role_prompt, temperature=0.2)
print(result)
  

Con il ruolo dichiarato, il modello genera DDL index, suggerimenti di EXPLAIN, e commenti da senior engineer. Senza ruolo, potrebbe non pensarci.

Pilastro 5: Structured Output — Forzare il Format

JSON, Markdown, Codice Specifico

Structured output significa dire esplicitamente: "Output JSON valido" o "Output solo codice Python, nessun commento". Riduce il parsing ambiguo.


# Structured output prompting (JSON format forcing)
structured_prompt = """Generate a JSON schema validation function for a User object.

OUTPUT FORMAT (MUST be valid Python code, no markdown, no comments):
def validate_user(data: dict) -> tuple[bool, list[str]]:
    errors = []

    # Validation rules:
    # - name: required, string, 1-100 chars
    # - email: required, valid email format
    # - age: optional, integer, 18-120
    # - role: optional, must be in ['admin', 'user', 'guest']

    # Return: (is_valid: bool, errors: list of error messages)

def validate_user(data: dict) -> tuple[bool, list[str]]:
"""

result = ollama_generate(structured_prompt, temperature=0.2)
print(result)
  

Dichiarando "OUTPUT FORMAT: Python code, nessun markdown", il modello si attiene a it. Se lo ometti, potrebbe wrappare il codice in triple backticks.

Pilastro 6: Iterazione e Feedback Loop

Refine in Base ai Risultati

Un prompt non è mai perfetto al primo colpo. Iterare: generare, valutare, refine.


import re

def validate_prompt_output(code: str) -> tuple[bool, list[str]]:
    """Valida il codice generato prima di usarlo."""
    issues = []

    # Check 1: Contiene almeno una def
    if not re.search(r'^\s*def\s+\w+', code, re.MULTILINE):
        issues.append("No function definition found")

    # Check 2: Syntax valido
    try:
        compile(code, '', 'exec')
    except SyntaxError as e:
        issues.append(f"Syntax error: {e}")

    # Check 3: Type hints presenti
    if not re.search(r'->\s*\w+', code):
        issues.append("Missing type hints on return")

    return len(issues) == 0, issues

# Iterazione loop
prompt = """Generate a function to parse CSV rows into dataclass instances."""
max_iterations = 3
for i in range(max_iterations):
    result = ollama_generate(prompt, temperature=0.2)
    is_valid, issues = validate_prompt_output(result)

    if is_valid:
        print(f"✓ Valid on iteration {i+1}")
        break
    else:
        print(f"✗ Iteration {i+1} issues: {issues}")
        # Refine prompt per la prossima iterazione
        prompt += f"\nFix: {issues[0]}"
  

La validazione automatica + feedback loop trasforma un prompt mediocre in uno buono in poche iterazioni.

Gestione del Context Window: Token Budget

Massimizzare l'uso dei Token

Ollama con llama3.1:8b ha ~8k token di context. Un prompt lungo + esempi + generazione di output lungo = overflow. Strategia: be concise, prioritize examples, truncate marginally useful context.


def estimate_tokens(text: str) -> int:
    """Stima rozza del numero di token (1 token ≈ 4 char)."""
    return len(text) // 4

def build_optimized_prompt(
    spec: str,
    examples: list[tuple[str, str]],
    task: str,
    max_context_tokens: int = 6000
) -> str:
    """Costruisce un prompt entro il budget di token."""
    lines = [
        "You are a code generation expert.",
        "",
        "SPECIFICATION:",
        spec,
        ""
    ]

    # Aggiungi esempi fino al budget
    current_tokens = estimate_tokens("\n".join(lines))
    for i, (input_ex, output_ex) in enumerate(examples):
        example_text = f"\nEXAMPLE {i+1}:\nInput: {input_ex}\nOutput: {output_ex}"
        if current_tokens + estimate_tokens(example_text) < max_context_tokens * 0.7:
            lines.append(example_text)
            current_tokens += estimate_tokens(example_text)

    lines.append(f"\nTASK:\n{task}")
    return "\n".join(lines)

# Utilizzo
spec = "Generate a Python function that validates a credit card number using Luhn algorithm."
examples = [
    ("4532015112830366", "True"),
    ("1234567890123456", "False"),
]
task = "def validate_cc(card_number: str) -> bool:"

optimized = build_optimized_prompt(spec, examples, task)
print(f"Prompt tokens: {estimate_tokens(optimized)}")
result = ollama_generate(optimized)
  

Case Study Reale: Generare un Parser SQL

Applicazione End-to-End

Immagina di voler generare un parser SQL semplice per SELECT queries. Applicheremo tutti i pilastri.


sql_parser_prompt = """You are a Python expert in parsing SQL queries.

TASK: Generate a simple SQL SELECT query parser class.

SPECIFICATION:
- Class name: SimpleSelectParser
- Input: SELECT query string (e.g., "SELECT id, name FROM users WHERE age > 18")
- Output: dict with keys: columns (list), table (str), where_condition (str or None)
- No external SQL libraries (no sqlparse)
- Must handle: SELECT col1, col2 FROM table WHERE condition

EXAMPLE:
Input: "SELECT id, name FROM users WHERE age > 18"
Output: {"columns": ["id", "name"], "table": "users", "where_condition": "age > 18"}

REASONING:
1. Use regex to extract SELECT clause, FROM clause, WHERE clause
2. Split columns on comma, strip whitespace
3. Extract table name after FROM
4. Extract everything after WHERE

CODE:
"""

result = ollama_generate(sql_parser_prompt, temperature=0.2)

# Validazione e test
test_input = 'SELECT id, email FROM customers WHERE status = "active"'
try:
    exec(result)
    parser = eval("SimpleSelectParser()")
    output = parser.parse(test_input)
    print(f"Parsed: {output}")
except Exception as e:
    print(f"Error: {e}")
    print("Refining prompt...")
  

Errori Comuni e Come Evitarli

Anti-Pattern Frequenti

1. Prompt troppo vago: "Scrivi un algoritmo di sort". Il modello genera bubble sort, quicksort, o merge sort a caso. Soluzione: specificare quale algoritmo e con quale complessità.

2. Contesto incompleto: Non dare abbastanza background sul progetto. Il modello non sa se la funzione deve integrare con PostgreSQL, Redis, o una API esterna. Soluzione: includere brevi commenti con dipendenze e constraint.

3. Nessuna validazione dell'output: Assumere che il codice generato sia sempre corretto. Soluzione: sempre compilare, testare con 1-2 case semplici, controllare type hints.

4. Temperature troppo alta: Con temperature > 0.5, il modello diverge da esempi e spec. Soluzione: usare temperature 0.1-0.3 per codice, 0.5-0.7 per testo creativo.

Best Practice per la Produzione

  • Versionare i prompt: Tieni un file prompt_v1.txt, prompt_v2.txt, ecc. Documenta cosa è cambiato e perché.
  • Caching dei prompt: Se usi lo stesso prompt 100 volte, cachela risposta. Redis + hash della richiesta.
  • Monitoraggio della qualità: Traccia quanti output generati passano la validazione. Target: >90%.
  • Fallback deterministico: Se Ollama è down o l'output è invalido, ritorna un template generico o chiedi all'utente.
  • Rate limiting consapevole: Ollama on-prem ha risorse finite. Limita 10 req/min per utente, queue il resto.

FAQ

D: Quanto è affidabile il codice generato da Ollama?

R: Dipende dal prompt. Con spec-first + few-shot + chain-of-thought + validazione, il tasso di successo è 85-95% per task semplici-intermedi (funzioni <100 righe, algoritmi classici). Per task complessi (architetture distribuite, ottimizzazioni numeriche), scendi a 50-70%. Sempre: generare, validare, rivedere manualmente, testare.

D: Posso usare un servizio AI cloud al posto di Ollama?

R: Tecnicamente sì, ma questo articolo e il backend di Federico Calò usano SOLO Ollama on-prem per privacy, zero costi ricorrenti, e control totale. I servizi AI cloud a consumo aggiungono costi ($0.01-0.02 per richiesta) e dipendenze esterne. Se hai già Ollama, usalo.

D: Come mantengo la coerenza di stile nel codice generato?

R: Nel prompt, cita uno snippet del tuo stile (nomi di variabili, struttura, import). Few-shot funziona: se dai 2 esempi nel tuo stile, il generato seguirà quello. Alternativa: post-process con Black (Python), Prettier (JS), o uno formatter automatico.

D: Quanti token consumo per richiesta?

R: Rough: spec (100-300 tok) + esempi (200-500 tok) + task (50-200 tok) + output (100-500 tok) = 500-1500 token per richiesta. Ollama non ha costi per token (è on-prem), quindi iterare è free. Con un servizio a consumo, calcoleresti costi di prompt + completion token e ottimizzeresti.

D: Il modello fallisce spesso. Cosa faccio?

R: Iterazione + feedback. Traccia gli errori, aggiorna il prompt con vincoli che prevengono quel tipo di errore, e riprova. Esempio: se il modello dimentica i type hints, aggiungi "MUST INCLUDE: Python 3.9+ type hints on all function parameters and returns" al prompt. Versiona e tieni story di miglioramenti.

Conclusione

Il prompt engineering per il codice non è una scienza astratta; è una skill pratica con ROI immediato: meno boilerplate da scrivere, meno bug, deploy più veloci. I 6 pilastri (spec-first, few-shot, chain-of-thought, role prompting, structured output, iterazione) sono il playbook verificato in produzione. Usa Ollama on-prem per evitare costi e dipendenze cloud, valida sempre l'output generato, e iterare fino a coverage >90%. Il prossimo passo: integrare questo workflow nel tuo CI/CD—genera codice, valida, testa automaticamente, e deploy se tutto passa.

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti