SQL Server 2025 e l’Integrazione AI Nativa

Nota importante sulla costruzione del prompt

Il punto 6 – STEP 5, dedicato alla costruzione del prompt, è volutamente la parte più curata di tutta la stored procedure.
Tutte le istruzioni inviate al motore AI sono scritte in italiano, anche quando il risultato finale deve essere generato in un’altra lingua.

Questa scelta non è casuale. I modelli di linguaggio moderni comprendono perfettamente l’italiano e rispondono in modo molto affidabile quando viene loro chiesto esplicitamente in quale lingua produrre l’output.
Scrivere il prompt nella propria lingua rende il codice più leggibile, più facile da manutenere e soprattutto più immediato da comprendere durante la fase di test e sperimentazione.

L’aspetto davvero critico non è la lingua del prompt, ma la chiarezza delle istruzioni.
Per questo motivo l’indicazione sulla lingua di output viene ripetuta in modo esplicito e rafforzata (“IMPORTANTE”, “INTERO messaggio”, “completamente in…”), così da evitare risposte miste o parzialmente tradotte.

In altre parole:

è preferibile scrivere prompt chiari e ben strutturati nella propria lingua, piuttosto che prompt in inglese meno precisi.

Questo approccio si è rivelato particolarmente efficace nella demo multilingua della Fabbrica di Babbo Natale e rappresenta una buona pratica anche in contesti reali, soprattutto quando il team che lavora sul codice utilizza l’italiano come lingua principale.

CREATE OR ALTER PROCEDURE dbo.sp_GeneraEmailBambino
    @IDBambino INT,
    @TipoNotifica NVARCHAR(50) = N'ConfermaLetterina',
    @Oggetto NVARCHAR(500) OUTPUT,
    @CorpoEmail NVARCHAR(MAX) OUTPUT,
    @Errore NVARCHAR(MAX) OUTPUT
