Files
knowledge-base/AI/mem0/deploy.md

405 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# mem0 自托管部署
## 镜像
```
ccr.ccs.tencentyun.com/tei_agent/mem0:latest
```
## 依赖服务tei namespace
| 服务 | 地址 | 用途 |
|---|---|---|
| PostgreSQL | 192.168.3.49:5432 | 关系数据存储 |
| Qdrant | qdrant:6333 | 向量存储 |
| TEI (BGE-M3) | tei:8080 | Embedder文本→向量 |
## 环境变量
### ConfigMap (mem0-env)
| 变量 | 值 | 说明 |
|---|---|---|
| APP_DB_NAME | mem0 | 数据库名 |
| QDRANT_HOST | qdrant | Qdrant 服务地址 |
| QDRANT_PORT | 6333 | Qdrant 端口 |
| QDRANT_COLLECTION_NAME | mem0 | 集合名 |
| EMBEDDER_PROVIDER | tei | Embedder 使用 TEI |
| TEI_ENDPOINT | http://tei:8080 | TEI endpoint |
| LLM_PROVIDER | openai | LLM providerMiniMax 兼容 OpenAI 格式) |
| OPENAI_API_KEY | YOUR_MINIMAX_KEY | MiniMax API key |
| OPENAI_BASE_URL | https://api.minimax.chat/v1 | MiniMax API 地址 |
| AUTH_DISABLED | false | 启用认证 |
| MEM0_TELEMETRY | false | 关闭遥测 |
| REQUEST_LOG_RETENTION_DAYS | 30 | 日志保留天数 |
| HISTORY_DB_PATH | /app/data/mem0_history.db | SQLite 历史数据库路径 |
### Secret (mem0-secrets)
| 变量 | 说明 |
|---|---|
| JWT_SECRET | JWT 签名密钥 |
| ADMIN_API_KEY | 管理后台 API key |
| POSTGRES_PASSWORD | PostgreSQL 密码 |
## 数据库迁移 (Alembic)
mem0 server 使用 Alembic 管理 PostgreSQL schema。**首次部署前必须先执行迁移**,创建 `request_logs` 等表。
### 迁移 Job
```yaml
apiVersion: batch/v1
kind: Job
metadata:
name: mem0-migrate
namespace: tei
spec:
ttlSecondsAfterFinished: 300 # 完成后5分钟自动清理
template:
spec:
restartPolicy: OnFailure
containers:
- name: alembic
image: ccr.ccs.tencentyun.com/tei_agent/mem0:latest
command: ["alembic", "upgrade", "head"]
envFrom:
- configMapRef:
name: mem0-env
- secretRef:
name: mem0-secrets
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: dify-prod-db-secret
key: password
resources:
limits:
memory: "512Mi"
cpu: "500m"
```
### 迁移部署步骤
```bash
# 1. 先跑迁移(只执行一次)
kubectl apply -f mem0-migrate-job.yaml
# 2. 确认迁移完成
kubectl get job mem0-migrate -n tei -w
# 3. 确认成功后再部署 mem0
kubectl apply -f mem0-deployment.yaml
# 4. 如果迁移失败,查看原因
kubectl logs job/mem0-migrate -n tei
```
**重要**:迁移 Job 只跑一次。Pod 重启时不需要重新迁移PostgreSQL schema 不会天天变。
## 前置要求
1. **pgvector 扩展** — PostgreSQL 需要安装 pgvector
2. **mem0 数据库** — 需要提前创建
3. **mem0-migrate Job** — 首次部署前必须先执行迁移
4. **mem0-history PVC** — 必须提前创建
5. **Qdrant collection** — mem0 启动时自动创建(首次调用时)
## 部署清单
### PVC + ConfigMap + Secret + Deployment + Service
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mem0-history
namespace: tei
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: standard # 根据你的 StorageClass 调整
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mem0-env
namespace: tei
data:
APP_DB_NAME: "mem0"
QDRANT_HOST: "qdrant"
QDRANT_PORT: "6333"
QDRANT_COLLECTION_NAME: "mem0"
EMBEDDER_PROVIDER: "tei"
TEI_ENDPOINT: "http://tei:8080"
LLM_PROVIDER: "openai"
OPENAI_API_KEY: "YOUR_MINIMAX_KEY"
OPENAI_BASE_URL: "https://api.minimax.chat/v1"
AUTH_DISABLED: "false"
MEM0_TELEMETRY: "false"
REQUEST_LOG_RETENTION_DAYS: "30"
HISTORY_DB_PATH: "/app/data/mem0_history.db"
---
apiVersion: v1
kind: Secret
metadata:
name: mem0-secrets
namespace: tei
type: Opaque
stringData:
JWT_SECRET: "your-jwt-secret-change-me"
ADMIN_API_KEY: "your-admin-key-change-me"
POSTGRES_PASSWORD: "gitlab"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mem0
namespace: tei
spec:
replicas: 1
selector:
matchLabels:
app: mem0
template:
metadata:
labels:
app: mem0
spec:
containers:
- name: mem0
image: ccr.ccs.tencentyun.com/tei_agent/mem0:latest
ports:
- containerPort: 8000
name: http
envFrom:
- configMapRef:
name: mem0-env
- secretRef:
name: mem0-secrets
volumeMounts:
- name: mem0-history
mountPath: /app/data
resources:
limits:
cpu: "2"
memory: "4Gi"
requests:
cpu: "500m"
memory: "1Gi"
volumes:
- name: mem0-history
persistentVolumeClaim:
claimName: mem0-history
---
apiVersion: v1
kind: Service
metadata:
name: mem0
namespace: tei
spec:
ports:
- port: 8000
name: http
selector:
app: mem0
```
## 验证
```bash
# 检查 PVC
kubectl get pvc -n tei mem0-history
# 检查 Pod
kubectl get pods -n tei -l app=mem0
# 查看日志
kubectl logs -n tei -l app=mem0 --tail=50
# 健康检查
curl http://mem0:8000/health
```
## History SQLite 说明
mem0 的对话历史存储在 SQLite位于 `/app/data/mem0_history.db`。**必须用 PVC 持久化**emptyDir 挂载重启后数据丢失。
如果 PVC 空间不足或不需要历史功能,可以改用空目录(数据丢失但不影响核心记忆功能):
```yaml
# 测试/轻量级环境用 emptyDir
volumes:
- name: mem0-history
emptyDir: {}
```
## Dashboard 部署
mem0 dashboard 是 Next.js 应用,需要单独部署。
### 环境变量
```yaml
NEXT_PUBLIC_API_URL=http://mem0.tei.svc.cluster.local:8000 # 浏览器调用(通过 Ingress
API_INTERNAL_URL=http://mem0.tei.svc.cluster.local:8000 # 服务端内部调用K8s 内部直连)
NEXT_PUBLIC_INSTANCE_NAME=Mem0
```
**`API_INTERNAL_URL`**K8s 内部服务间通信,直接使用 Service DNS不需要暴露到外部。浏览器无法解析 `mem0.tei.svc.cluster.local`,所以前端用 `NEXT_PUBLIC_API_URL` 通过 Ingress 访问。
两者可以相同,但分离部署时:
- `NEXT_PUBLIC_API_URL` = 对外域名Ingress
- `API_INTERNAL_URL` = 集群内部 `mem0.tei.svc.cluster.local:8000`
### CORS 配置
mem0 server 使用 CORSMiddleware需要通过环境变量配置允许的来源
```yaml
DASHBOARD_URL=https://mem0.your-domain.com # dashboard 的外部访问地址
```
mem0 server 启动时读取 `DASHBOARD_URL`,设置 `allow_origins=[DASHBOARD_URL]`
**常见问题**
- 浏览器访问 dashboard 登录页时,返回 `400 Bad Request`
- OPTIONS 预检请求失败,因为 `allow_origins` 为空
- Swagger 能正常登录(同域请求,无 CORS 问题)
确保 `DASHBOARD_URL` 设为 dashboard 的外部访问地址(与 `NEXT_PUBLIC_API_URL` 的域名部分一致)。
## sentence_transformers 导入错误(永久修复)
mem0 的 `huggingface.py` 在文件顶部执行 `from sentence_transformers import SentenceTransformer`,即使走 `huggingface_base_url` 路径(用 OpenAI 客户端调用 TEI不需要本地模型也会尝试加载造成 `ModuleNotFoundError`
通过 init container + emptyDir 挂载实现持久修复pod recreate 不丢失:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mem0
namespace: tei
spec:
replicas: 1
selector:
matchLabels:
app: mem0
template:
metadata:
labels:
app: mem0
spec:
initContainers:
- name: patch-mem0
image: ccr.ccs.tencentyun.com/tei_agent/mem0:latest
command:
- sh
- -c
- |
cp -r /usr/local/lib/python3.12/site-packages/mem0 /tmp/mem0-patch/
sed -i 's/from sentence_transformers import SentenceTransformer/# from sentence_transformers import SentenceTransformer/' /tmp/mem0-patch/embeddings/huggingface.py
echo "patch done"
volumeMounts:
- name: mem0-code
mountPath: /tmp/mem0-patch
containers:
- name: mem0-ui
image: ccr.ccs.tencentyun.com/tei_agent/mem0-ui:latest
ports:
- containerPort: 3000
name: http
env:
- name: NEXT_PUBLIC_API_URL
value: "https://api.mem0.violin-work.online"
- name: API_INTERNAL_URL
value: "http://mem0.tei.svc.cluster.local:8000"
- name: NEXT_PUBLIC_INSTANCE_NAME
value: Mem0
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 250Mi
- name: mem0
image: ccr.ccs.tencentyun.com/tei_agent/mem0:latest
ports:
- containerPort: 8000
name: http
envFrom:
- configMapRef:
name: mem0-env
- secretRef:
name: mem0-secrets
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: dify-prod-db-secret
key: password
volumeMounts:
- name: mem0-history
mountPath: /app/data
- name: mem0-code
mountPath: /usr/local/lib/python3.12/site-packages/mem0
resources:
limits:
cpu: "2"
memory: 4Gi
requests:
cpu: "500m"
memory: 1Gi
volumes:
- name: mem0-history
persistentVolumeClaim:
claimName: mem0-history
- name: mem0-code
emptyDir: {}
```
**原理**init container 先于主容器启动,把镜像里的 mem0 代码复制到 emptyDirsed 补丁打在 emptyDir 里。主容器挂载 emptyDir 到 site-packages覆盖镜像原有文件实现补丁持久化recreate 不丢)。
## 功能测试
mem0 未为 K8s 做适配,无健康检查端点。手动测试核心 API
```bash
cat > /tmp/test_mem0.py << 'TESTEOF'
import urllib.request, json, uuid
BASE = "http://mem0:8000"
def req(method, path, data=None):
url = BASE + path
body = json.dumps(data).encode() if data else None
headers = {"Content-Type": "application/json"}
try:
r = urllib.request.urlopen(urllib.request.Request(url, data=body, headers=headers, method=method), timeout=10)
return json.loads(r.read()), r.status
except urllib.error.HTTPError as e:
return json.loads(e.read()), e.code
except Exception as e:
return str(e), 0
uid = str(uuid.uuid4())
print("=== 1. 创建用户 ===")
print(req("POST", "/api/v1/users", {"user_id": uid, "email": f"{uid}@test.com"}))
print("=== 2. 添加记忆 ===")
print(req("POST", "/api/v1/memories", {"text": "我叫张三我喜欢Python", "user_id": uid}))
print("=== 3. 搜索记忆 ===")
print(req("GET", f"/api/v1/memories?query=python&user_id={uid}"))
print("=== 4. 获取历史 ===")
print(req("GET", f"/api/v1/history?user_id={uid}"))
TESTEOF
kubectl exec -n tei deploy/mem0 -- python3 /tmp/test_mem0.py
```