IA local com Ollama e Nginx: um guia para validação automatizada

Traduzido do original em espanhol. Ler em espanhol

iStock AI Generator

Objetivo do projeto

O objetivo deste projeto é executar um processo de IA para analisar cartelas de bingo e determinar se os números informados resultam em bingo. Para isso, usamos LLMs locais com o Ollama para a inferência de IA e o Nginx como camada de segurança para gerenciar SSL e autenticação. Embora este tutorial se concentre na análise de cartelas de bingo, o mesmo conceito pode ser aplicado a muitas outras tarefas que exijam validação com IA.

Este tutorial cobre a configuração do Ollama em um sistema Linux, incluindo a configuração do Nginx como proxy reverso com SSL.

Este é apenas um exemplo para fins de teste e não se destina a ambientes de produção.

Para mais detalhes sobre o Ollama, visite o repositório oficial: Ollama GitHub.

Instalar o Ollama

Para instalar o Ollama, execute os seguintes comandos:

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

Baixar os modelos

Obtenha os modelos necessários:

ollama pull llama3.2-vision
ollama pull phi4

Instalar o Nginx

Instale o Nginx no seu servidor:

sudo apt update
sudo apt install nginx -y

Obter certificados SSL

Você precisa obter um certificado SSL para uma comunicação segura. Pode usar o Let’s Encrypt ou outra autoridade certificadora, mas o processo não é abordado neste tutorial.

Configurar o Nginx como proxy reverso com SSL e autenticação

Edite a configuração do Nginx:

sudo vim /etc/nginx/nginx.conf

Adicione ou modifique o seguinte conteúdo:

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;
        }
    }
}

Para detalhes sobre a configuração de autenticação no Nginx, consulte a documentação oficial: Configuração de autenticação básica HTTP.

Reinicie o Nginx para aplicar as mudanças:

sudo systemctl restart nginx

O código

Primeiro, precisamos importar as bibliotecas necessárias e definir algumas constantes.

import base64
import requests
import json

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

Função para ler e codificar a imagem

Esta função lê a imagem da cartela de bingo e a codifica em base64 para enviá-la na requisição POST.

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

Função para enviar uma requisição POST

Esta função envia uma requisição POST ao servidor com a imagem codificada e o modelo de IA.

Seed: garante a consistência das respostas ao fixar a aleatoriedade do modelo.

Temperatura: controla a criatividade das respostas; valores baixos geram saídas previsíveis, enquanto valores altos permitem maior variabilidade.

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

Definição do formato de resposta esperado

Aqui definimos o formato que esperamos receber como resposta da IA. Isso nos ajuda a estruturar os dados de forma adequada. O uso de structured output em modelos de linguagem (LLMs) melhora a precisão, facilita a integração com sistemas externos, reduz ambiguidades e otimiza a análise de dados. Isso traz mais confiabilidade, interoperabilidade e escalabilidade em fluxos automatizados.

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"]
}

Preparação da imagem e requisição inicial

Lemos e codificamos a imagem da cartela de bingo e depois preparamos o prompt para identificar os números presentes na imagem.

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)

Análise dos resultados e validação

Analisamos os resultados obtidos da IA para ter a lista de números encontrados e depois validamos se é possível cantar bingo com uma lista de números sorteados.

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', [])

Código 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  :( !!!")

Este processo permite executar um servidor Ollama seguro em Linux, com SSL e autenticação, enquanto você faz o reconhecimento de números e a validação com modelos de IA.

Nota: Este exemplo se destina apenas a testes e desenvolvimento. Não é recomendado para ambientes de produção.

Maximiliano Díaz Doglia

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

Publicado em: IA