告别 Moltbook:如何搭建私有的 Agent 知识库

告别 Moltbook:如何搭建私有的 Agent 知识库

Moltbook 的数据泄露让很多人意识到了"数据主权"的重要性。

我们希望 Agent 能记住我们的喜好、历史任务、常用指令。我们希望它越用越懂我们,变成真正的"私人助理"。但我们不希望这些记忆存在别人的服务器上,更不希望这些记忆和 150 万人的数据一起被泄露。

好消息是:OpenClaw 完全可以搭配本地知识库使用。你不需要上传任何数据,也能拥有一个"懂你"的贾维斯。

这篇文章会手把手教你搭建一套私有的 Agent 记忆系统。

为什么需要知识库?

先理解一下问题。

LLM(大语言模型)本身是"无状态"的。每次对话,它都是从零开始。昨天你告诉它你喜欢吃川菜,今天它就忘了。上周你让它帮你整理了一份报告,这周问它要,它一脸懵逼。

解决这个问题的方法是给 Agent 加一个"外部记忆"——知识库。

知识库的工作原理:

  1. 存储:把每次对话、重要信息转换成向量(embedding),存到数据库里
  2. 检索:下次对话时,根据当前话题,从数据库里找出相关的历史信息
  3. 注入:把检索到的信息作为上下文,和当前问题一起发给 LLM

这就是所谓的 RAG(Retrieval-Augmented Generation)。

方案选择:本地向量数据库

市面上有很多向量数据库,我们要选择可以完全本地部署的:

方案 优点 缺点 推荐场景
ChromaDB 轻量、Python 原生、易上手 功能相对简单 个人使用、快速原型
Qdrant 功能强大、性能好、有 Web UI 配置稍复杂 中大规模部署
Milvus 企业级、高性能、分布式 重量级、资源消耗大 大规模生产环境
LanceDB 新项目、嵌入式、零配置 生态较新 嵌入式应用
pgvector PostgreSQL 扩展、熟悉的 SQL 需要 PostgreSQL 已有 PG 的环境

对于个人用户,我推荐 ChromaDBQdrant。下面分别介绍。

方案一:ChromaDB(最简单)

ChromaDB 是一个轻量级的向量数据库,特别适合 Python 生态。

安装和运行

# 方式一:直接用 Docker
docker run -p 8000:8000 chromadb/chroma

# 方式二:Python 包(嵌入式使用)
pip install chromadb

基础使用示例

import chromadb
from chromadb.config import Settings

# 创建客户端,数据持久化到本地目录
client = chromadb.Client(Settings(
    chroma_db_impl="duckdb+parquet",
    persist_directory="./chroma_data"
))

# 创建或获取一个 collection
collection = client.get_or_create_collection(
    name="agent_memory",
    metadata={"description": "OpenClaw Agent 的记忆存储"}
)

# 添加记忆
def add_memory(content, metadata=None):
    import uuid
    doc_id = str(uuid.uuid4())
    collection.add(
        documents=[content],
        metadatas=[metadata or {}],
        ids=[doc_id]
    )
    return doc_id

# 搜索相关记忆
def search_memory(query, n_results=5):
    results = collection.query(
        query_texts=[query],
        n_results=n_results
    )
    return results

# 示例:存储用户偏好
add_memory(
    "用户喜欢吃川菜,尤其是麻婆豆腐和水煮鱼。不能吃太辣的。",
    metadata={"type": "preference", "category": "food", "date": "2026-02-01"}
)

add_memory(
    "用户的工作是软件工程师,主要使用 Python 和 TypeScript。",
    metadata={"type": "profile", "category": "work", "date": "2026-02-01"}
)

# 示例:检索
results = search_memory("推荐一家餐厅吃饭")
print(results)
# 会返回关于饮食偏好的记忆

持久化配置

# 确保数据持久化
client = chromadb.Client(Settings(
    chroma_db_impl="duckdb+parquet",
    persist_directory="/path/to/your/data",  # 指定持久化目录
    anonymized_telemetry=False  # 关闭遥测
))

# 定期调用 persist() 确保数据写入磁盘
client.persist()

Docker Compose 部署

# docker-compose.yml
version: '3.8'

