本地部署Deepseek R1大模型实践:从Ollama到个人知识库应用
一、Ollama安装与配置
1.1 跨平台安装指南
Ollama作为本地运行大模型的利器,支持三大主流操作系统:
- macOS一键安装:无具体命令展示,可能需进一步查阅官方文档
- Windows用户:访问官网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
- 下载地址:
https://chatboxai.app/zh
- 配置本地模型:
-
进入设置页面
-
选择ollama api (本地部署)。
-
配置本机地址,默认
http://127.0.0.1:11434
-
至此即可开启问答模式
3.2 Page Assist浏览器插件
- 安装地址:Page Assist - 本地AI模型的Web UI
- 安装后简单配置即可开启问答模式,功能丰富,可参考官方引导。本插件支持本地知识库建设,因本次使用Dify建设,在此不赘述。
三、Dify知识库搭建
参考文档地址:Docker Compose部署
3.1 环境准备
- 拉取源代码,准备环境(以mac os为例)
# 克隆Dify源代码至本地环境。
git clone https://github.com/langgenius/dify.git
# 进入Dify源代码的Docker目录
cd dify/docker
# 复制环境配置文件
cp.env.example.env
- 启动Docker容器(需要先安装Docker)
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,在配置中添加
-
写入以下内容
{
//...
"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"
]
}
3.2 Dify创建聊天
- 访问
http://localhost/
(默认80端口)进入dify - 首次进入初始化设置账号密码。
- 点击Dify平台右上角头像 → 设置 → 模型供应商,选择Ollama,轻点“添加模型”。
在配置url时,因为是docker服务,http://localhost:11434
存在无法访问的情况,可尝试http://host.docker.internal:11434
4. 至此,可以开始创建应用,在主页选择全部 -> 创建空白应用 -> 填入应用信息即可
3.3 Dify知识库创建
- 主页选择知识库 -> 创建知识库 -> 上传知识 -> 等待处理完成
- 进入聊天应用,选择刚才创建的知识库,即可开始带有私域知识的沟通
四、应用测试
4.1 翻译场景
(1) 本地客户端有部分国际化测试文件需执行翻译,格式为多层嵌套的json格式,value为string
类型。需利用大模型对整个json文件进行翻译,将中文翻译为英文后按原格式返回。
例如原始文件zh.json
内容如下:
{
"window": {
"willUnload": {
"title": "确认刷新当前页面吗?",
"message": "系统可能不会保存您做的更改",
"unload_bt": "重新加载",
"cancel_bt": "取消"
}
}
}
(2) 实际应用测试,以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。
希望这篇文章能给大家带来一些启发和思考。
如果你觉得有用,记得关注、点赞、分享给更多的朋友哦!
看更多走心好文请扫码关注公众号,后续分享更多AI 大模型干货:
点亮【关注、赞和在看】,让钱和爱都流向你。
欢迎加入我的2024年AI社群,后续分享更多AI 大模型干货,大家一起成长。若想进一步了解,请选择以下通道详询!(备注来意:【AI】)