AS
BEGIN
    SET NOCOUNT ON;

    /* ============================================================
       0) CONFIG AI (LAB / DEMO) - dichiarata inline per semplicità
       ============================================================ */

    -- >>> Inserisci qui la tua API KEY (LAB). Non committare mai in repo pubblici.
    DECLARE @ApiKey NVARCHAR(200) = N'YOUR_API_KEY_HERE';

    -- Modello e versione API (Google AI / Gemini). Personalizza se necessario.
    DECLARE @ModelName NVARCHAR(100) = N'gemini-2.5-flash';
    DECLARE @ApiVersion NVARCHAR(10) = N'v1';

    -- Parametri di generazione (tuning)
    DECLARE @Temperature DECIMAL(3,2) = 0.90;  -- creatività/variabilità
    DECLARE @MaxOutputTokens INT = 8000;       -- limite output
    DECLARE @TopP DECIMAL(3,2) = 0.95;
    DECLARE @TopK INT = 64;

    -- Endpoint completo
    DECLARE @url NVARCHAR(4000) =
        N'https://generativelanguage.googleapis.com/' +
        @ApiVersion + N'/models/' + @ModelName +
        N':generateContent?key=' + @ApiKey;

    /* ============================================================
       1) Variabili di lavoro
       ============================================================ */

    -- Variabili REST
    DECLARE @payload NVARCHAR(MAX);
    DECLARE @response NVARCHAR(MAX);
    DECLARE @ret INT;
    DECLARE @resultJSON NVARCHAR(MAX);

    -- Dati bambino e contesto
    DECLARE @Nome NVARCHAR(100), @Cognome NVARCHAR(100);
    DECLARE @Email NVARCHAR(150), @Eta INT;
    DECLARE @CodiceLingua CHAR(2), @NomeLingua NVARCHAR(50);
    DECLARE @CodiceISO CHAR(5);
    DECLARE @StatoLista NVARCHAR(20);
    DECLARE @PunteggioComportamento INT;

    DECLARE @IDLetterina INT;
    DECLARE @ListaRegali NVARCHAR(MAX);
    DECLARE @ComportamentiPositivi NVARCHAR(MAX);

    -- Prompt finale
    DECLARE @promptCompleto NVARCHAR(MAX);

    BEGIN TRY

        /* ============================================================
           2) STEP 1 - Recupero dati bambino + lingua
           ============================================================ */

        SELECT 
            @Nome = b.Nome,
            @Cognome = b.Cognome,
            @Email = b.Email,
            @Eta = b.Eta,
            @CodiceLingua = b.CodiceLingua,
            @NomeLingua = l.NomeLingua,
            @CodiceISO = l.CodiceISO639,
            @StatoLista = b.StatoLista,
            @PunteggioComportamento = b.PunteggioComportamento
        FROM dbo.Bambini b
        INNER JOIN dbo.Lingue l ON b.CodiceLingua = l.CodiceLingua
        WHERE b.IDBambino = @IDBambino;

        IF @Nome IS NULL
        BEGIN
            SET @Errore = N'Bambino non trovato con ID: ' + CAST(@IDBambino AS NVARCHAR(20));
            RETURN -1;
        END

        /* ============================================================
           3) STEP 2 - Recupero ultima letterina del bambino
           ============================================================ */

        SELECT TOP (1) 
            @IDLetterina = IDLetterina
        FROM dbo.Letterine
        WHERE IDBambino = @IDBambino
        ORDER BY DataRicezione DESC;

        /* ============================================================
           4) STEP 3 - Aggrego lista regali (se presenti)
           ============================================================ */

        IF @IDLetterina IS NOT NULL
        BEGIN
            SELECT @ListaRegali = STRING_AGG(NomeRegalo, N', ')
            FROM dbo.RegaliRichiesti
            WHERE IDLetterina = @IDLetterina;
        END

        /* ============================================================
           5) STEP 4 - Recupero comportamenti positivi (ultimi 5)
           ============================================================ */

        SELECT @ComportamentiPositivi = STRING_AGG(DescrizioneAzione, N'; ')
        FROM (
            SELECT TOP (5) DescrizioneAzione
            FROM dbo.RegistroComportamenti
            WHERE IDBambino = @IDBambino
              AND TipoComportamento = 'Positivo'
            ORDER BY DataAzione DESC
        ) AS Pos;

        /* ============================================================
           6) STEP 5 - Costruzione PROMPT (la parte più importante)
           ============================================================ */

        -- 6.1 Ruolo / tono (imposta lo “stile”)
        SET @promptCompleto =
            N'Sei un elfo simpatico e magico dell''officina di Babbo Natale al Polo Nord. ';

        -- 6.2 Destinatario (età = linguaggio adeguato)
        SET @promptCompleto +=
            N'Scrivi una email natalizia calorosa e magica per ' + @Nome +
            N' (età ' + CAST(@Eta AS NVARCHAR(10)) + N' anni). ';

        -- 6.3 Contesto (stato e punteggio)
        SET @promptCompleto +=
            N'Il bambino è nella lista "' + ISNULL(@StatoLista, N'') +
            N'" con un punteggio comportamento di ' +
            CAST(ISNULL(@PunteggioComportamento, 0) AS NVARCHAR(10)) + N'/100. ';

        -- 6.4 Dettagli personalizzati (opzionali)
        IF @ListaRegali IS NOT NULL AND LEN(@ListaRegali) > 0
            SET @promptCompleto += N'Regali richiesti: ' + @ListaRegali + N'. ';

        IF @ComportamentiPositivi IS NOT NULL AND LEN(@ComportamentiPositivi) > 0
            SET @promptCompleto += N'Buone azioni recenti: ' + @ComportamentiPositivi + N'. ';

        -- 6.5 Vincoli formato (evita muri di testo)
        SET @promptCompleto +=
            N'Scrivi 2-3 paragrafi brevi, in tono gioioso. Usa 1-2 emoji natalizi. ';

        -- 6.6 Istruzione CRITICA sulla lingua (per evitare mix)
        SET @promptCompleto +=
            N'IMPORTANTE: Scrivi l''INTERO messaggio in lingua ' + @NomeLingua +
            N' (' + @CodiceISO + N'). ' +
            N'Il messaggio deve essere completamente in ' + @NomeLingua +
            N', inclusi saluti e firma.';

        /* ============================================================
           7) STEP 6 - Escape per JSON (evita payload non valido)
           ============================================================ */

        -- Escape minimo per proteggere backslash e virgolette
        SET @promptCompleto = REPLACE(@promptCompleto, N'\', N'\\');
        SET @promptCompleto = REPLACE(@promptCompleto, N'"', N'\"');
        SET @promptCompleto = REPLACE(@promptCompleto, CHAR(13), N'');
        SET @promptCompleto = REPLACE(@promptCompleto, CHAR(10), N' ');

        /* ============================================================
           8) STEP 7 - Costruzione payload JSON per Google AI
           ============================================================ */

        -- Nota: qui costruiamo un JSON “a mano” per semplicità (demo)
        SET @payload =
            N'{' +
            N'"contents":[{"parts":[{"text":"' + @promptCompleto + N'"}]}],' +
            N'"generationConfig":{' +
                N'"temperature":' + CAST(@Temperature AS NVARCHAR(20)) + N',' +
                N'"maxOutputTokens":' + CAST(@MaxOutputTokens AS NVARCHAR(20)) + N',' +
                N'"topP":' + CAST(@TopP AS NVARCHAR(20)) + N',' +
                N'"topK":' + CAST(@TopK AS NVARCHAR(20)) +
            N'}' +
            N'}';

        /* ============================================================
           9) STEP 8 - Chiamata REST al modello (sp_invoke_external_rest_endpoint)
           ============================================================ */

        EXEC @ret = sys.sp_invoke_external_rest_endpoint
            @url = @url,
            @method = 'POST',
            @payload = @payload,
            @response = @response OUTPUT;

        IF @ret <> 0
        BEGIN
            -- Prova a leggere il messaggio errore dal JSON (se presente)
            SET @Errore =
                N'Errore chiamata API (ret=' + CAST(@ret AS NVARCHAR(20)) + N'): ' +
                ISNULL(JSON_VALUE(@response, '$.result.error.message'),
                       JSON_VALUE(@response, '$.error.message'));

            IF @Errore IS NULL
                SET @Errore = N'Errore chiamata API (ret=' + CAST(@ret AS NVARCHAR(20)) + N') - risposta non interpretabile';

            RETURN -3;
        END

        /* ============================================================
           10) STEP 9 - Parsing risposta JSON e composizione output
           ============================================================ */

        -- Alcuni formati incapsulano la risposta sotto $.result
        SET @resultJSON = JSON_QUERY(@response, '$.result');

        -- Estrai il testo: prima da result, altrimenti fallback diretto
        SET @CorpoEmail = COALESCE(
            JSON_VALUE(@resultJSON, '$.candidates[0].content.parts[0].text'),
            JSON_VALUE(@response,   '$.candidates[0].content.parts[0].text')
        );

        IF @CorpoEmail IS NULL OR LEN(@CorpoEmail) = 0
        BEGIN
            SET @Errore = N'Impossibile estrarre il messaggio dalla risposta AI';
            RETURN -2;
        END

        -- Oggetto localizzato 
        SET @Oggetto = CASE @CodiceLingua
            WHEN 'IT' THEN N'🎅 Caro ' + @Nome + N', la tua letterina è arrivata!'
            WHEN 'EN' THEN N'🎅 Dear ' + @Nome + N', your letter has arrived!'
            WHEN 'FR' THEN N'🎅 Cher ' + @Nome + N', ta lettre est arrivée!'
            WHEN 'ES' THEN N'🎅 Querido ' + @Nome + N', ¡tu carta ha llegado!'
            WHEN 'DE' THEN N'🎅 Liebe/r ' + @Nome + N', dein Brief ist da!'
            WHEN 'PT' THEN N'🎅 Querido ' + @Nome + N', a tua carta chegou!'
            ELSE N'🎅 ' + @Nome + N' - Letter Received!'
        END;

        /* ============================================================
           11) STEP 10 - Logging (audit: prompt + risposta raw)
           ============================================================ */

        INSERT INTO dbo.LogNotifiche
        (
            IDBambino,
            TipoNotifica,
            Destinatario,
            Oggetto,
            CorpoMessaggio,
            LinguaUtilizzata,
            PromptUsato,
            RispostaAI,
            StatoInvio,
            DataInvio
        )
        VALUES
        (
            @IDBambino,
            @TipoNotifica,
            @Email,
            @Oggetto,
            @CorpoEmail,
            @CodiceLingua,
            @promptCompleto,
            @response,
            'Generata',
            GETDATE()
        );

        /* ============================================================
           12) Fine: successo
           ============================================================ */

        SET @Errore = NULL;
        RETURN 0;

    END TRY
    BEGIN CATCH
        -- Errore SQL “interno” (es. tabelle mancanti, conversioni, ecc.)
        SET @Errore = N'Eccezione SQL: ' + ERROR_MESSAGE();
        RETURN -999;
    END CATCH
END;
GO

Dopo aver costruito la stored procedure principale per la generazione delle email, è utile avere uno strumento semplice e immediato per testare il risultato lingua per lingua, senza dover ogni volta selezionare manualmente un bambino o scrivere query di supporto.

Continua …