services:
  chroma:
    image: chromadb/chroma:latest
    container_name: chroma
    ports:
      - "127.0.0.1:8000:8000"
    volumes:
      - ./chroma_data:/chroma/chroma  # 数据持久化
    environment:
      - ANONYMIZED_TELEMETRY=false
    restart: unless-stopped
  
  openclaw:
    image: openclaw/openclaw:latest
    depends_on:
      - chroma
    environment:
      - MEMORY_BACKEND=chroma
      - CHROMA_HOST=chroma
      - CHROMA_PORT=8000
    # ...其他配置

方案二:Qdrant(更强大)

Qdrant 是一个用 Rust 写的向量数据库,性能出色,功能完善。

Docker 部署

docker run -p 6333:6333 -p 6334:6334 \
    -v ./qdrant_data:/qdrant/storage \
    qdrant/qdrant

基础使用示例

from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
import uuid

# 连接到 Qdrant
client = QdrantClient(host="localhost", port=6333)

# 创建 collection
client.create_collection(
    collection_name="agent_memory",
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
    # 1536 是 OpenAI text-embedding-ada-002 的维度
)

# 生成 embedding(使用 OpenAI 或本地模型)
def get_embedding(text):
    import openai
    response = openai.Embedding.create(
        model="text-embedding-ada-002",
        input=text
    )
    return response['data'][0]['embedding']

# 添加记忆
def add_memory(content, metadata=None):
    point_id = str(uuid.uuid4())
    embedding = get_embedding(content)
    
    client.upsert(
        collection_name="agent_memory",
        points=[
            PointStruct(
                id=point_id,
                vector=embedding,
                payload={
                    "content": content,
                    **(metadata or {})
                }
            )
        ]
    )
    return point_id

# 搜索
def search_memory(query, limit=5):
    query_embedding = get_embedding(query)
    
    results = client.search(
        collection_name="agent_memory",
        query_vector=query_embedding,
        limit=limit
    )
    
    return [
        {
            "content": hit.payload["content"],
            "score": hit.score,
            "metadata": {k: v for k, v in hit.payload.items() if k != "content"}
        }
        for hit in results
    ]

使用本地 Embedding 模型

如果你不想把文本发给 OpenAI 来生成 embedding,可以用本地模型:

from sentence_transformers import SentenceTransformer

# 加载本地模型
model = SentenceTransformer('all-MiniLM-L6-v2')  # 384 维
# 或者中文模型
# model = SentenceTransformer('shibing624/text2vec-base-chinese')

def get_local_embedding(text):
    return model.encode(text).tolist()

# 注意:创建 collection 时要改成对应的维度
client.create_collection(
    collection_name="agent_memory",
    vectors_config=VectorParams(size=384, distance=Distance.COSINE)
)

Docker Compose 完整配置

version: '3.8'

services:
  qdrant:
    image: qdrant/qdrant:latest
    container_name: qdrant
    ports:
      - "127.0.0.1:6333:6333"  # REST API
      - "127.0.0.1:6334:6334"  # gRPC
    volumes:
      - ./qdrant_data:/qdrant/storage
    environment:
      - QDRANT__SERVICE__GRPC_PORT=6334
    restart: unless-stopped
  
  openclaw:
    image: openclaw/openclaw:latest
    depends_on:
      - qdrant
    environment:
      - MEMORY_BACKEND=qdrant
      - QDRANT_HOST=qdrant
      - QDRANT_PORT=6333
    volumes:
      - ./data:/app/data
    ports:
      - "127.0.0.1:3000:3000"
    restart: unless-stopped

与 OpenClaw 集成

假设 OpenClaw 支持自定义记忆后端(或者你可以修改代码),以下是集成思路:

# memory_adapter.py - OpenClaw 的记忆适配器

class LocalMemoryAdapter:
    def __init__(self, backend="chroma"):
        if backend == "chroma":
            import chromadb
            self.client = chromadb.Client(...)
            self.collection = self.client.get_or_create_collection("agent_memory")
        elif backend == "qdrant":
            from qdrant_client import QdrantClient
            self.client = QdrantClient(...)
    
    def store(self, content, metadata=None):
        """存储一条记忆"""
        # ...
    
    def recall(self, query, limit=5):
        """检索相关记忆"""
        # ...
    
    def forget(self, memory_id):
        """删除一条记忆"""
        # ...
    
    def summarize_and_compress(self):
        """定期总结和压缩旧记忆"""
        # 防止记忆库无限增长
        # ...


# 在 OpenClaw 的配置中使用
# config.json
{
  "memory": {
    "enabled": true,
    "backend": "local",
    "adapter": "memory_adapter.LocalMemoryAdapter",
    "options": {
      "backend": "qdrant",
      "host": "localhost",
      "port": 6333
    }
  }
}

