LM Studio + Laragon: LLM PHP App in locale

Image

LM Studio + Laragon: LLM PHP App in locale

Mi sono divertito un sacco a realizzare l’immagine del nerd che gongola con la birra “localhost”, volevo evitare la solita copertina con i loghetti dei software. E forse questo paragrafo rovinerà l’indicizzazione di questa pagina, ma non importa, in questo magazine non siamo ossessionati dal bisogno di popolarità.

Tra soluzioni cloud e API proprietarie, spicca una strada alternativa e sempre più interessante, il self-hosting. Questo articolo pensato per sviluppatori alle prime armi ma curiosi, ti guiderà passo-passo nella configurazione locale di LM Studio, nell’esposizione delle sue API, e infine nell’accesso al modello GPT-OSS-20B tramite un’applicazione PHP ospitata in Laragon. Un progetto pratico e replicabile, perfetto per chi vuole sperimentare, imparare o iniziare a costruire soluzioni aziendali autonome e sostenibili. Si presuppone che il lettore possieda conoscenze di base in ambito di reti e programmazione.

LM Studio: la base operativa

LM Studio è un’interfaccia desktop per l’uso locale di modelli di linguaggio open-source, come LLaMA, Mistral e GPT-OSS. Uno dei suoi punti di forza è la possibilità di esporre localmente un’API compatibile con OpenAI, rendendo l’integrazione con app esistenti straordinariamente semplice.

1. Installazione di LM Studio

  • Scarica LM Studio da lmstudio.ai
  • Installa come un normale programma desktop (disponibile per Windows, macOS e Linux)
  • Avvia LM Studio e scegli un modello, ad esempio GPT-OSS-20B, ottimo compromesso tra potenza e leggerezza.

2. Esporre le API

Premetto che nel momento in cui sto scrivendo la documentazione ufficiale non è proprio una meraviglia, soprattutto per gli utenti alle prime armi. Vediamo di colmare questo gap con uno screenshot.

  • Una volta caricato il tuo modello preferito, nella colonna di sinistra seleziona Developer.
  • In alto, accanto a Status: Running clicca su Settings e copia la configurazione che vedi nello screenshot per esporre le API di LM Studio SOLO in localhost. Se vuoi renderle disponibili a tutta la tua rete locale dovrai abilitare anche Serve on Local Network.

Con LM Studio abbiamo finito, passiamo al web server in locale, il mio preferito è Laragon.

Laragon: web server facile

1. Scarica Laragon

  • Vai alla pagina Download e prendi l’ultima Full (consigliata), con Apache/Nginx, MySQL, PHP, Node, Python, ecc.

2. Installa

  • Avvia l’installer → “Next, Next, Next…”.
  • Percorso consigliato: C:\laragon.
  • (Opzionale) Spunta Run Laragon when Windows starts per averlo pronto ad ogni avvio.

3. Avvia i servizi

  • Apri Laragon e clicca Start All (Apache/Nginx, MySQL, ecc.).
  • Con Quick Start puoi creare un progetto (es. WordPress) in pochi minuti.

4. Dove mettere i siti (Document Root) + URL “belli”

  • Metti i progetti in C:\laragon\www\.
  • Con Auto Virtual Hosts ottieni URL del tipo http://miosito.test (basta mettere la cartella miosito in www e fare Reload).
  • Il pattern {name}.test è configurabile da Menu → Preferences.

5. HTTPS locale in 1 click

  • Attiva Menu → Apache → SSL → Enabled: il tuo sito sarà raggiungibile anche in https://miosito.test.

6. Database & phpMyAdmin

  • Laragon può aggiungere phpMyAdmin dal menu Tools → Quick add → phpmyadmin.
  • Con PHP 8.4+ usa phpMyAdmin 6 (voce phpmyadmin6.0snapshot).
  • La pagina di download indica anche i due collegamenti: phpMyAdmin 5 e phpMyAdmin 6 nel menu MySQL.

7. Cambiare versioni di PHP/Apache

  • Aggiungi versioni con Tools → Quick add → [PHP/Apache/Nginx Version].
  • Cambia la versione di PHP da Menu → PHP → Version → [scelta].

