本地 AI 自动化助手:n8n 编排、Ollama 推理、Qdrant 检索的离线架构。
本文是《我在家里的 NAS 上,搭了一个不联网的 AI 助手》的技术扩展版。包含完整的 Docker Compose 配置、n8n 工作流 JSON 解读、API 调用示例、初始化脚本,以及所有踩坑点的详细解决方案。适合想完整复刻或深入理解每个组件的读者。
系统架构
┌─────────────────────────────────────────────────────┐
│ 你的电脑 / NAS │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ n8n │───▶│ Ollama │ │ Qdrant │ │
│ │ :5678 │ │ :11434 │ │ :6333 │ │
│ │ │ │ │ │ │ │
│ │ 工作流 │ │ qwen2.5:7b │ │ 向量数据库 │ │
│ │ 编排 │ │ (LLM推理) │ │ (语义检索) │ │
│ │ │ │ │ │ │ │
│ │ │───▶│nomic-embed │───▶│ documents │ │
│ │ │ │ (文本向量化) │ │ collection │ │
│ └──────────┘ └──────────────┘ └────────────┘ │
│ │ ▲ │
│ └────────────────────────────────────┘ │
│ 检索 + 生成 │
└─────────────────────────────────────────────────────┘
| 组件 | 角色 | 端口 | 资源需求 |
|---|---|---|---|
| n8n | 工作流引擎,Webhook 入口,编排所有调用 | 5678 | CPU 1-2核,RAM 512MB-1GB |
| Ollama | 本地 LLM 推理 + Embedding | 11434 | RAM 8GB+(7B模型),CPU推理或GPU加速 |
| Qdrant | 向量数据库,语义检索 | 6333 (HTTP) / 6334 (gRPC) | RAM 512MB-2GB,磁盘按数据量 |
Docker Compose 完整配置
version: "3.8"
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
networks:
- ai-local
restart: unless-stopped
# CPU-only 模式,无需 GPU 配置
# 如果有 NVIDIA GPU,取消下方注释:
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant
ports:
- "6333:6333" # HTTP API
- "6334:6334" # gRPC
volumes:
- qdrant_data:/qdrant/storage
networks:
- ai-local
restart: unless-stopped
n8n:
image: n8nio/n8n:latest
container_name: n8n
ports:
- "5678:5678"
volumes:
- n8n_data:/home/node/.n8n
networks:
- ai-local
restart: unless-stopped
environment:
- N8N_HOST=localhost
- N8N_PORT=5678
- N8N_PROTOCOL=http
- WEBHOOK_URL=http://localhost:5678/
- GENERIC_TIMEZONE=Asia/Shanghai
# 容器内通过服务名访问 Ollama 和 Qdrant
- OLLAMA_BASE_URL=http://ollama:11434
- QDRANT_BASE_URL=http://qdrant:6333
volumes:
ollama_data:
qdrant_data:
n8n_data:
networks:
ai-local:
driver: bridge
关键配置说明:
-
网络:三个服务都在
ai-localbridge 网络中,容器间可以通过服务名互相访问。这是解决"n8n 容器内访问不了 Ollama"的关键——用http://ollama:11434,不是http://localhost:11434。 -
持久化:三个 volume 分别保存 Ollama 模型、Qdrant 向量数据、n8n 工作流和凭据。
docker compose down不会删除 volume,只有docker compose down -v才会。 -
GPU 支持:默认是 CPU 模式。如果有 NVIDIA GPU,取消注释
deploy段即可。需要系统已安装 NVIDIA Container Toolkit。 -
时区:
GENERIC_TIMEZONE=Asia/Shanghai确保 n8n 的执行日志和定时任务使用正确时区。
初始化脚本
启动容器后,需要拉取模型并创建 Qdrant collection:
#!/bin/bash
# init.sh - 初始化模型和向量数据库
set -e
OLLAMA_URL="${OLLAMA_URL:-http://localhost:11434}"
QDRANT_URL="${QDRANT_URL:-http://localhost:6333}"
echo "=== 1. 拉取 Embedding 模型 ==="
curl -s -X POST "$OLLAMA_URL/api/pull" \
-d '{"name": "nomic-embed-text"}' | \
python3 -c "import sys,json; [print(json.loads(l).get('status','')) for l in sys.stdin if l.strip()]"
echo ""
echo "=== 2. 拉取 LLM 模型 ==="
curl -s -X POST "$OLLAMA_URL/api/pull" \
-d '{"name": "qwen2.5:7b"}' | \
python3 -c "import sys,json; [print(json.loads(l).get('status','')) for l in sys.stdin if l.strip()]"
echo ""
echo "=== 3. 创建 Qdrant Collection ==="
curl -s -X PUT "$QDRANT_URL/collections/documents" \
-H 'Content-Type: application/json' \
-d '{
"vectors": {
"size": 768,
"distance": "Cosine"
}
}' | python3 -m json.tool
echo ""
echo "=== 初始化完成 ==="
echo "已拉取: nomic-embed-text (274MB), qwen2.5:7b (4.7GB)"
echo "已创建: Qdrant collection 'documents' (768维, Cosine)"
注意事项:
nomic-embed-text输出 768 维向量,Qdrant collection 的size必须对应 768- 距离度量用
Cosine(余弦相似度),适合文本语义检索 - 首次拉取 qwen2.5:7b 约 4.7GB,需要 5-15 分钟。Ollama 的 pull API 是流式返回,curl 不会显示进度条,看起来像卡住了。可以用
docker compose logs -f ollama查看进度
文档摄入、向量检索与问答生成拆成可复用工作流,方便在 NAS 或本地机器上复刻。
n8n 工作流详解
系统包含两个工作流:文档摄入(Ingestion)和文档问答(Query)。
工作流 1:文档摄入
流程: Webhook → 文本分块 → Ollama Embedding → 准备 Qdrant 数据 → 存入 Qdrant → 返回结果
完整 JSON(可直接导入 n8n):
{
"name": "文档摄入 - Document Ingestion",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "ingest",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-ingest",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [250, 300]
},
{
"parameters": {
"functionCode": "const text = $input.first().json.body.text || '';\nconst title = $input.first().json.body.title || 'untitled';\nconst chunkSize = 500;\nconst overlap = 50;\n\nconst chunks = [];\nlet i = 0;\nwhile (i < text.length) {\n const end = Math.min(i + chunkSize, text.length);\n chunks.push({\n content: text.slice(i, end),\n title: title,\n chunk_index: chunks.length,\n metadata: { start: i, end: end }\n });\n i += chunkSize - overlap;\n}\n\nreturn chunks.map(c => ({ json: c }));"
},
"id": "chunk-text",
"name": "文本分块",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [450, 300]
},
{
"parameters": {
"method": "POST",
"url": "http://ollama:11434/api/embeddings",
"sendBody": true,
"options": {},
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ model: \"nomic-embed-text\", prompt: $json.content }) }}"
},
"id": "ollama-embed",
"name": "Ollama Embedding",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [650, 300]
},
{
"parameters": {
"functionCode": "const chunk = $items('文本分块')[$itemIndex].json;\nconst embedding = $input.first().json.embedding;\n\nreturn [{\n json: {\n id: Date.now() + $itemIndex,\n vector: embedding,\n payload: {\n content: chunk.content,\n title: chunk.title,\n chunk_index: chunk.chunk_index,\n metadata: chunk.metadata\n }\n }\n}];"
},
"id": "prepare-qdrant",
"name": "准备Qdrant数据",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [850, 300]
},
{
"parameters": {
"method": "PUT",
"url": "http://qdrant:6333/collections/documents/points",
"sendBody": true,
"options": {},
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ points: [$json] }) }}"
},
"id": "qdrant-store",
"name": "存入Qdrant",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1050, 300]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, chunks: $input.all().length }) }}",
"options": {}
},
"id": "respond",
"name": "Respond",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [1250, 300]
}
],
"connections": {
"Webhook": { "main": [[{ "node": "文本分块", "type": "main", "index": 0 }]] },
"文本分块": { "main": [[{ "node": "Ollama Embedding", "type": "main", "index": 0 }]] },
"Ollama Embedding": { "main": [[{ "node": "准备Qdrant数据", "type": "main", "index": 0 }]] },
"准备Qdrant数据": { "main": [[{ "node": "存入Qdrant", "type": "main", "index": 0 }]] },
"存入Qdrant": { "main": [[{ "node": "Respond", "type": "main", "index": 0 }]] }
},
"settings": { "executionOrder": "v1" }
}
关键节点解读:
文本分块(Function 节点): 把长文本切成 500 字一块,相邻块有 50 字重叠。重叠是为了避免语义在切割边界被截断。每块保留原文、标题、块索引和元数据。
Ollama Embedding(HTTP Request 节点): 调用 Ollama 的 /api/embeddings 接口,模型是 nomic-embed-text,输入是分块后的文本,输出是 768 维浮点向量。
存入 Qdrant(HTTP Request 节点): 调用 Qdrant 的 REST API,把向量和原文 payload 一起存入 documents collection。注意 body 格式必须是 {"points": [...]},不能直接传数组。
工作流 2:文档问答
流程: Webhook → 查询向量化 → Qdrant 检索 → 构建 Prompt → Ollama 生成答案 → 返回结果
完整 JSON(可直接导入 n8n):
{
"name": "文档问答 - Document Q&A",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "query",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-query",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [250, 300]
},
{
"parameters": {
"method": "POST",
"url": "http://ollama:11434/api/embeddings",
"sendBody": true,
"options": {},
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ model: \"nomic-embed-text\", prompt: $input.first().json.body.query }) }}"
},
"id": "ollama-embed-query",
"name": "查询向量化",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [450, 300]
},
{
"parameters": {
"method": "POST",
"url": "http://qdrant:6333/collections/documents/points/search",
"sendBody": true,
"options": {},
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ vector: $json.embedding, limit: 3, with_payload: true }) }}"
},
"id": "qdrant-search",
"name": "Qdrant检索",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [650, 300]
},
{
"parameters": {
"functionCode": "const query = $('Webhook').first().json.body.query;\nconst body = $input.first().json;\nconst results = body.result || [];\nconst contexts = results.map(r => r.payload.content).join('\\n\\n---\\n\\n');\n\nconst prompt = `基于以下参考资料回答问题。如果资料中没有相关信息,请说明。\\n\\n参考资料:\\n${contexts}\\n\\n问题:${query}\\n\\n答案:`;\n\nreturn [{\n json: {\n prompt: prompt,\n context: contexts\n }\n}];"
},
"id": "build-prompt",
"name": "构建Prompt",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [850, 300]
},
{
"parameters": {
"method": "POST",
"url": "http://ollama:11434/api/generate",
"sendBody": true,
"options": {},
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ model: \"qwen2.5:7b\", prompt: $json.prompt, stream: false }) }}"
},
"id": "ollama-generate",
"name": "Ollama生成答案",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1050, 300]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ answer: $input.first().json.response, context: $('构建Prompt').first().json.context }) }}",
"options": {}
},
"id": "respond-answer",
"name": "Respond",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [1250, 300]
}
],
"connections": {
"Webhook": { "main": [[{ "node": "查询向量化", "type": "main", "index": 0 }]] },
"查询向量化": { "main": [[{ "node": "Qdrant检索", "type": "main", "index": 0 }]] },
"Qdrant检索": { "main": [[{ "node": "构建Prompt", "type": "main", "index": 0 }]] },
"构建Prompt": { "main": [[{ "node": "Ollama生成答案", "type": "main", "index": 0 }]] },
"Ollama生成答案": { "main": [[{ "node": "Respond", "type": "main", "index": 0 }]] }
},
"settings": { "executionOrder": "v1" }
}
关键节点解读:
查询向量化: 和摄入流程一样调用 Ollama embedding,但输入是用户的查询文本。
Qdrant 检索: 调用 /collections/documents/points/search,传入查询向量和 limit: 3,返回最相似的 3 个文档片段。Qdrant 返回的结果包含 score(相似度分数)和 payload(原文内容)。
构建 Prompt(Function 节点): 把检索到的 3 个片段拼接成参考资料,和用户问题一起组成 RAG Prompt。这个 Prompt 会引导 LLM 基于参考资料回答,而不是自由发挥。
Ollama 生成答案: 调用 /api/generate,模型是 qwen2.5:7b,stream: false 表示一次性返回完整回答(不是流式)。
API 调用参考
Ollama API
# 拉取模型
curl -X POST http://localhost:11434/api/pull \
-d '{"name": "qwen2.5:7b"}'
# 文本 Embedding
curl -X POST http://localhost:11434/api/embeddings \
-H 'Content-Type: application/json' \
-d '{
"model": "nomic-embed-text",
"prompt": "Docker是什么?"
}'
# 返回: {"embedding": [0.0123, -0.0456, ...]} (768维)
# 文本生成
curl -X POST http://localhost:11434/api/generate \
-H 'Content-Type: application/json' \
-d '{
"model": "qwen2.5:7b",
"prompt": "基于以下资料回答问题。\n\n资料:Docker是一个容器化平台。\n\n问题:Docker是什么?",
"stream": false
}'
# 返回: {"response": "Docker是一个...", "done": true}
# 查看已拉取的模型
curl http://localhost:11434/api/tags
Qdrant API
# 创建 collection
curl -X PUT http://localhost:6333/collections/documents \
-H 'Content-Type: application/json' \
-d '{"vectors": {"size": 768, "distance": "Cosine"}}'
# 存入向量
curl -X PUT http://localhost:6333/collections/documents/points \
-H 'Content-Type: application/json' \
-d '{
"points": [{
"id": 1,
"vector": [0.0123, -0.0456, ...],
"payload": {"content": "Docker是一个...", "title": "Docker入门"}
}]
}'
# 向量检索
curl -X POST http://localhost:6333/collections/documents/points/search \
-H 'Content-Type: application/json' \
-d '{
"vector": [0.0123, -0.0456, ...],
"limit": 3,
"with_payload": true
}'
# 返回: {"result": [{"id": 1, "score": 0.99, "payload": {...}}]}
# 查看 collection 状态
curl http://localhost:6333/collections/documents
# 返回: {"result": {"status": "green", "points_count": 2, ...}}
# 删除 collection
curl -X DELETE http://localhost:6333/collections/documents
n8n Webhook 调用
# 文档摄入
curl -X POST http://localhost:5678/webhook/ingest \
-H 'Content-Type: application/json' \
-d '{
"title": "Docker入门指南",
"text": "Docker是一个开源的容器化平台,可以让开发者将应用及其依赖打包成轻量级容器。Docker使用镜像来定义应用运行环境,使用容器来运行镜像实例。Docker Compose是一个用于定义和运行多容器应用的工具,通过YAML文件配置应用的服务、网络和存储卷。"
}'
# 返回: {"success": true, "chunks": 1}
# 文档问答
curl -X POST http://localhost:5678/webhook/query \
-H 'Content-Type: application/json' \
-d '{"query": "Docker Compose是什么?"}'
# 返回:
# {
# "answer": "Docker Compose是一个用于定义和运行多容器应用的工具。通过使用YAML文件,开发人员可以配置服务、网络和存储卷来描述他们的应用环境。",
# "context": "Docker是一个开源的容器化平台..."
# }
实测结果
运行环境
- Hermes worker 容器:Linux x86_64,无 Docker daemon
- Qdrant:1.18.2,本地二进制运行,监听 127.0.0.1:6333
- n8n:1.111.1,npm 本地安装,监听 127.0.0.1:5678
- Ollama:NAS 服务 192.168.100.152:11434
- Embedding 模型:nomic-embed-text,768 维
- LLM 模型:qwen2:latest
Qdrant Collection 状态
{
"status": "ok",
"result": {
"status": "green",
"points_count": 2,
"config": {
"params": {
"vectors": {
"size": 768,
"distance": "Cosine"
}
}
}
}
}
【截图插入位 1:Qdrant collection 状态 JSON 输出截图】
n8n 工作流执行记录
两个 webhook workflow 均成功执行:
执行ID: 1 | Workflow: X8ANwqaO4kGbuNfe | 状态: success | 触发: webhook | 开始: 07:46:19 | 结束: 07:46:19
执行ID: 2 | Workflow: t0IChkQD6yKg2Fk0 | 状态: success | 触发: webhook | 开始: 07:46:19 | 结束: 07:46:39
【截图插入位 2:n8n Executions 页面截图,显示两次成功执行】
文档问答结果
输入:{"query": "Docker Compose是什么?"}
返回:
{
"answer": "Docker Compose是一个用于定义和运行多容器应用的工具。通过使用YAML文件,开发人员可以配置服务、网络和存储卷来描述他们的应用环境。这使得在本地测试和部署复杂的多服务应用程序变得更加简单和高效。",
"context": "Docker是一个开源的容器化平台...Docker Compose是一个用于定义和运行多容器应用的工具..."
}
【截图插入位 3:终端中 curl 调用 webhook/query 的输入输出截图】
手动 RAG 链路验证
为排除 n8n 外部因素,用 Python 直接调用 Ollama + Qdrant:
- Qdrant upsert:
status=acknowledged - Qdrant 检索 top score:
0.7697159 - LLM 回答:成功生成 Docker Compose 定义
两条路径结果一致,确认系统端到端可用。
踩坑全记录
坑 1:n8n 容器内访问 Ollama 用不了 localhost
现象: n8n 工作流执行时,HTTP Request 节点调用 Ollama 报错 timeout 或 connection refused。
原因: Docker 容器内的 localhost 指向容器自身,不是宿主机。n8n 容器要用 http://ollama:11434(通过 Docker 网络的服务名解析)。
解决: 确保工作流中的 URL 使用服务名:
- ✅ http://ollama:11434/api/embeddings
- ❌ http://localhost:11434/api/embeddings
从宿主机 curl 测试时用 localhost,从 n8n 容器内调用时用 ollama。
坑 2:Qdrant 向量维度不匹配
现象: 存入向量时报错 Wrong input: Vector dimension error。
原因: nomic-embed-text 输出 768 维向量,但 Qdrant collection 创建时指定了错误的维度。
解决: 创建 collection 时必须指定 size: 768。如果已经创建了错误维度的 collection,必须先删除再重建:
curl -X DELETE http://localhost:6333/collections/documents
# 然后重新运行 init.sh
坑 3:n8n Function 节点版本兼容
现象: 导入 workflow JSON 后,Function 节点报错 Unexpected token 或参数不识别。
原因: n8n 不同版本对 Function 节点的参数名不同。旧版用 function,新版(1.111+)需要 functionCode。HTTP Request 节点也需要使用 contentType: json + specifyBody: json + jsonBody 的组合。
解决: 本文提供的 workflow JSON 已针对 n8n 1.111.1 修复。如果你的版本不同,可能需要调整参数名。
坑 4:Qdrant upsert body 格式
现象: 存入 Qdrant 时报错 Wrong input: body is expected。
原因: Qdrant 的 /points 接口要求 body 是 {"points": [...]},不能直接传数组。也不能把数组序列化成字符串。
解决: Function 节点输出的 JSON 必须经过 JSON.stringify({ points: [$json] }) 包装。
坑 5:Qdrant search 的 vector 字段
现象: 检索时报错 Wrong input: vector is expected。
原因: Qdrant search API 的 vector 字段必须是数组,不能作为字符串提交。
解决: 在构建请求 body 时确保 vector 是原始数组:{ vector: $json.embedding, limit: 3, with_payload: true }。
坑 6:CPU 推理速度慢
现象: qwen2.5:7b 在 CPU 上生成答案很慢(10-30 秒)。
原因: 7B 参数模型在纯 CPU 上推理确实不快。
缓解方案:
- 使用更小的模型:qwen2.5:3b(约 2GB),速度快很多
- 如果有 GPU:取消 docker-compose.yml 中 GPU 配置的注释
- 设置合理的 num_ctx:减少上下文长度可以加速
- 首次推理会慢(模型加载),后续会快一些
坑 7:Docker Compose version 属性警告
现象: the attribute 'version' is obsolete, it will be ignored
原因: Docker Compose v2+ 不再需要 version 字段。
解决: 可以直接忽略(只是警告),也可以从 docker-compose.yml 中删除 version: "3.8" 行。
坑 8:内存不足导致 OOM Kill
现象: Ollama 容器被 kill,docker compose ps 显示 exited。
原因: qwen2.5:7b 推理时需要约 5-8GB 内存。
解决: 确保系统至少有 8GB 可用内存。使用更小的模型或设置内存限制。
本地部署不是零成本,但对高频、敏感数据场景,长期成本和数据边界更可控。
成本测算数据
云端 API 定价(2026年7月)
| 模型 | 输入 $/百万token | 输出 $/百万token | 上下文窗口 |
|---|---|---|---|
| OpenAI GPT-4o | $2.50 | $10.00 | 128K |
| Claude Sonnet 4.6 | $3.00 | $15.00 | 1M |
| DeepSeek V3 | $0.27 | $1.10 | 128K |
本地设备功耗参考
| 设备类型 | 推理时功耗 | 24h平均 | 年电费(¥0.56/kWh) |
|---|---|---|---|
| NAS(4盘位) | 35-55W | ~43W | ¥211 |
| Mac mini M1 | ~15W | ~12W | ¥59 |
| Mac mini M4 | ~12W | ~8W | ¥39 |
| N100 迷你主机 | 8-12W | ~10W | ¥49 |
| GPU 台式机(RTX 4060) | ~200W | ~80W | ¥392 |
| GPU 台式机(RTX 4090) | ~370W | ~150W | ¥736 |
三档场景月成本对比
| 方案 | 轻度(50次/天) | 中度(200次/天) | 高频(1000次/天) |
|---|---|---|---|
| GPT-4o | ¥107 | ¥858 | ¥8,580 |
| Claude Sonnet 4.6 | ¥145 | ¥1,158 | ¥11,583 |
| DeepSeek V3 | ¥12 | ¥94 | ¥937 |
| NAS + Ollama(已有) | ¥3-5 | ¥10-15 | ¥30-50 |
| Mac mini M1 + Ollama | ¥5-8 | ¥12-18 | ¥40-60 |
| GPU台式机 RTX 4060 | — | ¥30-50 | ¥100-180 |
设备购置回本周期(对比 GPT-4o,中度场景)
| 设备 | 购置成本 | 月节省 | 回本周期 |
|---|---|---|---|
| Mac mini M1(二手) | ¥2,500-3,500 | ~¥840 | 3-4个月 |
| Mac mini M4 | ¥4,000-5,000 | ~¥840 | 5-6个月 |
| GPU 台式机 RTX 4060 | ¥5,000-7,000 | ~¥830 | 6-8个月 |
对比 DeepSeek V3(¥94/月),本地方案回本需要 36-67 个月,不推荐纯为省钱而本地部署。
扩展方向
- 笔记自动标签:修改 ingest workflow,让 LLM 自动生成标签并存入 payload
- 多文档源:添加 n8n 节点读取本地文件夹 / RSS / 网页
- 更大模型:如果有 GPU,可以换
qwen2.5:14b或llama3.1:8b - 对话历史:在 Qdrant 中额外存一个
conversationscollection - Web UI:n8n 自带表单节点可以做简单的问答界面
- 混合方案:简单任务走本地 7B,复杂任务走 GPT-4o/Claude API
常用运维命令
# 查看服务状态
docker compose ps
# 查看日志
docker compose logs -f ollama
docker compose logs -f n8n
docker compose logs -f qdrant
# 停止服务(保留数据)
docker compose down
# 停止并清除所有数据
docker compose down -v
# 查看已拉取的模型
curl http://localhost:11434/api/tags | python3 -m json.tool
# 拉取更多模型
curl -X POST http://localhost:11434/api/pull -d '{"name": "llama3.2:3b"}'
# 查看 Qdrant collection 状态
curl http://localhost:6333/collections/documents | python3 -m json.tool
数据来源
- OpenAI 官方定价页:https://platform.openai.com/docs/pricing
- Anthropic 官方定价页:https://platform.claude.com/docs/en/about-claude/pricing
- DeepSeek 官方定价页:https://api-docs.deepseek.com/
- Apple Mac mini 功耗数据:https://support.apple.com/en-us/103253
- NAS 功耗实测:https://raidsize.com/blog/en/nas-power-consumption
- LLM 推理功耗基准:https://runopensourcellm.blogspot.com/2026/05/llm-power-consumption-guide-watts-per.html
- n8n Self-Hosted AI Starter Kit:https://github.com/n8n-io/self-hosted-ai-starter-kit
- Ollama 官方 n8n 集成文档:https://docs.ollama.com/integrations/n8n
- Qdrant 官方文档:https://qdrant.tech/documentation/
- 中国居民电价均值 ¥0.56/kWh(国家能源局 2025 年度统计)
- 汇率假设:1 USD = 7.15 CNY
配图建议: - 图 1:系统架构图(三组件关系 + 数据流向) - 图 2:Qdrant collection 状态 JSON 输出 - 图 3:n8n Executions 页面截图 - 图 4:curl 调用 webhook/query 的终端输出 - 图 5:成本对比表格可视化 - 图 6:n8n 工作流编辑器截图(可视化展示节点连接)


