IA locale con Ollama e Nginx: una guida alla validazione automatizzata

Tradotto dall'originale in spagnolo. Leggi in spagnolo

iStock AI Generator

Scopo del progetto

L’obiettivo di questo progetto è eseguire un processo di IA che analizza le cartelle della tombola (bingo) e stabilisce se i numeri forniti fanno bingo. Lo facciamo usando LLM locali con Ollama per l’inferenza e Nginx come livello di sicurezza per gestire SSL e autenticazione. Anche se questo tutorial si concentra sull’analisi delle cartelle, lo stesso concetto si può applicare a molti altri compiti che richiedono una validazione basata sull’IA.

Questo tutorial copre la configurazione di Ollama su un sistema Linux, inclusa la configurazione di Nginx come reverse proxy con SSL.

È solo un esempio a scopo di test e non è pensato per ambienti di produzione.

Per maggiori dettagli su Ollama, visita il repository ufficiale: Ollama GitHub.

Installare Ollama

Per installare Ollama, esegui i seguenti comandi:

curl -fsSL https://ollama.com/install.sh | sh

Scaricare i modelli

Ottieni i modelli necessari:

ollama pull llama3.2-vision
ollama pull phi4

Installare Nginx

Installa Nginx sul tuo server:

sudo apt update
sudo apt install nginx -y

Ottenere i certificati SSL

Ti serve un certificato SSL per una comunicazione sicura. Puoi usare Let’s Encrypt o un’altra autorità di certificazione, ma la procedura non è trattata in questo tutorial.

Configurare Nginx come reverse proxy con SSL e autenticazione

Modifica la configurazione di Nginx:

sudo vim /etc/nginx/nginx.conf

Aggiungi o modifica il seguente contenuto:

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  3600;
    client_max_body_size  5G;
    client_body_timeout  3600;
    proxy_read_timeout  3600;
    proxy_send_timeout  3600;

    server {
        listen       11435 ssl;
        server_name  yourdomain.com;

        ssl_certificate      /path/to/your/cert.pem;
        ssl_certificate_key  /path/to/your/key.pem;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  60m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
            proxy_pass http://localhost:11434;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

Per i dettagli sulla configurazione dell’autenticazione in Nginx, consulta la documentazione ufficiale: Configurare l’autenticazione HTTP Basic.

Riavvia Nginx per applicare le modifiche:

sudo systemctl restart nginx

Il codice

Per prima cosa dobbiamo importare le librerie necessarie e definire alcune costanti.

import base64
import requests
import json

OLLAMA_SERVER_URL = "https://ollama.zerogap.com:11435"
MODEL_NAME = "llama3.2-vision"
IMAGE_PATH = "bingo.jpg"

Funzione per leggere e codificare l’immagine

Questa funzione legge l’immagine della cartella e la codifica in base64 per poterla inviare nella richiesta POST.

def encode_image(image_path):
    with open(image_path, 'rb') as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')

Funzione per inviare una richiesta POST

Questa funzione invia una richiesta POST al server con l’immagine codificata e il modello di IA.

Seed: garantisce la coerenza delle risposte fissando la casualità del modello.

Temperatura: controlla la creatività delle risposte; valori bassi producono output prevedibili, mentre valori alti consentono maggiore variabilità.

def send_post_request(model, prompt, images=None, stream=False, response_format=None, system_prompt=None):
    body = {
        "model": model,
        "prompt": prompt,
        "system": system_prompt,
        "images": images if images else [],
        "stream": stream,
        "format": response_format if response_format else {},
        "options": {
            "seed": 46,
            "temperature": 0.1
        }
    }
    headers = {
        "Content-Type": "application/json",
    }

    try:
        response = requests.post(f"{OLLAMA_SERVER_URL}/api/generate", json=body, headers=headers)
        response.raise_for_status()  # Raise an HTTPError on bad response
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"An error occurred: {e}")
        return None

Definizione del formato di risposta atteso

Qui definiamo il formato che ci aspettiamo come risposta dall’IA. Questo ci aiuta a strutturare correttamente i dati. L’uso dello structured output nei modelli linguistici (LLM) migliora la precisione, facilita l’integrazione con sistemi esterni, riduce le ambiguità e ottimizza l’analisi dei dati. Ne derivano maggiore affidabilità, interoperabilità e scalabilità nei flussi automatizzati.

response_format = {
    "type": "object",
    "properties": {
        "found_numbers": {
            "type": "array",
            "items": {"type": "number"}
        }
    },
    "required": ["found_numbers"]
}

validation_response_format = {
    "type": "object",
    "properties": {
        "is_valid": {"type": "boolean"}
    },
    "required": ["is_valid"]
}

Preparazione dell’immagine e richiesta iniziale

Leggiamo e codifichiamo l’immagine della cartella, poi prepariamo il prompt per identificare i numeri presenti nell’immagine.

encoded_image = encode_image(IMAGE_PATH)

vision_identifier_prompt =  "Identify and return all numbers present in the image. The output should be a structured JSON object with a key 'found_numbers' containing a list of all detected numbers."
system_prompt="Your task is to analyze the image provided, following specific instructions from the user."