8. Crea il primo progetto “al volo”

  • Crea la cartella myapp in cui mettete i file php in C:\laragon\www\myapp e genera http://myapp.test.

9. Scriviamo il codice della nostra applicazione php

Prima programmiamo index.php per la UI

PHP
<!doctype html>
<html lang="it">
<head>
  <meta charset="utf-8">
  <title>Chat LLM locale (LM Studio) — Demo PHP</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    body{font-family:system-ui,Segoe UI,Arial,sans-serif;max-width:760px;margin:32px auto;padding:0 16px}
    textarea{width:100%;min-height:140px;padding:10px}
    pre{background:#f6f6f6;padding:12px;border-radius:8px;white-space:pre-wrap}
    .row{margin:12px 0}
    .muted{color:#666}
    button{padding:10px 16px;border:0;border-radius:8px;background:black;color:white;cursor:pointer}
    button:hover{opacity:.9}
    .card{border:1px solid #eee;border-radius:12px;padding:16px;margin-top:16px}
    .error{color:#b00020}
  </style>
</head>
<body>
  <h1>Chat con LLM locale (LM Studio)</h1>
  <p class="muted">Endpoint locale chiamato da PHP: <code>http://localhost:1234/v1/chat/completions</code></p>

  <form id="chat-form" class="card">
    <div class="row">
      <label for="prompt"><strong>Prompt</strong></label><br>
      <textarea id="prompt" name="prompt" placeholder="Scrivi la tua richiesta..."></textarea>
    </div>
    <div class="row">
      <label for="temperature">Temperature (0–1)</label><br>
      <input id="temperature" name="temperature" type="number" min="0" max="1" step="0.1" value="0.7">
    </div>
    <div class="row">
      <label for="max_tokens">Max tokens (opzionale)</label><br>
      <input id="max_tokens" name="max_tokens" type="number" placeholder="es. 512">
    </div>
    <div class="row">
      <button type="submit">Invia</button>
    </div>
    <p class="muted">Assicurati in LM Studio: <em>Settings → Developer → Enable Local Inference Server</em> (porta predefinita 1234).</p>
  </form>

  <div id="output" class="card" style="display:none;">
    <h3>Risposta del modello</h3>
    <pre id="answer"></pre>
  </div>

  <div id="error" class="card" style="display:none;">
    <h3>Errore</h3>
    <pre class="error" id="errText"></pre>
  </div>

  <script>
    const form = document.getElementById('chat-form');
    const outBox = document.getElementById('output');
    const ans = document.getElementById('answer');
    const errBox = document.getElementById('error');
    const errText = document.getElementById('errText');

    form.addEventListener('submit', async (e) => {
      e.preventDefault();
      outBox.style.display = 'none';
      errBox.style.display = 'none';

      const prompt = document.getElementById('prompt').value.trim();
      const temperature = document.getElementById('temperature').value;
      const max_tokens = document.getElementById('max_tokens').value;

      if (!prompt) {
        errText.textContent = 'Prompt mancante.';
        errBox.style.display = 'block';
        return;
      }

      ans.textContent = 'Sto pensando...';
      outBox.style.display = 'block';

      try {
        const res = await fetch('chat.php', {
          method: 'POST',
          headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
          body: new URLSearchParams({ prompt, temperature, max_tokens })
        });
        const data = await res.json();

        if (!res.ok || data.error) {
          errText.textContent = (data.error ? JSON.stringify(data.error, null, 2) : `HTTP ${res.status}`);
          errBox.style.display = 'block';
          outBox.style.display = 'none';
          return;
        }

        ans.textContent = data.content || '(nessuna risposta)';
        outBox.style.display = 'block';
      } catch (err) {
        errText.textContent = String(err);
        errBox.style.display = 'block';
        outBox.style.display = 'none';
      }
    });
  </script>
</body>
</html>

Poi codifichiamo chat.php per la connessioni alle API

PHP
<?php
// chat.php — chiama l'API locale di LM Studio (compatibile OpenAI)

header('Content-Type: application/json; charset=utf-8');

$BASE_URL = 'http://localhost:1234/v1';         // cambia se usi altra porta/host
$MODEL    = 'openai/gpt-oss-20b';               // deve corrispondere al modello caricato in LM Studio
$SYSTEM   = 'Sei un assistente conciso e utile.';

$prompt = trim($_POST['prompt'] ?? '');
if ($prompt === '') {
    http_response_code(400);
    echo json_encode(['error' => 'Prompt mancante']);
    exit;
}

// Opzioni opzionali dal form
$temperature = isset($_POST['temperature']) && $_POST['temperature'] !== '' ? floatval($_POST['temperature']) : 0.7;
$max_tokens  = isset($_POST['max_tokens']) && $_POST['max_tokens'] !== '' ? intval($_POST['max_tokens']) : null;

$payload = [
    'model'    => $MODEL,
    'messages' => [
        ['role' => 'system', 'content' => $SYSTEM],
        ['role' => 'user',   'content' => $prompt],
    ],
    'temperature' => $temperature,
];
if ($max_tokens !== null) $payload['max_tokens'] = $max_tokens;

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL            => $BASE_URL . '/chat/completions',
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        // Se hai configurato un token sul server locale, sblocca la riga sotto:
        // 'Authorization: Bearer not-needed',
    ],
    CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_UNICODE),
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT        => 60,
]);

