[PHP][AI] 免費token吃到飽 超簡單開源模型PHP應用實例 (包含 Ollama 的安裝與使用教學)
在 AI 百花齊放的今天,各家大模型很好用想必大家都已經知道了。隨之而來的各種訂閱費用,想當個免費仔越來越難。如果只是公司內網系統中的一個小小 AI 應用(例如:翻譯),其實免費的開源本地模型就夠了,不用訂閱費,token 還可以吃到飽,使用方法也很簡單。
第一步:安裝管理本地模型的工具【Ollama】
想要使用開源的本地模型,你需要先有管理模型的工具,我們這邊選擇很多人愛用的 Ollama。
Ollama 是一個讓你在本地端(Local)輕鬆執行、管理開源大型語言模型(LLM)的工具。它將模型權重、設定和依賴環境打包在一起,讓你可以直接在終端機透過簡單的指令快速啟動 AI。
1. 安裝 Ollama:
最快的方式是直接透過終端機指令安裝。
curl -fsSL https://ollama.com/install.sh | sh
2. 下載並執行模型:
安裝完成後,你可以直接下達 run 指令。如果本地端沒有該模型,Ollama 會自動幫你從 registry 下載。以 Google 最新的開源模型 Gemma4 為例(e4b-it-q4_K_M 是版本號,因為每個人的本地主機規格不一樣,規格好記憶體大就可以裝高規的版本,可參考 https://ollama.com/search):
ollama run gemma4:e4b-it-q4_K_M
3. 管理本地模型:
隨時間推移,你可能會下載許多模型。你可以用 list 去查看目前下載了哪些模型,不需要的模型可以用 rm 移除,避免佔用空間。
ollama list
ollama rm gemma4:e4b-it-q4_K_M
第二步:啟動本地模型的服務
我們在上一步的操作中,成功的在本地主機上裝好了 Ollama,也一併下載了 Google 免費開源模型 Gemma4 之後,請將本地主機重新開機。
開機後本地主機不會主動啟動 Ollama 的服務,我們可以透過以下指令啟動服務:
OLLAMA_HOST="0.0.0.0:11434" OLLAMA_ORIGINS="*" ollama serve
因為光靠 ollama serve 來啟動的 Ollama 是不允許跨網域連線(CORS)的,這會讓網頁前端的 JavaScript 無法直接呼叫。所以需要在 ollama serve 前面加上兩個額外的命令:
- OLLAMA_ORIGINS="*":解除跨來源資源共用(CORS)限制,允許網頁前端直接呼叫。
- OLLAMA_HOST="0.0.0.0:11434":允許非本機(如同網域內的其他開發機、測試伺服器)連線。
之所以要重新開機的原因是,許多初學者在第一步執行完 ollama run xxx,就緊接著執行 ollama serve,這會導致連接埠衝突的錯誤。因為執行了 ollama run xxx,Ollama 除了運作該模型之外,會自動在背景順便把 ollama serve(也就是 11434 埠)給一起啟動,所以才需要重新開機,當然你手動關閉 11434 port 的服務也可以。
依照時間序,當本地主機重開機後,第一件事是啟動服務(OLLAMA_HOST="0.0.0.0:11434" OLLAMA_ORIGINS="*" ollama serve),此時完全沒有任何 AI 模型會運作,運作的模型數量是 0。Ollama 只是把「大門打開」僅僅是啟動了 Ollama 的伺服器主程式。它在記憶體裡只佔了一點點空間,建立好一個連接埠(11434),然後就在那裡「發呆、等待」。
那模型到底是「什麼時候」才會開始運作?只有當你的網頁程式(例如前端 JavaScript 發送 fetch)被指定的模型才會被喚醒並運作。假設你的 1.php 裡寫著 model: "qwen2.5-coder:14b" 當你在網頁按下按鈕的「那一瞬間」,Ollama 伺服器才會突然從硬碟把 qwen2.5-coder:14b 讀取並載入到 VRAM 中開始運作。如果你執行 2.php,程式裡寫著 model: "gemma4:e4b-it-q4_K_M" 那 Ollama 就會去硬碟改抓 Gemma4 載入到 VRAM 中運作。
當你的網頁拿到 AI 回傳的 JSON 資料後,這個模型就會繼續在 VRAM 裡待命。如果過了 5 分鐘你都沒有再點擊網頁、沒有給它任何新任務,Ollama 為了不霸佔資源,就會自動把這個模型從記憶體裡踢出去(釋放 VRAM)。伺服器會重新回到「發呆、等待」的狀態(此時運作的模型數量又變回 0)。
第三步:編寫後端 PHP 串接程式與參數優化
接下來,我們編寫一段標準的後端 PHP 程式碼:
function translateChineseToEnglish(string $chineseText): string
{
$url = "http://127.0.0.1:11434/v1/chat/completions";
// 透過 messages 陣列設定角色,利用 system 角色來規範模型的行為
$data = [
"model" => "gemma4:e4b-it-q4_K_M", // 確保使用你現有的模型名稱
"messages" => [
[
"role" => "system",
"content" => "你是一個專業的繁體中文到英文的翻譯專家。請直接提供翻譯後的英文結果,不要包含任何解釋、不要前言、不要引號、也不要任何多餘的寒暄。只需回傳翻譯文字本身。"
],
[
"role" => "user",
"content" => "請幫我翻譯這段文字:\n\n" . $chineseText
]
],
"stream" => false, // 關閉串流,讓 PHP 一次性接收完整 JSON
"options" => [
"temperature" => 0.2, // 降低隨機性,讓程式碼產出更嚴謹精準
"num_ctx" => 16384 // 核心優化:將上下文開大至 16K,避免長文本被截斷
]
];
// 初始化 cURL
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
$err = curl_error($ch);
if ($err) {
return "cURL Error: " . $err;
}
$result = json_decode($response, true);
// 核心修正:OpenAI 相容介面 (/v1/chat/completions) 的標準回傳路徑為 choices.0.message.content
if (isset($result['choices'][0]['message']['content'])) {
return trim($result['choices'][0]['message']['content']);
}
// 除錯機制:如果還是失敗,印出 Ollama 實際回傳的錯誤訊息
if (isset($result['error']['message'])) {
return 'Ollama Error: ' . $result['error']['message'];
}
return 'Translation Failed. Raw Response: ' . substr($response, 0, 200);
}
// =====================================================
// 運行測試範例
// =====================================================
header('Content-Type: text/plain; charset=utf-8');
echo "--- 本地 AI 翻譯測試 ---\n\n";
// 測試 1:日常對話
$text1 = "今天天氣很好,我們全家打算去公園走走。";
echo "原文字:{$text1}\n";
echo "翻譯後:\n" . translateChineseToEnglish($text1) . "\n\n";
echo "----------------------------------------\n";
// 測試 2:商務/系統開發術語
$text2 = "此帳號已被鎖定,請確認您的密碼並在15分鐘後重新嘗試登入,或聯繫系統管理員。";
echo "原文字:{$text2}\n";
echo "翻譯後:\n" . translateChineseToEnglish($text2) . "\n\n";
PHP程式碼中的IP是用127.0.0.1當範例,正式使用時請替換成你本地主機的內部IP。