告别 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 的悲剧,不应该在你身上重演。

← 返回博客列表