vision_results = send_post_request(MODEL_NAME, vision_identifier_prompt, images=[encoded_image],response_format=response_format, system_prompt=system_prompt)

Analisi dei risultati e validazione

Analizziamo i risultati ottenuti dall’IA per ricavare l’elenco dei numeri trovati e poi verifichiamo se si può fare bingo con un elenco di numeri estratti.

if vision_results:
    message_content = vision_results.get('response')
    vision_data = json.loads(message_content)
    found_numbers = vision_data.get('found_numbers', [])

    validator_prompt = (
        f"""CARD: {found_numbers}, SELECTED: [32,29,17,75]"""
    )

    system_prompt = "Your task is to determine if all numbers in the 'SELECTED' list are present in the 'CARD' list. Return only a JSON object with the key 'is_valid' and the boolean value true if all numbers are found, or false otherwise. Do not provide any explanation or code. Example: Input: { \"CARD\": [3, 9, 10, 14, 17, 20, 24, 25, 29, 32, 34, 37, 42, 47, 48, 50, 55, 57, 62, 68, 69, 72, 70, 75], \"SELECTED\": [302, 29, 17, 9] } Output: { \"is_valid\": false }"

    MODEL_NAME = "phi4"
    validation_results = send_post_request(MODEL_NAME, validator_prompt, response_format=validation_response_format, system_prompt=system_prompt)

    if validation_results:
        validation_message_content = validation_results.get('response')
        validation_data = json.loads(validation_message_content)
        validated_numbers = validation_data.get('is_valid', [])

Codice completo

import base64
import requests
import json

# Define constants for server URL and model
OLLAMA_SERVER_URL = "https://ollama.zerogap.com:11435"
MODEL_NAME = "llama3.2-vision"
IMAGE_PATH = "bingo.jpg"

# Function to read and encode the image
def encode_image(image_path):
    with open(image_path, 'rb') as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')

# Function to send a POST request
def send_post_request(model, prompt, images=None, stream=False, response_format=None, system_prompt=None):
    body = {
        "model": model,
        "prompt": prompt,
        "system": system_prompt,
        "images": images if images else [],
        "stream": stream,
        "format": response_format if response_format else {},
        "options": {
            "seed": 46,
            "temperature": 0.1
        }
    }
    headers = {
        "Content-Type": "application/json",
    }

    try:
        response = requests.post(f"{OLLAMA_SERVER_URL}/api/generate", json=body, headers=headers)
        response.raise_for_status()  # Raise an HTTPError on bad response
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"An error occurred: {e}")
        return None

# Define the expected response format for a list of numbers
response_format = {
    "type": "object",
    "properties": {
        "found_numbers": {
            "type": "array",
            "items": {"type": "number"}
        }
    },
    "required": ["found_numbers"]
}

# Define the expected response format for validations (boolean)
validation_response_format = {
    "type": "object",
    "properties": {
        "is_valid": {"type": "boolean"}
    },
    "required": ["is_valid"]
}

# Prepare the image
encoded_image = encode_image(IMAGE_PATH)

# Improved vision identifier prompt
vision_identifier_prompt =  "Identify and return all numbers present in the image. The output should be a structured JSON object with a key 'found_numbers' containing a list of all detected numbers."
system_prompt="Your task is to analyze the image provided."

vision_results = send_post_request(MODEL_NAME, vision_identifier_prompt, images=[encoded_image],response_format=response_format, system_prompt=system_prompt)

if vision_results:
    # Parse the structured output from the vision results
    message_content = vision_results.get('response')
    vision_data = json.loads(message_content)
    found_numbers = vision_data.get('found_numbers', [])

    # Validate the results
    validator_prompt = (
        f"""CARD: {found_numbers}, SELECTED: [32,29,17,75]"""
    )

    system_prompt = "Your task is to determine if all numbers in the 'SELECTED' list are present in the 'CARD' list. Return only a JSON object with the key 'is_valid' and the boolean value true if all numbers are found, or false otherwise. Do not provide any explanation or code. Example: Input: { \"CARD\": [3, 9, 10, 14, 17, 20, 24, 25, 29, 32, 34, 37, 42, 47, 48, 50, 55, 57, 62, 68, 69, 72, 70, 75], \"SELECTED\": [302, 29, 17, 9] } Output: { \"is_valid\": false }"

    MODEL_NAME = "phi4"
    validation_results = send_post_request(MODEL_NAME, validator_prompt, response_format=validation_response_format, system_prompt=system_prompt)

    if validation_results:
        # Parse the structured output from the validation results
        validation_message_content = validation_results.get('response')
        validation_data = json.loads(validation_message_content)
        validated_numbers = validation_data.get('is_valid', [])

# Print the final analysis
print("--- Final Analysis ---")
if validated_numbers:
    print("BINGO!!!")
else:
    print("No BINGO  :( !!!")

Questo processo ti permette di eseguire un server Ollama sicuro su Linux, con SSL e autenticazione, mentre esegui il riconoscimento dei numeri e la validazione con modelli di IA.

Nota: questo esempio è pensato solo per test e sviluppo. Non è consigliato per ambienti di produzione.

Maximiliano Díaz Doglia

AI Platform Engineer & Full-Stack Developer
Building Enterprise Integrations & Automations

Pubblicato in: IA