在当今企业环境中,数据隐私和安全已成为不可忽视的核心需求。许多组织面临着两难困境:既希望利用大语言模型(LLM)的强大能力,又必须确保敏感数据不会离开本地环境。这正是我们构建"隐私优先"本地RAG(检索增强生成)知识库的出发点。
这个项目通过整合Docker、Ollama和LangChain三大技术栈,实现了:
关键优势:所有数据处理和模型推理都在企业内网完成,彻底杜绝了数据外泄风险,特别适合处理财务报告、人事档案、商业机密等敏感信息。
Docker作为本项目的基石,解决了AI应用部署中最头疼的环境一致性问题。通过容器化,我们将:
选择Docker而非其他容器技术的原因:
Ollama是目前最易用的本地大模型运行工具,相比直接使用HuggingFace Transformers库,它提供了:
我们选择Meta的Llama 3 8B模型是因为:
LangChain作为AI应用编排框架,在本项目中扮演"胶水"角色,将各个组件有机连接:
code复制[文档加载] → [文本分割] → [向量化] → [向量存储] → [检索] → [生成回答]
其核心价值在于:
根据实测,不同硬件配置下的性能表现:
| 硬件配置 | 推理速度 (tokens/s) | 内存占用 | 适用场景 |
|---|---|---|---|
| Mac M2 Max (32GB) | 45-50 | 12GB | 流畅交互体验 |
| NVIDIA RTX 4090 (24GB) | 55-60 | 10GB | 高性能需求 |
| Intel i7 + 32GB RAM | 8-10 | 9GB | 基础开发测试 |
| 云主机 (4核8G) | 5-8 | 7GB | 低频率内部使用 |
实践建议:对于生产环境,建议至少16GB内存和6GB以上显存。纯CPU环境虽然可行,但响应延迟可能影响用户体验。
系统采用微服务架构,各组件通过Docker网络通信:
code复制[前端Vue] ←HTTP→ [FastAPI应用] ←gRPC→ [Milvus向量库]
↑
[HTTP请求]
↓
[宿主机Ollama服务]
关键设计决策:
原始Dockerfile可进一步优化为多阶段构建,减少最终镜像大小:
dockerfile复制# 构建阶段
FROM python:3.10 as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# 运行时阶段
FROM python:3.10-slim
WORKDIR /app
# 从构建阶段复制已安装的包
COPY --from=builder /root/.local /root/.local
COPY . .
# 确保脚本能发现用户安装的包
ENV PATH=/root/.local/bin:$PATH
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]
优化效果:
确保容器能访问宿主机服务的核心配置:
yaml复制# docker-compose.yml关键片段
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- OLLAMA_BASE_URL=http://host.docker.internal:11434/v1
原理说明:
extra_hosts将主机名映射到宿主机IPhost-gateway解析hostpython复制from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
# 1. 文档加载与分块
loader = PyPDFLoader("policy.pdf")
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len,
is_separator_regex=False,
)
chunks = text_splitter.split_documents(docs)
# 2. 向量化模型选择
embedder = HuggingFaceEmbeddings(
model_name="BAAI/bge-small-zh-v1.5",
model_kwargs={"device": "cpu"}, # 可改为"cuda"加速
)
# 3. 向量存储
from langchain_community.vectorstores import Milvus
vector_db = Milvus.from_documents(
chunks,
embedder,
connection_args={"host": "milvus-standalone", "port": "19530"},
collection_name="company_policies"
)
关键参数说明:
chunk_size=1000:平衡检索精度和上下文完整性bge-small-zh-v1.5:专为中文优化的轻量级嵌入模型connection_args:指向Compose中定义的Milvus服务python复制from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
template = """你是一个专业的企业制度助手,请根据以下上下文回答问题:
{context}
问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
retriever = vector_db.as_retriever(search_kwargs={"k": 3})
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
优化技巧:
search_kwargs={"k": 3}:检索3个最相关片段平衡精度和速度配置结构化日志便于监控:
python复制import logging
from pythonjsonlogger import jsonlogger
logger = logging.getLogger()
logHandler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
"%(asctime)s %(levelname)s %(message)s",
datefmt="%Y-%m-%dT%H:%M:%SZ"
)
logHandler.setFormatter(formatter)
logger.addHandler(logHandler)
日志字段建议包含:
request_id:追踪单个请求全流程model_latency:记录LLM响应时间retrieved_chunks:分析检索效果使用Prometheus + Grafana监控关键指标:
示例Prometheus配置:
yaml复制# prometheus.yml
scrape_configs:
- job_name: 'rag_app'
static_configs:
- targets: ['app:8000']
- job_name: 'milvus'
static_configs:
- targets: ['milvus-standalone:9091']
端口冲突:
bash复制netstat -tuln | grep 8000
修改compose文件或停止占用端口的服务
内存不足:
bash复制docker stats
调整资源限制或优化模型配置
测试API可达性:
bash复制curl http://host.docker.internal:11434/api/generate -d '{
"model": "llama3",
"prompt": "Hello"
}'
防火墙设置:
bash复制sudo ufw allow 11434/tcp
调整分块策略:
python复制text_splitter = RecursiveCharacterTextSplitter(
chunk_size=800, # 减小块大小
separators=["\n\n", "。", "!", "?"], # 中文友好分隔符
)
重新嵌入文档:
bash复制docker-compose run app python manage.py reindex --collection policies
LoRA(低秩适应)通过在原始模型旁添加小型适配层实现微调:
code复制原始权重 W ∈ R^{d×k}
LoRA层 ΔW = BA^T, 其中 B ∈ R^{d×r}, A ∈ R^{k×r}, r ≪ min(d,k)
更新后权重 W' = W + ΔW
优势:
准备数据集(JSON格式):
json复制[
{
"instruction": "解释公司报销政策",
"input": "",
"output": "差旅费需在返回后15天内提交..."
}
]
启动训练:
bash复制llamafactory-cli train \
--model_name_or_path llama3 \
--data_path ./data/fine_tune.json \
--lora_rank 8 \
--learning_rate 3e-4 \
--per_device_train_batch_size 2
合并适配器:
bash复制ollama create my_company_model -f ./Modelfile
关键参数建议:
lora_rank=8:平衡效果和计算成本batch_size=2:消费级显卡的实用设置max_steps=500:小数据集足够收敛使用RAGAS框架进行量化评估:
python复制from ragas import evaluate
from datasets import Dataset
dataset = Dataset.from_dict({
"question": ["出差补贴标准是多少?"],
"answer": ["国内出差每日补贴200元..."],
"contexts": [["根据2023年财务制度..."]]
})
result = evaluate(
dataset,
metrics=["faithfulness", "answer_relevancy"],
)
print(result)
优化方向:
上传加密:
python复制from cryptography.fernet import Fernet
key = Fernet.generate_key()
cipher_suite = Fernet(key)
encrypted = cipher_suite.encrypt(file.read())
存储隔离:
yaml复制# docker-compose.yml
volumes:
policy_data:
driver_opts:
type: tmpfs
device: tmpfs
自动清理:
python复制import schedule
def clean_temp_files():
for f in Path("/temp").glob("*"):
if f.stat().st_mtime < time.time() - 3600:
f.unlink()
schedule.every().hour.do(clean_temp_files)
API认证:
python复制from fastapi.security import APIKeyHeader
api_key_header = APIKeyHeader(name="X-API-KEY")
async def validate_key(api_key: str = Depends(api_key_header)):
if api_key != os.getenv("API_KEY"):
raise HTTPException(status_code=403)
审计日志:
python复制@app.middleware("http")
async def audit_log(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
log_data = {
"path": request.url.path,
"method": request.method,
"duration": time.time() - start_time
}
logger.info(log_data)
return response
混合检索策略:
python复制from langchain.retrievers import BM25Retriever, EnsembleRetriever
bm25_retriever = BM25Retriever.from_documents(docs)
vector_retriever = vector_db.as_retriever()
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6]
)
查询扩展:
python复制from langchain.retrievers import QueryAugmentationRetriever
expanded_retriever = QueryAugmentationRetriever(
base_retriever=vector_retriever,
llm=llm,
include_original=True
)
量化加载:
bash复制ollama pull llama3:8b-instruct-q4_0
批处理请求:
python复制from langchain_core.runnables import RunnableParallel
batch_chain = RunnableParallel(
answer1=chain,
answer2=chain
)
batch_chain.batch([
{"question": "问题1"},
{"question": "问题2"}
])
缓存机制:
python复制from langchain.cache import SQLiteCache
langchain.llm_cache = SQLiteCache(database_path=".langchain.db")
在实际部署中,我们发现两个关键性能瓶颈:文档解析速度和检索延迟。通过将PyPDFLoader替换为速度更快的pdfminer.six,并将部分预处理步骤移到上传阶段异步执行,系统吞吐量提升了40%。同时,为Milvus配置适当的索引类型(HNSW)后,检索延迟从平均320ms降至180ms。