记忆管理最佳实践

1. 分类存储

不要把所有东西混在一起。按类型分 collection:

collections = {
    "conversations": "对话历史",
    "user_preferences": "用户偏好",
    "knowledge": "知识库(文档、笔记)",
    "tasks": "任务记录",
    "feedback": "用户反馈"
}

for name, description in collections.items():
    client.get_or_create_collection(
        name=name,
        metadata={"description": description}
    )

2. 设置 TTL(过期时间)

旧的记忆可能不再相关,设置过期机制:

import time

def add_memory_with_ttl(content, ttl_days=30):
    expiry = time.time() + ttl_days * 86400
    add_memory(content, metadata={"expiry": expiry})

def cleanup_expired():
    # 定期清理过期记忆
    current_time = time.time()
    # 查询并删除过期的记录
    # ...

3. 定期备份

#!/bin/bash
# backup_memory.sh

BACKUP_DIR="/backup/agent_memory"
DATE=$(date +%Y%m%d_%H%M%S)

# 停止服务(可选,确保数据一致性)
docker-compose stop qdrant

# 备份数据目录
tar -czvf "${BACKUP_DIR}/qdrant_${DATE}.tar.gz" ./qdrant_data

# 重启服务
docker-compose start qdrant

# 保留最近 7 天的备份
find ${BACKUP_DIR} -name "qdrant_*.tar.gz" -mtime +7 -delete

echo "Backup completed: qdrant_${DATE}.tar.gz"

加入 crontab:

# 每天凌晨 3 点备份
0 3 * * * /path/to/backup_memory.sh >> /var/log/memory_backup.log 2>&1

4. 加密敏感记忆

对于特别敏感的信息,存储前先加密:

from cryptography.fernet import Fernet

# 生成密钥并安全保存
key = Fernet.generate_key()
cipher = Fernet(key)

def add_encrypted_memory(content, is_sensitive=False):
    if is_sensitive:
        encrypted = cipher.encrypt(content.encode()).decode()
        add_memory(encrypted, metadata={"encrypted": True})
    else:
        add_memory(content, metadata={"encrypted": False})

def search_and_decrypt(query, limit=5):
    results = search_memory(query, limit)
    for result in results:
        if result.get("metadata", {}).get("encrypted"):
            result["content"] = cipher.decrypt(result["content"].encode()).decode()
    return results

完整的部署示例

# docker-compose.yml
version: '3.8'

services:
  # 向量数据库
  qdrant:
    image: qdrant/qdrant:latest
    container_name: agent-memory
    ports:
      - "127.0.0.1:6333:6333"
    volumes:
      - qdrant_data:/qdrant/storage
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:6333/"]
      interval: 30s
      timeout: 10s
      retries: 3
  
  # Embedding 服务(可选,如果用本地模型)
  embedding:
    image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.0
    container_name: embedding-service
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - embedding_cache:/data
    environment:
      - MODEL_ID=BAAI/bge-small-en-v1.5
    restart: unless-stopped
  
  # OpenClaw
  openclaw:
    image: openclaw/openclaw:latest
    container_name: openclaw
    depends_on:
      - qdrant
      - embedding
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - openclaw_data:/app/data
      - ./config:/app/config:ro
    environment:
      - MEMORY_ENABLED=true
      - QDRANT_URL=http://qdrant:6333
      - EMBEDDING_URL=http://embedding:80
    restart: unless-stopped

volumes:
  qdrant_data:
  embedding_cache:
  openclaw_data:

总结

搭建私有知识库的要点:

  1. 选择合适的向量数据库:个人用户推荐 ChromaDB 或 Qdrant
  2. 数据永不离开本地:配置持久化目录,绑定到 localhost
  3. 考虑使用本地 Embedding:避免把文本发给 OpenAI
  4. 做好备份:定期备份数据目录
  5. 必要时加密:敏感信息存储前先加密

这样做的体验差异几乎为零,但安全性天差地别。你的 Agent 依然聪明,记得你上次让它写的代码风格,记得你讨厌吃香菜。但这些信息物理上从未离开过你的控制范围。

对于那些敏感的笔记、代码片段、私人对话,本地知识库是唯一的归宿。既然选择了 Local Agent 这条路,那就把"Local"贯彻到底吧。Moltbook 的悲剧,不应该在你身上重演。

← 返回博客列表