$response = curl_exec($ch);
$err      = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($response === false) {
    http_response_code(500);
    echo json_encode(['error' => ['type' => 'curl_error', 'message' => $err]]);
    exit;
}

$data = json_decode($response, true);

// Se LM Studio risponde con errore o HTTP non 2xx
if ($httpCode < 200 || $httpCode >= 300) {
    http_response_code($httpCode);
    echo json_encode(['error' => $data ?: $response], JSON_UNESCAPED_UNICODE);
    exit;
}

// Estrai il testo in stile OpenAI Chat Completions
$content = $data['choices'][0]['message']['content'] ?? null;

echo json_encode([
    'content' => $content,
    'model'   => $MODEL,
    'usage'   => $data['usage'] ?? null,
], JSON_UNESCAPED_UNICODE);

Puntiamo il browser a http://myapp.test

Con questo setup iniziale hai già in mano una base solida, semplice da mantenere e pronta a crescere. Agganciando Bootstrap per dare struttura all’interfaccia e qualche animazione leggera durante l’attesa della risposta dell’LLM, l’esperienza diventerebbe subito più fluida e piacevole.

Ma il vero divertimento inizia adesso, potremmo introdurre un avatar virtuale che ci assista, presenti i contenuti e ci guidi tra le funzioni, trasformando una semplice chat in un’interazione più naturale e memorabile. Da qui si aprono strade praticamente infinite, streaming token-by-token, prompt library condivisa, logging e analisi delle conversazioni, autenticazione, fino a integrazioni con servizi esterni.

In sintesi, abbiamo costruito le fondamenta giuste. Il prossimo passo è rifinire la UX e sperimentare con piccoli miglioramenti iterativi che, sommati, faranno la differenza tra un prototipo funzionale e uno strumento capace di generare valore reale.

Se l’articolo ti è piaciuto restiamo in contatto su linkedin a https://www.linkedin.com/in/andreatonin/

Laragon #PHP #SviluppoLocale #LMStudio #LLM #API #Apache #MySQL #SviluppoWeb #DevTools

Releated Posts

ComfyUI: Prompt Engineering per LTX-2.3

LTX-2.3 è l’evoluzione di LTX-2, il modello open-source di Lightricks che genera video con audio sincronizzato in un…

DiByAndrea Tonin Lug 9, 2026

ComfyUI: Krea2 Conditioning Control

Capire come un’immagine prende forma dentro un modello generativo significa guardare oltre il prompt scritto. Le parole non…

DiByAndrea Tonin Giu 29, 2026

Seedance: Tecniche Avanzate

Seedance 2.0, nella documentazione pubblica, è pensato come sistema multimodale che accetta input di tipo testo, immagini, video…

DiByAndrea Tonin Giu 23, 2026

Seedance: Camera Movement Cheat Sheet

Per video AI, conviene descrivere la camera come farebbe un director of photography: tipo di movimento + direzione…

DiByAndrea Tonin Giu 23, 2026