Deepseek 本地部署詳細指南!從 Ollama 到個人知識庫應用
丨作者:lushen 每個人都能擁有專屬 AI 助手,安全高效,開啓智能化知識管理新體驗。
系統介紹
- mbp pro
一、Ollama 安裝與配置
1.1 跨平臺安裝指南
Ollama 作爲本地運行大模型的利器,支持三大主流操作系統:
# macOS一鍵安裝
# Windows用戶
訪問官網 https://ollama.com/download 下載安裝包
# Linux安裝(Ubuntu/Debian爲例)
curl -fsSL https://ollama.com/install.sh | sudo bash
sudo usermod -aG ollama $USER # 添加用戶權限
sudo systemctl start ollama # 啓動服務
1.2 服務驗證
ollama -v
# 輸出ollama version is 0.5.7
出現上述則表示安裝成功,可瀏覽器訪問 http://localhost:11434 / 驗證
二、Deepseek 模型部署
2.1 模型下載與加載
以 deepseek r1 模型爲例:
-
訪問 https://ollama.com/library/deepseek-r1,默認爲 7b 模型,如需其他模型,可以在當前頁搜索所需模型
-
模型詳情頁複製安裝命令 ollama run deepseek-r1
-
安裝完成後在終端執行:
ollama run deepseek-r1
# 執行後
pulling manifest
pulling 96c415656d37... 100% ▕██████████████▏ 4.7 GB
pulling 369ca498f347... 100% ▕██████████████▏ 387 B
pulling 6e4c38e1172f... 100% ▕██████████████▏ 1.1 KB
pulling f4d24e9138dd... 100% ▕██████████████▏ 148 B
pulling 40fb844194b2... 100% ▕██████████████▏ 487 B
verifying sha256 digest
writing manifest
success
> > > Send a message (/? for help)
> > > `
> > > 當看到上述提示,即可開始模型對話。
-
mac 後臺標識
-
win 後臺標識
見任務欄托盤區
2.2 模型驗證測試
運行交互式對話測試:
請用Python寫一個快速排序算法
當看到完整代碼輸出,說明模型已成功加載。
硬件要求建議:
-
最低配置:16GB 內存 + 8GB 顯存
-
推薦配置:32GB 內存 + 16GB 顯存(RTX 3060 級別)
三、安裝交互 ui
3.1 chatbox
-
下載地址 chatboxai.app
-
配置本地模型
- 進入設置頁面
-
選擇 ollama api (本地部署)
-
配置本機地址,默認 http://127.0.0.1:11434
至此即可開啓問答模式
3.2 Page Assist 瀏覽器插件
-
安裝後簡單配置即可開啓問答模式,功能豐富,可以參考官方引導
-
本插件支持本地知識庫建設,因本次使用 Dify 建設,在此不贅述。
四、Dify 知識庫搭建
參考文檔地址 Docker Compose 部署
4.1 環境準備
- 拉取源代碼,準備環境
# mac os
# 克隆 Dify 源代碼至本地環境。
git clone https://github.com/langgenius/dify.git
# 進入 Dify 源代碼的 Docker 目錄
cd dify/docker
# 複製環境配置文件
cp .env.example .env
- 啓動 Docker 容器(需要先安裝 D ocker)
docker compose up -d
# 如果版本是 Docker Compose V1,使用以下命令:
docker-compose up -d
# 正常返回
[+] Running 74/9
✔ db Pulled 834.2s
✔ sandbox Pulled 1120.7s
✔ weaviate Pulled 526.5s
✔ web Pulled 174.0s
✔ redis Pulled 893.7s
✔ api Pulled 2919.8s
✔ worker Pulled 2919.8s
✔ ssrf_proxy Pulled 494.0s
✔ nginx Pulled 184.7s
[+] Running 11/11
✔ Network docker_default Created 0.0s
✔ Network docker_ssrf_proxy_network Created 0.0s
✔ Container docker-db-1 Started 1.1s
✔ Container docker-web-1 Started 1.1s
✔ Container docker-redis-1 Started 1.1s
✔ Container docker-sandbox-1 Started 1.1s
✔ Container docker-weaviate-1 Started 1.1s
✔ Container docker-ssrf_proxy-1 Started 1.1s
✔ Container docker-api-1 Started 0.7s
✔ Container docker-worker-1 Started 0.7s
✔ Container docker-nginx-1 Started 0.8s
在此階段可能會遇到下列失敗的情況,可以嘗試切換源解決 我當時的條件
-
修改配置後重啓 docker
-
辦公網環境下
docker compose up -d
[+] Running 9/9
✘ web Error context canceled 14.9s
✘ redis Error context canceled 14.9s
✘ db Error context canceled 14.9s
✘ nginx Error context canceled 14.9s
✘ ssrf_proxy Error context canceled 14.9s
✘ sandbox Error Head "https://registry-1.do... 14.9s
✘ api Error context canceled 14.9s
✘ worker Error context canceled 14.9s
✘ weaviate Error context canceled 14.9s
Error response from daemon: Head "https://registry-1.docker.io/v2/langgenius/dify-sandbox/manifests/0.2.10": Get "https://auth.docker.io/token?scope=repository%3Alanggenius%2Fdify-sandbox%3Apull&service=registry.docker.io": EOF
解決方法
-
右上角齒輪圖標進入設置 -> Docker engine,在配置中添加
-
寫入以下內容 ocker)
{
// ...
"registry-mirrors": [
"https://docker.hpcloud.cloud",
"https://docker.m.daocloud.io",
"https://docker.unsee.tech",
"https://docker.1panel.live",
"http://mirrors.ustc.edu.cn",
"https://docker.chenby.cn",
"http://mirror.azure.cn",
"https://dockerpull.org",
"https://dockerhub.icu",
"https://hub.rat.dev"
]
}
4.2 Dify 創建聊天
-
訪問 http://localhost/(默認 80 端口) 進入 dify
-
首次進入初始化設置賬號密碼
-
點擊 Dify 平臺右上角頭像 → 設置 → 模型供應商,選擇 Ollama,輕點 “添加模型”。
在配置 url 時,因爲是 docker 服務,http://localhost:11434 存在無法訪問的情況,可以嘗試 http://host.docker.internal:11434
- 至此,可以開始創建應用,在主頁選擇 全部 -> 創建空白應用 -> 填入應用信息即可
4.3 Dify 知識庫創建
-
主頁選擇 知識庫 -> 創建知識庫 -> 上傳知識 -> 等待處理完成
-
進入聊天應用,選擇剛纔創建的知識庫,即可開始帶有私域知識的溝通
五、應用測試
5.1 翻譯場景
- 本地客戶端具有部分國際化測試文件需要執行翻譯,格式示例如下,多層嵌套的 json 格式,value 爲 string 類型。需要利用大模型對整個 json 文件進行翻譯,將中文翻譯爲英文後按原格式返回
// zh.json
{
"window": {
"willUnload": {
"title": "確認刷新當前頁面嗎?",
"message": "系統可能不會保存您做的更改",
"unload_bt": "重新加載",
"cancel_bt": "取消"
}
}
}
ocker)
-
實際應用測試,以 deepseek-r1:7b/14b 模型做測試。得到結果如下
-
執行腳本 trans.js
const fs = require("fs");
const axios = require("axios");
// 1. 讀取本地JSON文件
const readJsonFile = (filePath) => {
return new Promise((resolve, reject) => {
fs.readFile(filePath, "utf8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(JSON.parse(data));
}
});
});
};
const MODEL = "deepseek-r1:14b";
// 2. 調用本地大模型接口進行翻譯
const translateText = async (text, key) => {
let response;
try {
console.time(`run worker ${key}`);
response = await axios.post("http://localhost:11434/api/generate", {
// model: 'deepseek-r1:7b',
model: MODEL,
prompt: `有部分客戶端國際化的配置文件,內容爲json格式,需要翻譯,要求按步驟進行翻譯:
1. 將中文翻譯爲英文
2. 保持原有json格式不變,將value替換成翻譯後的文本
3. 你始終以合法的JSON格式響應,返回結果格式如: {"key1":"翻譯後的文本1","key2":"翻譯後的文本2"},直接返回結果,不需要符號包裹
配置文件
"""${JSON.stringify(text)}"""`,
stream: false,
});
console.timeEnd(`run worker ${key}`);
const splitText = "</think>";
const startIndex = response.data.response.indexOf(splitText);
const result = response.data.response
.slice(startIndex + splitText.length)
.trim()
.replace(/<<+|>>+/g, "");
// console.log('response.data.response:', response.data.response, JSON.parse(result), result)
return JSON.parse(result); // 假設接口返回的翻譯結果在response.data.translatedText中
} catch (error) {
console.error("翻譯出錯:", key);
return translateText(text, key); // 如果翻譯失敗,返回原文
}
};
// 3. 並行翻譯邏輯(手動控制併發)
const translateJson = async (jsonData, concurrency = 5) => {
const entries = Object.entries(jsonData);
const translatedData = {};
let currentIndex = 0; // 當前處理的任務索引
// 定義工作線程:每個線程不斷處理下一個任務
const worker = async () => {
while (currentIndex < entries.length) {
const index = currentIndex++;
if (index >= entries.length) break; // 所有任務已完成
const [key, value] = entries[index];
try {
translatedData[key] = await translateText(value, key);
} catch (error) {
translatedData[key] = value; // 保留原文
}
}
};
// 啓動指定數量的工作線程
const workers = Array(concurrency).fill(null).map(worker);
await Promise.all(workers); // 等待所有線程完成
const result = {};
// 保持原有順序
entries.forEach(([key, value]) => {
result[key] = translatedData[key] || value;
});
return result;
};
// 4. 將翻譯後的內容生成新的文件
const writeTranslatedJson = (filePath, data) => {
return new Promise((resolve, reject) => {
fs.writeFile(filePath, JSON.stringify(data, null, 2), "utf8", (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
function compareObjectsWithPath(obj1, obj2, path = "") {
// 類型不同時直接返回路徑
if (typeof obj1 !== typeof obj2) {
return { success: false, path: path || "root" };
}
// 處理可遍歷對象(對象或數組)
if (typeof obj1 === "object" && obj1 !== null && obj2 !== null) {
const isArr1 = Array.isArray(obj1);
const isArr2 = Array.isArray(obj2);
// 數組類型不一致
if (isArr1 !== isArr2) {
return { success: false, path: path || "root" };
}
if (isArr1) {
// 數組長度不同
if (obj1.length !== obj2.length) {
return { success: false, path: path || "root" };
}
// 遞歸檢查數組元素
for (let i = 0; i < obj1.length; i++) {
const currentPath = `${path}[${i}]`;
const result = compareObjectsWithPath(obj1[i], obj2[i], currentPath);
if (!result.success) return result;
}
return { success: true };
} else {
// 檢查是否爲純對象(字面量對象)
const isPlainObj1 = isPlainObject(obj1);
const isPlainObj2 = isPlainObject(obj2);
if (isPlainObj1 !== isPlainObj2) {
return { success: false, path: path || "root" };
}
// 非純對象(如 Date、RegExp)需檢查是否均爲字符串
if (!isPlainObj1) {
return typeof obj1 === "string" && typeof obj2 === "string"
? { success: true }
: { success: false, path: path || "root" };
}
// 合併所有 key 並檢查數量
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
const allKeys = new Set([...keys1, ...keys2]);
if (allKeys.size !== keys1.length || allKeys.size !== keys2.length) {
return { success: false, path: path || "root" };
}
// 遞歸檢查每個屬性
for (const key of allKeys) {
const currentPath = path ? `${path}.${key}` : key;
if (!keys1.includes(key) || !keys2.includes(key)) {
return { success: false, path: currentPath };
}
const result = compareObjectsWithPath(
obj1[key],
obj2[key],
currentPath
);
if (!result.success) return result;
}
return { success: true };
}
} else {
// 基本類型:檢查是否均爲字符串
return typeof obj1 === "string" && typeof obj2 === "string"
? { success: true }
: { success: false, path: path || "root" };
}
}
// 判斷是否爲純對象(字面量對象)
function isPlainObject(value) {
return Object.prototype.toString.call(value) === "[object Object]";
}
// 主函數
const main = async () => {
console.time("run main");
const inputFilePath = "./locales/zh.json"; // 輸入的JSON文件路徑
const outputFilePath = `output_${MODEL}.json`; // 輸出的JSON文件路徑
try {
// 讀取JSON文件
const jsonData = await readJsonFile(inputFilePath);
// 翻譯JSON內容
const translatedData = await translateJson(jsonData);
// 將翻譯後的內容寫入新文件
await writeTranslatedJson(outputFilePath, translatedData);
console.log(
"翻譯完成,結果是否存在遺漏項:",
compareObjectsWithPath(jsonData, translatedData)
);
console.log("翻譯完成,結果已寫入:", outputFilePath);
} catch (error) {
console.error("處理過程中出錯:", error);
}
console.timeEnd("run main");
};
// 執行主函數
main();
7b
run worker window: 1:16.909 (m:ss.mmm)
翻譯出錯: window
run worker contextMenu: 1:19.915 (m:ss.mmm)
翻譯出錯: contextMenu
run worker autoUpdater: 1:24.182 (m:ss.mmm)
run worker menu: 1:54.272 (m:ss.mmm)
run worker openWindowWarn: 2:08.219 (m:ss.mmm)
翻譯出錯: openWindowWarn
run worker contextMenu: 54.257s
翻譯出錯: contextMenu
run worker createPreloadFileWarn: 1:05.595 (m:ss.mmm)
翻譯出錯: createPreloadFileWarn
run worker window: 1:13.320 (m:ss.mmm)
翻譯出錯: window
run worker openWindowWarn: 42.933s
run worker renderer: 1:06.620 (m:ss.mmm)
run worker contextMenu: 58.129s
run worker createPreloadFileWarn: 51.205s
run worker window: 1:10.067 (m:ss.mmm)
翻譯出錯: window
run worker window: 17.583s
翻譯出錯: window
run worker window: 16.479s
翻譯出錯: window
run worker window: 53.783s
翻譯完成,結果是否存在遺漏項: { success: false, path: 'menu' }
翻譯完成,結果已寫入: output_deepseek-r1:7b.json
run main: 5:08.166 (m:ss.mmm)

----------------
run worker openWindowWarn: 27.835s
翻譯出錯: openWindowWarn
run worker window: 47.317s
翻譯出錯: window
run worker contextMenu: 1:00.365 (m:ss.mmm)
翻譯出錯: contextMenu
run worker openWindowWarn: 42.320s
run worker window: 1:00.580 (m:ss.mmm)
翻譯出錯: window
run worker menu: 2:01.575 (m:ss.mmm)
翻譯出錯: menu
run worker contextMenu: 1:05.158 (m:ss.mmm)
run worker autoUpdater: 2:08.553 (m:ss.mmm)
run worker createPreloadFileWarn: 1:41.123 (m:ss.mmm)
run worker window: 1:28.518 (m:ss.mmm)
翻譯出錯: window
run worker renderer: 1:46.725 (m:ss.mmm)
run worker menu: 1:54.031 (m:ss.mmm)
翻譯出錯: menu
run worker window: 57.867s
run worker menu: 1:16.267 (m:ss.mmm)
翻譯完成,結果是否存在遺漏項: { success: false, path: 'menu' }
翻譯完成,結果已寫入: output_deepseek-r1:7b.json
run main: 5:11.880 (m:ss.mmm)

翻譯結果
"window": {
"willUnload": {
"title": "What should you confirm before refreshing the current page?",
"message": "the system might not save your changes",
"unload_bt": "Reload",
"cancel_bt": "Cancel"
}
},
14b
run worker window: 2:15.983 (m:ss.mmm)
run worker contextMenu: 2:17.554 (m:ss.mmm)
run worker autoUpdater: 3:02.960 (m:ss.mmm)
run worker menu: 4:06.753 (m:ss.mmm)
run worker openWindowWarn: 4:14.074 (m:ss.mmm)
run worker createPreloadFileWarn: 2:04.443 (m:ss.mmm)
run worker renderer: 2:21.099 (m:ss.mmm)
翻譯完成,結果是否存在遺漏項: { success: true }
翻譯完成,結果已寫入: output_deepseek-r1:14b.json
run main: 4:38.673 (m:ss.mmm)
------------------------
run worker autoUpdater: 1:34.068 (m:ss.mmm)
run worker openWindowWarn: 1:57.715 (m:ss.mmm)
run worker window: 2:09.907 (m:ss.mmm)
run worker contextMenu: 2:14.214 (m:ss.mmm)
run worker renderer: 1:38.631 (m:ss.mmm)
run worker createPreloadFileWarn: 2:24.484 (m:ss.mmm)
run worker menu: 4:16.409 (m:ss.mmm)
翻譯出錯: menu
run worker menu: 2:00.482 (m:ss.mmm)
翻譯完成,結果是否存在遺漏項: { success: true }
翻譯完成,結果已寫入: output_deepseek-r1:14b.json
run main: 6:16.900 (m:ss.mmm)
翻譯結果
"window": {
"willUnload": {
"title": "Confirm to refresh the current page?",
"message": "The system may not save your changes.",
"unload_bt": "Reload",
"cancel_bt": "Cancel"
}
},
- 整體體驗下來,14b 模型在翻譯工作上比 7b 模型更爲準確,一次性翻譯成功率高。7B 模型翻譯結果噪聲多,返回結果可序列化效果差。翻譯結果遠遠不如 14b。
結論
14b 在 macos 執行效率能滿足特定業務場景要求
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/SPEvYTmTBxhoEkJqm1yPmw