Files
Hermes-Skills/okx-auto-position/references/qdrant-memory-provider-integration-2026-08-13.md

5.2 KiB
Raw Permalink Blame History

Qdrant Memory Provider 接入 + 时间衰减 (2026-08-13)

接入状态

# 项 状态
1 config.yaml 启用 memory.provider: qdrant ✅
2 hermes memory status 显示 Provider: qdrant ✅
3 容器 hermes-qdrant 运行中 (7+ 天) ✅
4 向量库 263 points ✅
5 同步 cron e9278f724fc8 每小时运行 ✅
6 新 session 自动 prefetch top-3 → system prompt ✅
7 qdrant_recall 工具暴露 ✅
8 时间衰减 (indexed_at + recency_half_life_days) ✅ 2026-08-13 新增

架构

~/.hermes/hermes-agent/
  plugins/memory/qdrant/__init__.py  ← QdrantProvider (prefetch + recall)
  agent/agent_init.py line 1360       ← load_memory_provider() 自动加载

~/.hermes/config.yaml                 ← memory.provider: qdrant
~/.hermes/qdrant/
  index_memory.py                      ← 写入侧: chunk_text() 加 indexed_at
  sync_memory.sh                       ← cron wrapper: MEMORY.md/USER.md/git history

⚠️ qdrant_client 1.18 API 破坏性变更 (2026-08-13 实测)

问题: qdrant_client 1.18 移除了 client.search() 方法,改用 client.query_points()。

错误信息:

AttributeError: 'QdrantClient' object has no attribute 'search'

修复 (plugins/memory/qdrant/__init__.py _query()):

# 旧 (qdrant_client < 1.18):
results = self._client.search(
    collection_name=self._collection,
    query_vector=query_vector,
    limit=top_k,
    score_threshold=min_score,
    with_payload=True,
)

# 新 (qdrant_client 1.18+):
from qdrant_client.models import ExactCountQuery, ScrollQuery
results = self._client.query_points(
    collection_name=self._collection,
    query=ExactCountQuery(**{"exact": False}),
    query_vector=query_vector,
    limit=top_k,
    score_threshold=min_score,
    with_payload=True,
).points

验证: python3 -c "from qdrant_client import QdrantClient; print(hasattr(QdrantClient, 'search'), hasattr(QdrantClient, 'query_points'))" → False True

Server 版本: qdrant server 1.13, client 1.18, minor 差 5 → 有警告但功能正常:

Qdrant client version 1.18.0 is incompatible with server version 1.13.0.
Major versions should match and minor version difference must not exceed 1.

时间衰减机制 (2026-08-13 新增)

写入侧: ~/.hermes/qdrant/index_memory.py

chunk_text() 每个 chunk 加 indexed_at 字段:

indexed_at = datetime.now(timezone.utc).isoformat()
# 每个 chunk payload: {text, source, file, indexed_at, fp}

查询侧: plugins/memory/qdrant/__init__.py

prefetch() 里 _recency_boost() 对结果重排:

def _recency_boost(self, results, half_life_days=60):
    """按 indexed_at 时间衰减, 新笔记优先"""
    for r in results:
        age_days = (now - indexed_at).days
        decay = 0.5 ** (age_days / half_life_days)  # 60天前半衰
        r['score'] = r['score'] * decay
    # 按 combined_score 降序重排 → 截 top_k

衰减效果 (实测)

笔记 原始 score 年龄 衰减后
2026-08-13 新笔记 (今天, indexed_at 有) 0.65 0 天 ×1.0 0.65
2026-08-04 旧笔记 (无 indexed_at) 0.65 ~1 年 0.19 (×0.3)

结果: 旧笔记自动沉底,新笔记冲到 top。

配置项 (config.yaml)

memory:
  provider: qdrant
  qdrant:
    host: 127.0.0.1
    port: 6333
    collection: hermes_memory
    embed_model: BAAI/bge-small-zh-v1.5
    top_k: 3
    min_score: 0.3
    enabled: true
    recency_half_life_days: 60   # 新增: 半衰期(天)
    overfetch: 4                  # 新增: 多召回倍数(给重排留余地)

BOM + 截断 UTF-8 兼容 (2026-08-13)

问题: MEMORY.md 有 UTF-8 BOM (0xEF 0xBB 0xBF),且末尾截断(最后字节 0xBB 不完整)。

报错:

UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 1758-1759: unexpected end of data

修复 (~/.hermes/qdrant/index_memory.py index_file()):

try:
    text = file_path.read_text(encoding='utf-8-sig', errors='ignore')
except Exception:
    text = file_path.read_text(encoding='utf-8', errors='ignore')

git 提交

仓库 提交 内容
~/.hermes/qdrant/ (新 init) ae090fe index_memory.py (indexed_at + UTF-8 容错) + sync_memory.sh + docker-compose.yml
~/.hermes/hermes-agent/ (fork) e969ec498d plugins/memory/qdrant/__init__.py (时间衰减 + overfetch + query_points 修复)

验证命令

# 1. provider 状态
hermes memory status

# 2. prefetch 召回测试 (新 session)
hermes chat -q "lottery Qi Nq 期号到底用哪个"

# 3. 衰减验证 (Python)
cd ~/.hermes/hermes-agent && PYTHONPATH=. venv/bin/python -c "
from plugins.memory.qdrant import QdrantProvider
p = QdrantProvider({'host':'127.0.0.1','port':6333,'collection':'hermes_memory','top_k':3,'min_score':0.2,'recency_half_life_days':60,'overfetch':6})
p.initialize('test')
print(p.prefetch('六合 cron Nq 期号', session_id='test'))
"

# 4. is_lock_expired 验证 (锁不过期)
cd ~/.hermes/skills/trading/okx-auto-position/scripts
python3 -c "from signal_tracker import is_lock_expired; print(is_lock_expired(None), is_lock_expired({}))"
# 输出: False False