Files
memory-system/docs/index.html
T
zqf b1f93588f1 初始化:记忆系统源代码上传(已脱敏)
- 排除 .env / *.bak / 内部运维文档(README_INTERNAL.html)
- config.py 默认密码已替换为占位符 CHANGE_ME_*
- init_db.sql 移除生产数据库用户 GRANT 段
- README.html 数据库用户名已脱敏
- 保留:源码 + 公网 API 文档 + 建表 SQL(无授权语句)
2026-08-03 04:53:48 +08:00

1810 lines
83 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<link rel="icon" type="image/png" href="doc.png">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多智能体记忆系统 | Memory System</title>
<style>
:root {
--bg: #0d1117; --bg2: #161b22; --bg3: #1c2128;
--border: #30363d; --text: #c9d1d9; --text-dim: #8b949e;
--heading: #f0f6fc; --accent: #58a6ff;
--green: #3fb950; --orange: #d29922; --red: #f85149;
--get: #61affe; --post: #49cc90; --put: #fca130; --del: #f93e3e;
--sidebar-w: 300px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
background: var(--bg); color: var(--text); line-height: 1.8; font-size: 16px; }
.sidebar { position: fixed; top: 0; left: 0; width: var(--sidebar-w); height: 100vh;
background: var(--bg2); border-right: 1px solid var(--border);
overflow-y: auto; padding: 24px 0; z-index: 100; scrollbar-width: none; -ms-overflow-style: none; }
.sidebar::-webkit-scrollbar { display: none; }
.sidebar-header { padding: 0 20px 20px; border-bottom: 1px solid var(--border); }
.sidebar-header h2 { font-size: 22px; color: var(--heading); margin-bottom: 4px; }
.sidebar-header p { font-size: 15px; color: var(--text-dim); }
.sidebar-nav { padding: 16px 0; }
.sidebar-nav a { display: block; padding: 8px 20px; color: var(--text-dim); text-decoration: none;
font-size: 18px; border-left: 3px solid transparent; transition: all 0.2s; }
.sidebar-nav a:hover { color: var(--accent); background: rgba(88,166,255,0.08);
border-left-color: var(--accent); }
.sidebar-nav a.active { color: var(--accent); background: rgba(88,166,255,0.12);
border-left-color: var(--accent); font-weight: 600; }
.sidebar-nav a.section { font-weight: 700; color: var(--text); font-size: 20px;
margin-top: 12px; padding: 8px 20px; }
.sidebar-nav a.sub { padding-left: 36px; font-size: 18px; }
.content { margin-left: var(--sidebar-w); max-width: 1200px; padding: 40px 64px 80px; }
h1 { font-size: 36px; color: var(--heading); margin: 0 0 8px; }
.subtitle { font-size: 17px; color: var(--text-dim); margin-bottom: 32px; }
h2 { font-size: 28px; color: var(--heading); margin: 48px 0 20px; padding-bottom: 10px;
border-bottom: 2px solid var(--border); }
h3 { font-size: 21px; color: var(--heading); margin: 28px 0 12px; }
h4 { font-size: 17px; color: var(--heading); margin: 20px 0 8px; }
p { margin: 10px 0; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
strong { color: var(--heading); }
ul, ol { margin: 10px 0 10px 24px; }
li { margin: 4px 0; }
code { background: rgba(110,118,129,0.4); padding: 2px 6px; border-radius: 4px;
font-size: 13px; font-family: "SF Mono",Monaco,Consolas,"Liberation Mono",monospace; }
pre { background: var(--bg2); border: 1px solid var(--border); border-radius: 8px;
padding: 16px; overflow-x: auto; font-size: 13px;
font-family: "SF Mono",Monaco,Consolas,"Liberation Mono",monospace;
line-height: 1.5; margin: 12px 0; }
pre code { background: none; padding: 0; }
table { width: 100%; border-collapse: collapse; margin: 16px 0; }
th, td { border: 1px solid var(--border); padding: 10px 14px; text-align: left; font-size: 14px; }
th { background: var(--bg2); font-weight: 600; color: var(--heading); }
tr:hover { background: rgba(88,166,255,0.04); }
.card { background: var(--bg2); border: 1px solid var(--border); border-radius: 8px;
padding: 20px; margin: 16px 0; }
.tip { background: rgba(63,185,80,0.08); border: 1px solid rgba(63,185,80,0.3);
border-radius: 8px; padding: 16px 20px; margin: 16px 0; }
.warn { background: rgba(210,153,34,0.08); border: 1px solid rgba(210,153,34,0.3);
border-radius: 8px; padding: 16px 20px; margin: 16px 0; }
.endpoint { margin: 16px 0; padding: 14px 16px; background: var(--bg2);
border: 1px solid var(--border); border-radius: 8px;
border-left: 5px solid var(--accent); }
.endpoint.get { border-left-color: var(--get); }
.endpoint.post { border-left-color: var(--post); }
.endpoint.put { border-left-color: var(--put); }
.endpoint.delete { border-left-color: var(--del); }
.method { display: inline-block; padding: 2px 10px; border-radius: 4px;
font-weight: 700; font-size: 11px; margin-right: 10px; letter-spacing: 0.5px; }
.method.get { background: var(--get); color: #000; }
.method.post { background: var(--post); color: #000; }
.method.put { background: var(--put); color: #000; }
.method.delete { background: var(--del); color: #fff; }
.path { font-family: "SF Mono",Monaco,Consolas,"Liberation Mono",monospace;
font-size: 14px; font-weight: 600; color: var(--heading); }
.desc { margin: 8px 0 4px; color: var(--text-dim); font-size: 14px; }
.tag { display: inline-block; padding: 1px 8px; border-radius: 4px;
font-size: 11px; font-weight: 600; margin-left: 8px; }
.tag.auth { background: var(--orange); color: #000; }
.tag.public { background: var(--green); color: #000; }
.hero-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px; margin: 20px 0; }
.hero-card { background: var(--bg2); border: 1px solid var(--border); border-radius: 8px;
padding: 20px; }
.hero-card h4 { color: var(--accent); margin: 0 0 8px; font-size: 16px; }
.hero-card p { margin: 0; font-size: 14px; color: var(--text-dim); }
.mermaid-box { background: var(--bg2); border: 1px solid var(--border);
border-radius: 8px; padding: 24px; margin: 20px 0;
overflow-x: auto; text-align: center; }
.mermaid-box svg { height: auto; }
@media (max-width: 1100px) {
.sidebar { display: none; }
.content { margin-left: 0; padding: 20px 16px; }
}
hr { border: none; border-top: 1px solid var(--border); margin: 48px 0; }
.footer { text-align: center; color: var(--text-dim); font-size: 13px; padding: 32px 0 16px; }
</style>
</head>
<body>
<!-- Sidebar Navigation -->
<div class="sidebar">
<div class="sidebar-header">
<h2>🧠 多智能体记忆系统</h2>
<p>Memory System v3.0.2</p>
</div>
<nav class="sidebar-nav">
<a href="#overview" class="section">1. 项目概述</a>
<a href="#background" class="sub">背景与定位</a>
<a href="#capabilities" class="sub">核心能力</a>
<a href="#memory-model" class="sub">记忆层次说明</a>
<a href="#architecture" class="sub">架构流程图</a>
<a href="#use-cases" class="sub">适用场景</a>
<a href="#quickstart" class="section">2. 快速开始</a>
<a href="#prerequisites" class="sub">前置条件</a>
<a href="#get-key" class="sub">获取 API Key</a>
<a href="#first-request" class="sub">第一个请求</a>
<a href="#python-sdk" class="sub">Python SDK</a>
<a href="#auth" class="section">3. 认证机制</a>
<a href="#auth-model" class="sub">API Key 与签名</a>
<a href="#sign-algo" class="sub">签名算法详解</a>
<a href="#error-codes" class="sub">错误码说明</a>
<a href="#sign-examples" class="sub">跨语言签名示例</a>
<a href="#endpoints" class="section">4. API 端点</a>
<a href="#ep-system" class="sub">系统</a>
<a href="#ep-teams" class="sub">团队管理</a>
<a href="#ep-agents" class="sub">Agent 管理</a>
<a href="#ep-personal" class="sub">个人长期记忆</a>
<a href="#ep-team-mem" class="sub">团队共享记忆</a>
<a href="#ep-working" class="sub">工作记忆</a>
<a href="#ep-lifecycle" class="sub">生命周期管理</a>
<a href="#examples" class="section">5. 对接示例</a>
<a href="#ex-openclaw" class="sub">OpenClaw</a>
<a href="#ex-hermes" class="sub">Hermes</a>
<a href="#ex-claude" class="sub">Claude Code</a>
<a href="#ex-codex" class="sub">Codex</a>
<a href="#ex-isolation" class="sub">跨团队隔离验证</a>
<a href="#ex-lifecycle" class="sub">生命周期完整示例</a>
<a href="#data-mgmt" class="section">6. 数据管理</a>
<a href="#persistence" class="sub">数据持久化</a>
<a href="#cleanup-strategy" class="sub">清理与压缩策略</a>
<a href="#last-accessed" class="sub">last_accessed 维护</a>
<a href="#faq" class="section">7. 常见问题</a>
</nav>
</div>
<!-- Main Content -->
<div class="content">
<h1>多智能体记忆系统</h1>
<p class="subtitle">为 AI Agent 提供跨会话持久化记忆、团队知识沉淀与语义检索能力 | Memory System API v3.0.2</p>
<!-- ==================== Section 1: Overview ==================== -->
<h2 id="overview">1. 项目概述</h2>
<h3 id="background">1.1 背景与定位</h3>
<p>在多智能体协作场景中,AI Agent 面临以下核心痛点:</p>
<ul>
<li><strong>无状态困境</strong>:Agent 每次对话都是从零开始,之前的交互经验无法积累</li>
<li><strong>会话间记忆丢失</strong>:对话结束后,上下文和决策依据全部消失</li>
<li><strong>团队知识孤岛</strong>:多个 Agent 各自独立工作,无法共享已学到的知识和经验</li>
<li><strong>检索效率低下</strong>:当记忆积累到一定规模,关键词匹配无法满足语义级别的回忆需求</li>
<li><strong>记忆无限膨胀</strong>:缺少自动化的清理和压缩机制,存储成本持续增长</li>
<li><strong>知识结构化不足</strong>:原始记忆是自由文本,缺少事实提取、场景聚合和画像生成等智能化处理能力</li>
</ul>
<p><strong>多智能体记忆系统</strong>正是为解决这些问题而设计的。它为 AI Agent 提供了一套<strong>双层次(工作记忆 + 长期记忆)、LLM 驱动的智能生命周期管理、多团队隔离、语义检索</strong>的记忆基础设施,让 Agent 能够像人类一样"记住"、"回忆"、"总结"和"遗忘"。</p>
<h3 id="capabilities">1.2 核心能力</h3>
<div class="hero-grid">
<div class="hero-card">
<h4>💾 持久化记忆</h4>
<p>跨会话、跨部署的记忆存储。Agent 重启后依然能回忆之前学到的知识和经验。</p>
</div>
<div class="hero-card">
<h4>🔍 语义检索</h4>
<p>基于 BGE 向量模型的语义搜索,无需精确关键词匹配,用自然语言即可找到相关记忆。</p>
</div>
<div class="hero-card">
<h4>👥 多团队隔离</h4>
<p>团队间数据完全隔离,互不可见。每个团队拥有独立的记忆空间和 API Key。</p>
</div>
<div class="hero-card">
<h4>🔄 生命周期管理</h4>
<p>工作记忆自动过期、长期记忆智能压缩与清理。让记忆保持精简有价值。</p>
</div>
<div class="hero-card">
<h4>📝 知识共享</h4>
<p>团队级共享记忆,一个 Agent 的发现可以被整个团队复用,避免重复劳动。</p>
</div>
<div class="hero-card">
<h4>⚡ 高性能架构</h4>
<p>MySQL 持久化 + Redis 缓存 + BGE 向量检索,写入 P99 &lt; 20ms,读取 P99 &lt; 30ms。</p>
</div>
<div class="hero-card">
<h4>🧠 智能生命周期</h4>
<p>LLM 驱动的原子事实提取、场景聚合和用户画像生成,配合 Pipeline 自动化引擎实现记忆全生命周期管理。</p>
</div>
<div class="hero-card">
<h4>🤖 Pipeline 自动化</h4>
<p>可配置的自动压缩、清理规则和暖机机制。写入工作记忆时自动检查阈值,无需手动触发。</p>
</div>
</div>
<h3 id="memory-model">1.3 记忆层次说明</h3>
<p>系统采用<strong>双层次记忆模型</strong>,模拟人类的短期记忆与长期记忆,同时支持基于 LLM 的<strong>场景聚合</strong><strong>用户画像</strong>层:</p>
<table>
<tr><th>维度</th><th>工作记忆 (Working Memory)</th><th>长期记忆 (Long-term Memory)</th></tr>
<tr><td><strong>存储介质</strong></td><td>Redis</td><td>MySQL + BGE 向量</td></tr>
<tr><td><strong>生命周期</strong></td><td>72 小时 TTL(可配置),或手动压缩后删除</td><td>永久保存,直到被清理策略移除</td></tr>
<tr><td><strong>检索方式</strong></td><td>按时间顺序获取最近条目</td><td>语义向量检索(余弦相似度)</td></tr>
<tr><td><strong>数据结构</strong></td><td>有序列表(Redis List</td><td>结构化记录 + 512 维向量</td></tr>
<tr><td><strong>适用场景</strong></td><td>当前会话上下文、临时笔记、待处理任务</td><td>经验总结、用户偏好、代码规范、决策记录</td></tr>
<tr><td><strong>容量特点</strong></td><td>轻量、快速、自动过期</td><td>大容量、可检索、支持重要性评分</td></tr>
<tr><td><strong>持久化</strong></td><td>纯内存,服务重启丢失</td><td>磁盘持久化,不怕重启</td></tr>
</table>
<h4>扩展数据类型</h4>
<table>
<tr><th>数据类型</th><th>说明</th><th>生成方式</th><th>持久化</th></tr>
<tr><td><strong>场景块 (Scenarios)</strong></td><td>按主题聚合的记忆块,含摘要和关联记忆 ID 列表</td><td><code>/lifecycle/aggregate-scenes</code>LLM 聚合)</td><td>MySQL 持久化</td></tr>
<tr><td><strong>用户画像 (Persona)</strong></td><td>结构化画像:偏好、习惯、擅长领域、沟通风格</td><td><code>/lifecycle/generate-persona</code>LLM 提取)</td><td>MySQL 持久化</td></tr>
<tr><td><strong>原子事实 (Facts)</strong></td><td>从工作记忆提取的独立结构化事实,存为长期记忆</td><td><code>/lifecycle/extract-facts</code>LLM 提取)</td><td>MySQL 持久化(metadata 标记来源)</td></tr>
<tr><td><strong>Pipeline 配置</strong></td><td>自动压缩、清理和暖机规则</td><td><code>/lifecycle/auto-config</code></td><td>MySQL 持久化</td></tr>
</table>
<div class="card">
<h4>记忆流转路径</h4>
<p>工作记忆 →(压缩/事实提取)→ 长期记忆 →(场景聚合/画像生成)→ 场景块/用户画像</p>
<p>长期记忆 →(清理)→ 归档/删除</p>
<ol>
<li><strong>写入阶段</strong>Agent 将当前交互的关键信息写入<strong>工作记忆</strong>Redis),速度快、不阻塞</li>
<li><strong>沉淀阶段</strong>:通过 <code>/lifecycle/compress</code> 将工作记忆压缩为摘要,或通过 <code>/lifecycle/extract-facts</code> 提取结构化原子事实,写入<strong>长期记忆</strong>MySQL</li>
<li><strong>聚合阶段</strong>:通过 <code>/lifecycle/aggregate-scenes</code> 将相关记忆聚合为场景块,通过 <code>/lifecycle/generate-persona</code> 生成用户画像</li>
<li><strong>检索阶段</strong>:Agent 需要回忆时,通过语义搜索从长期记忆中找到相关条目</li>
<li><strong>清理阶段</strong>:通过 <code>/lifecycle/cleanup</code><code>/lifecycle/auto-config</code> 配置的自动规则清理过期记忆</li>
</ol>
</div>
<h3 id="architecture">1.4 架构流程图</h3>
<div class="mermaid-box">
<pre class="mermaid">
graph LR
subgraph Client["客户端"]
A[AI Agent<br/>OpenClaw / Hermes / Claude Code / Codex]
end
subgraph Gateway["接入层"]
B[Nginx<br/>HTTPS + SSL]
C[Auth Middleware<br/>HMAC-SHA256 签名验证]
end
subgraph Core["核心服务 (Flask)"]
D[API Routes]
E[Memory System<br/>业务逻辑]
F[Lifecycle Manager]
F1[Fact Extractor<br/>原子事实提取]
F2[Scenario Aggregator<br/>场景聚合]
F3[Persona Generator<br/>画像生成]
F4[Pipeline Engine<br/>自动化引擎]
end
subgraph Storage["存储层"]
G[(MySQL<br/>长期记忆 + 向量)]
H[(Redis<br/>工作记忆缓存)]
I[BGE Embedding<br/>512 维向量化]
end
A -->|"HTTPS 请求<br/>+ HMAC 签名"| B
B --> C
C -->|"认证通过"| D
C -.->|"认证失败 → 401"| A
D --> E
E -->|"写入记忆"| G
E -->|"缓存工作记忆"| H
E -->|"文本→向量"| I
E -->|"语义搜索"| G
F -->|"压缩: 工作→长期"| E
F -->|"清理: 过期记忆"| G
F1 -->|"提取事实→长期记忆"| E
F2 -->|"聚合场景"| G
F3 -->|"生成画像"| G
F4 -->|"自动触发"| F
F4 -->|"自动触发"| F1
style Client fill:#1a1f2e,stroke:#58a6ff,color:#c9d1d9
style Gateway fill:#1a1f2e,stroke:#d29922,color:#c9d1d9
style Core fill:#1a1f2e,stroke:#3fb950,color:#c9d1d9
style Storage fill:#1a1f2e,stroke:#f85149,color:#c9d1d9
</pre>
</div>
<div class="card">
<h4>数据流说明</h4>
<table>
<tr><th>流程</th><th>路径</th><th>说明</th></tr>
<tr><td>写入记忆</td><td>Agent → Nginx → Auth → Routes → Memory System → MySQL + BGE向量</td><td>文本经 BGE 模型向量化后,连同元数据一起持久化到 MySQL</td></tr>
<tr><td>语义检索</td><td>Agent → Nginx → Auth → Routes → Memory System → BGE向量 → MySQL向量比对</td><td>查询文本向量化后,与存储向量计算余弦相似度,返回 Top-K 结果</td></tr>
<tr><td>工作记忆</td><td>Agent → Nginx → Auth → Routes → Memory System → Redis List</td><td>写入 Redis List(TTL 自动过期),读取时按时间倒序返回</td></tr>
<tr><td>压缩</td><td>Lifecycle → 读取工作记忆 → 调用 LLM 摘要 → 写入长期记忆 → 删除工作记忆</td><td>将多条工作记忆压缩为一条长期记忆摘要</td></tr>
<tr><td>事实提取</td><td>Lifecycle → 读取工作记忆 → LLM 提取结构化事实 → 每条事实存为独立长期记忆</td><td>从工作记忆中提取独立的、可检索的原子事实</td></tr>
<tr><td>场景聚合</td><td>Lifecycle → 读取个人记忆 → LLM 按主题聚合 → 存储场景块</td><td>将相关记忆聚合为场景块并生成摘要</td></tr>
<tr><td>画像生成</td><td>Lifecycle → 读取记忆+场景 → LLM 分析 → 输出结构化画像</td><td>从历史记忆中提取用户偏好、习惯、擅长领域</td></tr>
<tr><td>Pipeline</td><td>Lifecycle → 每次写入工作记忆 → 检查阈值 → 自动触发压缩/清理</td><td>可配置的自动压缩、清理和暖机规则</td></tr>
<tr><td>清理</td><td>Lifecycle → 查询过期记忆 → 删除低重要性条目</td><td>基于 last_accessed + importance 双维度清理</td></tr>
</table>
</div>
<h3 id="use-cases">1.5 适用场景</h3>
<div class="hero-grid">
<div class="hero-card">
<h4>🤖 多智能体协作开发</h4>
<p>多个 AI Agent 协同编写代码,共享编码规范、设计决策和技术栈知识,避免重复踩坑。</p>
</div>
<div class="hero-card">
<h4>💬 客服系统</h4>
<p>客服 Agent 记住用户历史问题和偏好,提供个性化服务。团队共享常见问题解决方案。</p>
</div>
<div class="hero-card">
<h4>🔬 研究团队</h4>
<p>研究 Agent 积累文献笔记、实验结论和方法论,团队成员可互相检索和引用。</p>
</div>
<div class="hero-card">
<h4>📊 个人助理</h4>
<p>个人 Agent 记住你的偏好、习惯和待办事项,跨会话提供连续的个性化体验。</p>
</div>
<div class="hero-card">
<h4>📈 量化策略</h4>
<p>策略 Agent 积累回测经验、参数调优记录和市场观察,形成可检索的策略知识库。</p>
</div>
<div class="hero-card">
<h4>🏗️ 项目管理</h4>
<p>PM Agent 记录会议决策、进度更新和风险点,团队成员随时回顾项目上下文。</p>
</div>
</div>
<!-- ==================== Section 2: Quick Start ==================== -->
<h2 id="quickstart">2. 快速开始</h2>
<h3 id="prerequisites">2.1 前置条件</h3>
<table>
<tr><th>条件</th><th>说明</th></tr>
<tr><td>HTTP 客户端</td><td>curl / Python requests / 任何支持 HTTP 的语言</td></tr>
<tr><td>API Key + Secret</td><td>由管理员签发,每个团队一对</td></tr>
<tr><td>时钟同步</td><td>客户端时钟偏差需在 ±5 分钟以内(NTP 同步即可)</td></tr>
</table>
<h3 id="get-key">2.2 获取 API Key</h3>
<div class="card">
<p>API Key 由管理员统一分配。请联系系统管理员获取:</p>
<ul>
<li><code>X-API-Key</code>API 访问密钥,用于标识团队身份</li>
<li><code>Secret</code>:签名密钥,用于计算 HMAC-SHA256 签名。<strong>仅在创建时展示一次,请妥善保管</strong></li>
</ul>
<div class="tip">每个 API Key 绑定一个团队(team_id),同团队下多个 Agent 共用同一个 Key。</div>
</div>
<h3 id="first-request">2.3 第一个请求</h3>
<p>获取 API Key 后,先调用 <code>/health</code> 验证连通性(无需认证):</p>
<pre><code>curl -s https://memory.lsz.name/health
# {"ok": true, "data": {"status": "ok"}}</code></pre>
<p>然后用认证接口验证 Key 是否正常:</p>
<pre><code>import hashlib, hmac, time, json, requests
API_KEY = "your_api_key_here"
SECRET = "your_secret_here"
BASE = "https://memory.lsz.name"
ts = int(time.time())
sig = hmac.new(SECRET.encode(), str(ts).encode(), hashlib.sha256).hexdigest()
r = requests.get(f"{BASE}/stats", headers={
"X-API-Key": API_KEY,
"X-Timestamp": str(ts),
"X-Signature": sig
})
print(r.json())
# {"ok": true, "data": {"personal_memories": 0, "team_memories": 0, "agents": 0, ...}}</code></pre>
<h3 id="python-sdk">2.4 Python SDKMemoryClient</h3>
<p>推荐使用封装好的客户端类,所有签名细节自动处理:</p>
<pre><code>import hashlib, hmac, time, json, requests
class MemoryClient:
"""Memory System API 客户端 — 自动处理 HMAC 签名"""
def __init__(self, api_key, secret, base_url="https://memory.lsz.name"):
self.api_key = api_key
self.secret = secret
self.base_url = base_url
def _sign(self, method, ts, body=None):
if method == "GET" or body is None:
msg = str(ts).encode() # GET: body = b""
else:
msg = str(ts).encode() + json.dumps(body, ensure_ascii=True).encode()
return hmac.new(self.secret.encode(), msg, hashlib.sha256).hexdigest()
def _headers(self, method, ts, body=None):
return {
"X-API-Key": self.api_key,
"X-Timestamp": str(ts),
"X-Signature": self._sign(method, ts, body),
}
def get(self, path, params=None):
ts = int(time.time())
return requests.get(f"{self.base_url}{path}",
headers=self._headers("GET", ts), params=params)
def post(self, path, body):
ts = int(time.time())
return requests.post(f"{self.base_url}{path}",
json=body, headers=self._headers("POST", ts, body))
def put(self, path, body):
ts = int(time.time())
return requests.put(f"{self.base_url}{path}",
json=body, headers=self._headers("PUT", ts, body))
def delete(self, path):
ts = int(time.time())
return requests.delete(f"{self.base_url}{path}",
headers=self._headers("DELETE", ts))
# ---- 便捷方法 ----
def write_memory(self, agent_id, content, importance=0.5):
return self.post("/memories/personal",
{"agent_id": agent_id, "content": content, "importance": importance})
def search(self, agent_id, query, limit=10, min_score=0.3, max_chars_per_memory=0, max_total_chars=0):
body = {"agent_id": agent_id, "query": query, "limit": limit, "min_score": min_score}
if max_chars_per_memory:
body["max_chars_per_memory"] = max_chars_per_memory
if max_total_chars:
body["max_total_chars"] = max_total_chars
return self.post("/memories/personal/search", body)
def get_recent(self, agent_id, limit=10):
return self.get(f"/memories/personal/recent/{agent_id}", params={"limit": limit})
def get_stats(self):
return self.get("/stats")
# ---- 生命周期方法 ----
def extract_facts(self, agent_id, max_memories=20, delete_after=False):
从工作记忆提取原子事实
return self.post("/lifecycle/extract-facts",
{"agent_id": agent_id, "max_memories": max_memories, "delete_after": delete_after})
def aggregate_scenarios(self, agent_id, max_memories=50):
将记忆按主题聚合为场景块
return self.post("/lifecycle/aggregate-scenes",
{"agent_id": agent_id, "max_memories": max_memories})
def get_scenarios(self, agent_id):
获取已存储的场景块
return self.get(f"/lifecycle/scenarios/{agent_id}")
def generate_persona(self, agent_id, max_items=30):
从记忆和场景中生成用户画像
return self.post("/lifecycle/generate-persona",
{"agent_id": agent_id, "max_items": max_items})
def get_persona(self, agent_id):
获取最新用户画像
return self.get(f"/lifecycle/persona/{agent_id}")
def set_pipeline_config(self, **kwargs):
设置 Pipeline 自动化配置
return self.post("/lifecycle/auto-config", kwargs)
def get_pipeline_config(self):
获取 Pipeline 自动化配置
return self.get("/lifecycle/auto-config")
def compress(self, agent_id, target_count=5):
压缩工作记忆为长期记忆
return self.post("/lifecycle/compress",
{"agent_id": agent_id, "target_count": target_count})
# 使用示例
client = MemoryClient("your_api_key_here", "your_secret_here")
r = client.write_memory("assistant", "用户偏好简洁回复", importance=0.8)
print(r.json())</code></pre>
<!-- ==================== Section 3: Auth ==================== -->
<h2 id="auth">3. 认证机制</h2>
<h3 id="auth-model">3.1 API Key 与签名</h3>
<p><code>/health</code> 外,所有接口均需认证。认证方式为 <strong>API Key + HMAC-SHA256 签名</strong></p>
<h4>认证模型</h4>
<ul>
<li>每个团队分配唯一的 API Key + SecretKey 绑定到对应 <code>team_id</code></li>
<li>同团队下多个 Agent 共用同一个 Key</li>
<li>跨团队数据隔离由系统强制保证</li>
</ul>
<h4>请求头</h4>
<table>
<tr><th>Header</th><th>说明</th></tr>
<tr><td><code>X-API-Key</code></td><td>API 访问密钥</td></tr>
<tr><td><code>X-Timestamp</code></td><td>UTC Unix 时间戳(秒),误差范围 ±5 分钟</td></tr>
<tr><td><code>X-Signature</code></td><td>HMAC-SHA256 签名(十六进制)</td></tr>
</table>
<h3 id="sign-algo">3.2 签名算法详解</h3>
<h4>签名公式</h4>
<pre><code>GET 请求: HMAC-SHA256(secret, str(timestamp)) # body 为空字节 b""
POST 请求: HMAC-SHA256(secret, str(timestamp) + raw_body) # body 为原始 JSON 字节</code></pre>
<h4>签名要点</h4>
<ul>
<li><strong>GET 请求</strong>使用空 body <code>b""</code> 进行签名(不是 <code>b"{}"</code></li>
<li><strong>POST / PUT 请求</strong>使用实际发送的原始 JSON 字节。Python <code>requests.post(url, json=body)</code> 默认 <code>ensure_ascii=True</code>,中文字符会被转义(如 <code>\u7b56\u7565</code>),签名必须使用相同的序列化方式</li>
<li><strong>时间戳过期后不可直接重试</strong>:必须用新的当前时间戳重新计算签名</li>
<li><strong>时钟同步</strong>:客户端与服务器时钟偏差需在 ±5 分钟以内</li>
</ul>
<h3 id="error-codes">3.3 错误码说明</h3>
<p>认证失败时返回 <code>{"ok": false, "error": "...", "code": "..."}</code> 格式:</p>
<h4>认证错误码</h4>
<table>
<tr><th>HTTP</th><th>code</th><th>说明</th></tr>
<tr><td>401</td><td><code>AUTH_MISSING_HEADERS</code></td><td>缺少 X-API-Key / X-Timestamp / X-Signature</td></tr>
<tr><td>401</td><td><code>AUTH_INVALID_TIMESTAMP</code></td><td>时间戳格式错误(非数字)</td></tr>
<tr><td>401</td><td><code>AUTH_TIMESTAMP_EXPIRED</code></td><td>时间戳过期(超出 ±5 分钟窗口)</td></tr>
<tr><td>401</td><td><code>AUTH_INVALID_KEY</code></td><td>API Key 不存在</td></tr>
<tr><td>401</td><td><code>AUTH_KEY_DISABLED</code></td><td>API Key 已被禁用</td></tr>
<tr><td>401</td><td><code>AUTH_INVALID_SIGNATURE</code></td><td>签名不匹配</td></tr>
</table>
<h4>通用错误码</h4>
<table>
<tr><th>HTTP</th><th>code</th><th>说明</th></tr>
<tr><td>400</td><td><code>VALIDATION_ERROR</code></td><td>缺少必填参数或参数格式错误</td></tr>
<tr><td>403</td><td><code>FORBIDDEN_CROSS_TEAM</code></td><td>无权访问其他团队的数据</td></tr>
<tr><td>404</td><td><code>TEAM_NOT_FOUND</code></td><td>团队不存在</td></tr>
<tr><td>404</td><td><code>AGENT_NOT_FOUND</code></td><td>Agent 不存在</td></tr>
<tr><td>404</td><td><code>NOT_FOUND</code></td><td>资源不存在</td></tr>
</table>
<h3 id="sign-examples">3.4 跨语言签名示例</h3>
<h4>Python</h4>
<pre><code>import hashlib, hmac, time
def sign(secret, timestamp, body=None):
"""body=None 表示 GET 请求,使用空字节"""
if body is None:
msg = str(timestamp).encode()
else:
msg = str(timestamp).encode() + body # body 已经是 bytes
return hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
ts = int(time.time())
sig = sign("your_secret_here", ts) # GET 请求
</code></pre>
<h4>Node.js</h4>
<pre><code>const crypto = require('crypto');
function sign(secret, timestamp, body = null) {
const msg = body
? Buffer.concat([Buffer.from(String(timestamp)), body])
: Buffer.from(String(timestamp));
return crypto.createHmac('sha256', secret).update(msg).digest('hex');
}
const ts = Math.floor(Date.now() / 1000);
const sig = sign('your_secret_here', ts); // GET 请求
</code></pre>
<h4>Go</h4>
<pre><code>import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
)
func sign(secret string, timestamp int64, body []byte) string {
msg := strconv.FormatInt(timestamp, 10)
if body != nil {
msg += string(body)
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(msg))
return hex.EncodeToString(mac.Sum(nil))
}
ts := time.Now().Unix()
sig := sign("your_secret_here", ts, nil) // GET 请求
</code></pre>
<!-- ==================== Section 4: API Endpoints ==================== -->
<h2 id="endpoints">4. API 端点</h2>
<h3 id="ep-system">4.1 系统</h3>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/health</span><span class="tag public">公开</span>
<div class="desc">健康检查。不需要认证。</div>
<pre><code>curl -s https://memory.lsz.name/health
# {"ok": true, "data": {"status": "ok"}}</code></pre>
</div>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/stats</span><span class="tag auth">需认证</span>
<div class="desc">获取当前团队的统计信息(记忆条数、Agent 数量等)。返回的数据自动限定在 API Key 所属的团队范围内。</div>
<pre><code>curl -X GET "https://memory.lsz.name/stats" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: $(date +%s)" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": {
# "personal_memories": 11,
# "team_memories": 4,
# "agents": 4,
# "redis_ok": true,
# "embedding_ok": true
# }}
# 响应字段:
# personal_memories int 个人记忆条数
# team_memories int 团队共享记忆条数
# agents int Agent 数量
# redis_ok bool Redis 连接状态
# embedding_ok bool Embedding 服务状态</code></pre>
</div>
<h3 id="ep-teams">4.2 团队管理</h3>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/teams</span><span class="tag auth">需认证</span>
<div class="desc">创建团队。</div>
<pre><code>curl -X POST https://memory.lsz.name/teams \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"team_id": "my_team", "name": "My Team", "description": "团队描述"}'
# {"ok": true, "data": {"id": "my_team", "name": "My Team", ...}}
# 参数:
# team_id string 必填 团队唯一标识
# name string 必填 团队名称
# description string 可选 描述
# config object 可选 配置(JSON</code></pre>
</div>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/teams/{team_id}</span><span class="tag auth">需认证</span>
<div class="desc">查询团队信息。</div>
<pre><code>curl -X GET "https://memory.lsz.name/teams/my_team" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": {"id": "my_team", "name": "My Team", ...}}</code></pre>
</div>
<div class="endpoint delete">
<span class="method delete">DELETE</span><span class="path">/teams/{team_id}</span><span class="tag auth">需认证</span>
<div class="desc">删除团队及其所有记忆。</div>
<pre><code>curl -X DELETE "https://memory.lsz.name/teams/my_team" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": null}</code></pre>
</div>
<h3 id="ep-agents">4.3 Agent 管理</h3>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/agents</span><span class="tag auth">需认证</span>
<div class="desc">在当前团队下创建 Agent。</div>
<pre><code>curl -X POST https://memory.lsz.name/agents \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"agent_id": "researcher", "name": "研究员", "role": "research"}'
# {"ok": true, "data": {"id": "researcher", "team_id": "my_team", "name": "研究员", ...}}
# 参数:
# agent_id string 必填 Agent 唯一标识(团队内唯一)
# name string 必填 显示名称
# role string 可选 角色描述
# config object 可选 配置(JSON</code></pre>
</div>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/agents/{team_id}/{agent_id}</span><span class="tag auth">需认证</span>
<div class="desc">查询 Agent 信息。仅能查询本团队的 Agent。</div>
<pre><code>curl -X GET "https://memory.lsz.name/agents/my_team/researcher" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": {"id": "researcher", "team_id": "my_team", ...}}</code></pre>
</div>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/agents/{team_id}</span><span class="tag auth">需认证</span>
<div class="desc">列出团队下所有 Agent。</div>
<pre><code>curl -X GET "https://memory.lsz.name/agents/my_team" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": [{"id": "researcher", ...}, {"id": "coder", ...}]}</code></pre>
</div>
<div class="endpoint delete">
<span class="method delete">DELETE</span><span class="path">/agents/{team_id}/{agent_id}</span><span class="tag auth">需认证</span>
<div class="desc">删除 Agent 及其个人记忆和工作记忆。</div>
<pre><code>curl -X DELETE "https://memory.lsz.name/agents/my_team/researcher" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": null}</code></pre>
</div>
<h3 id="ep-personal">4.4 个人长期记忆</h3>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/memories/personal</span><span class="tag auth">需认证</span>
<div class="desc">写入个人长期记忆。自动进行 BGE 向量化并持久化到 MySQL。</div>
<pre><code>curl -X POST https://memory.lsz.name/memories/personal \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"agent_id": "researcher", "content": "用户偏好简洁回复,不喜欢冗长解释", "importance": 0.8}'
# {"ok": true, "data": {"id": 42, "content": "用户偏好简洁回复...", "importance": 0.8, ...}}
# 参数:
# agent_id string 必填 Agent 标识
# content string 必填 记忆内容
# importance float 可选 重要性评分 0.0~1.0(默认 0.5
# metadata object 可选 元数据(JSON
# enable_dedup bool 可选 是否启用去重(默认 true)
# dedup_threshold float 可选 去重相似度阈值 0.0~1.0(默认 0.85
#
# 去重行为: 当 enable_dedup=true 时,系统会将新记忆与已有记忆做向量相似度
# 比较。若存在相似度 >= dedup_threshold 的已有记忆,则跳过写入并返回该已有
# 记忆(响应中包含 dedup_skipped=true 和 dedup_score 字段)。</code></pre>
</div>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/memories/personal/search</span><span class="tag auth">需认证</span>
<div class="desc">语义检索个人记忆。查询文本经 BGE 向量化后,与存储向量计算余弦相似度,返回最相关的 Top-K 结果。命中的记忆会自动更新 <code>last_accessed</code><br><strong>提示:</strong>query 为空时,自动 fallback 为返回最近的记忆列表(等同于 <code>GET /memories/personal/recent/{agent_id}</code>)。</div>
<pre><code>curl -X POST https://memory.lsz.name/memories/personal/search \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"agent_id": "researcher", "query": "用户的沟通偏好", "limit": 5, "min_score": 0.3}'
# {"ok": true, "data": [
# {"id": 42, "content": "用户偏好简洁回复...", "importance": 0.8, "score": 0.75,
# "metadata": {"source": "compression", "source_items": [...]}, ...},
# ...
# ]}
# 参数:
# agent_id string 必填 Agent 标识
# query string 可选 查询文本(自然语言),为空时返回最近记忆
# limit int 可选 返回条数(默认 10,最大 50)
# min_score float 可选 最低相似度阈值 0.0~1.0(默认 0.3
# max_chars_per_memory int 可选 单条记忆最大字符数(0=不限制)
# max_total_chars int 可选 所有返回记忆的总字符预算(0=不限制)
#
# 字符限制: max_chars_per_memory 会截断超长单条记忆(末尾追加 "...")。
# max_total_chars 在累计达到预算后停止返回更多结果。两者可组合使用。</code></pre>
</div>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/memories/personal/recent/{agent_id}</span><span class="tag auth">需认证</span>
<div class="desc">获取最近的个人记忆列表(按时间倒序)。读取时自动更新 <code>last_accessed</code></div>
<pre><code>curl -X GET "https://memory.lsz.name/memories/personal/recent/researcher?limit=10" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": [{"id": 42, "content": "...", "importance": 0.8, ...}, ...]}
# 查询参数:
# limit int 可选 返回条数(默认 20,最大 100)</code></pre>
</div>
<div class="endpoint put">
<span class="method put">PUT</span><span class="path">/memories/personal/{id}</span><span class="tag auth">需认证</span>
<div class="desc">更新个人记忆(内容、重要性等)。更新时自动重新向量化并更新 <code>last_accessed</code></div>
<pre><code>curl -X PUT https://memory.lsz.name/memories/personal/42 \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"content": "用户偏好简洁回复,重要结论需要给出论据", "importance": 0.9}'
# {"ok": true, "data": {"id": 42, "content": "...", "importance": 0.9, ...}}</code></pre>
</div>
<div class="endpoint delete">
<span class="method delete">DELETE</span><span class="path">/memories/personal/{id}</span><span class="tag auth">需认证</span>
<div class="desc">删除个人记忆。</div>
<pre><code>curl -X DELETE "https://memory.lsz.name/memories/personal/42" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": null}</code></pre>
</div>
<h3 id="ep-team-mem">4.5 团队共享记忆</h3>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/memories/team</span><span class="tag auth">需认证</span>
<div class="desc">写入团队共享记忆。团队内所有 Agent 可检索。</div>
<pre><code>curl -X POST https://memory.lsz.name/memories/team \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"content": "代码规范:所有接口返回 ResultBean 统一格式", "importance": 0.9}'
# {"ok": true, "data": {"id": 5, "content": "代码规范:...", "importance": 0.9, ...}}
# 参数:
# content string 必填 记忆内容
# importance float 可选 重要性评分 0.0~1.0(默认 0.5
# category string 可选 分类标签(默认 "general"
# metadata object 可选 元数据(JSON
# enable_dedup bool 可选 是否启用去重(默认 true)
# dedup_threshold float 可选 去重相似度阈值 0.0~1.0(默认 0.85</code></pre>
</div>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/memories/team/search</span><span class="tag auth">需认证</span>
<div class="desc">语义检索团队共享记忆。<strong>提示:</strong>query 为空时,自动 fallback 为返回最近的记忆列表(等同于 <code>GET /memories/team/recent</code>)。</div>
<pre><code>curl -X POST https://memory.lsz.name/memories/team/search \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"query": "接口规范", "limit": 5, "min_score": 0.3}'
# {"ok": true, "data": [{"id": 5, "content": "代码规范:...", "score": 0.82, ...}, ...]}
# 参数:
# query string 可选 查询文本,为空时返回最近记忆
# limit int 可选 返回条数(默认 10,最大 50)
# min_score float 可选 最低相似度阈值(默认 0.3)
# max_chars_per_memory int 可选 单条记忆最大字符数(0=不限制)
# max_total_chars int 可选 所有返回记忆的总字符预算(0=不限制)</code></pre>
</div>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/memories/team/recent</span><span class="tag auth">需认证</span>
<div class="desc">获取最近的团队共享记忆列表。</div>
<pre><code>curl -X GET "https://memory.lsz.name/memories/team/recent?limit=10" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": [{"id": 5, "content": "...", ...}, ...]}</code></pre>
</div>
<div class="endpoint put">
<span class="method put">PUT</span><span class="path">/memories/team/{id}</span><span class="tag auth">需认证</span>
<div class="desc">更新团队共享记忆。</div>
<pre><code>curl -X PUT https://memory.lsz.name/memories/team/5 \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"content": "代码规范:所有接口返回 ResultBean,含 ok/data/error/code 字段"}'
# {"ok": true, "data": {"id": 5, "content": "...", ...}}</code></pre>
</div>
<div class="endpoint delete">
<span class="method delete">DELETE</span><span class="path">/memories/team/{id}</span><span class="tag auth">需认证</span>
<div class="desc">删除团队共享记忆。</div>
<pre><code>curl -X DELETE "https://memory.lsz.name/memories/team/5" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": null}</code></pre>
</div>
<h3 id="ep-working">4.6 工作记忆</h3>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/memories/working</span><span class="tag auth">需认证</span>
<div class="desc">写入工作记忆。存储在 Redis 中,默认 TTL 72 小时。</div>
<pre><code>curl -X POST https://memory.lsz.name/memories/working \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"agent_id": "researcher", "content": "当前正在处理用户关于量化策略的查询"}'
# {"ok": true, "data": {"agent_id": "researcher", "content": "..."}}
# 参数:
# agent_id string 必填 Agent 标识
# content string 必填 工作记忆内容
# ttl int 可选 TTL 秒数(默认 259200,即 72 小时)</code></pre>
</div>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/memories/working/{agent_id}</span><span class="tag auth">需认证</span>
<div class="desc">获取指定 Agent 的工作记忆列表(按时间倒序)。</div>
<pre><code>curl -X GET "https://memory.lsz.name/memories/working/researcher?limit=20" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": [{"content": "...", "created_at": "..."}, ...]}
# 查询参数:
# limit int 可选 返回条数(默认 50)</code></pre>
</div>
<div class="endpoint delete">
<span class="method delete">DELETE</span><span class="path">/memories/working/{agent_id}</span><span class="tag auth">需认证</span>
<div class="desc">清空指定 Agent 的全部工作记忆。</div>
<pre><code>curl -X DELETE "https://memory.lsz.name/memories/working/researcher" \
-H "X-API-Key: your_api_key_here" \
-H "X-Timestamp: &lt;unix-ts&gt;" \
-H "X-Signature: &lt;signature&gt;"
# {"ok": true, "data": {"cleared": 5}}</code></pre>
</div>
<h3 id="ep-lifecycle">4.7 生命周期管理</h3>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/lifecycle/compress</span><span class="tag auth">需认证</span>
<div class="desc">将工作记忆压缩为长期记忆。从 Redis 读取工作记忆,调用 LLM 生成摘要后写入 MySQL 长期记忆,原始工作记忆被删除。</div>
<pre><code>curl -X POST https://memory.lsz.name/lifecycle/compress \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"agent_id": "researcher", "target_count": 5}'
# {"ok": true, "data": {
# "compressed": 12,
# "remaining": 3,
# "total_before": 15,
# "target_count": 5,
# "summary": "用户主要关注量化策略优化..."
# }}
# 参数:
# agent_id string 必填 Agent 标识
# target_count int 可选 压缩后保留的目标条数(默认 5)
# 响应字段:
# compressed int 已压缩的条目数
# remaining int 压缩后剩余的工作记忆条目数
# total_before int 压缩前的工作记忆总条目数
# target_count int 请求的目标压缩条数
# summary string 生成的摘要文本(可为 null)</code></pre>
</div>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/lifecycle/cleanup</span><span class="tag auth">需认证</span>
<div class="desc">清理过期和低重要性的长期记忆。基于 <code>last_accessed</code>(或回退到 <code>created_at</code>)和 <code>importance</code> 双维度筛选。</div>
<pre><code>curl -X POST https://memory.lsz.name/lifecycle/cleanup \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"max_age_days": 90, "min_importance": 0.2}'
# {"ok": true, "data": {"cleaned": 3, "remaining": 8}}
# 参数:
# max_age_days int 可选 最大未访问天数(默认 90)
# min_importance float 可选 重要性阈值 0.0~1.0(默认 0.2
# 仅清理 importance &lt; min_importance
# 且 last_accessed &gt; max_age_days 的记忆</code></pre>
</div>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/lifecycle/extract-facts</span><span class="tag auth">需认证</span>
<div class="desc">从工作记忆中提取结构化原子事实。调用 LLM 分析工作记忆内容,提取独立的、有价值的结构化事实,每条事实存为独立的长期记忆。</div>
<div class="warn">⏱️ <strong>LLM 驱动接口</strong>:此接口调用大语言模型处理,预计耗时 5-15 秒,请适当设置客户端超时。</div>
<pre><code>curl -X POST https://memory.lsz.name/lifecycle/extract-facts \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"agent_id": "researcher", "max_memories": 20, "delete_after": false}'
# {"ok": true, "data": {
# "facts": [
# {"id": "abc123", "content": "...", "importance": 0.8, "category": "..."},
# ...
# ],
# "count": 4
# }}
# 参数:
# agent_id string 必填 Agent 标识
# max_memories int 可选 分析的最大工作记忆条数(默认 20)
# delete_after bool 可选 提取后是否清空工作记忆(默认 false)
# 响应字段:
# facts array 提取的事实列表,每条含 id/content/importance/category
# count int 提取的事实数量</code></pre>
</div>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/lifecycle/aggregate-scenes</span><span class="tag auth">需认证</span>
<div class="desc">将个人记忆按主题聚合成场景块。由系统内置 LLM 自动分析记忆内容,将相关记忆归入同一场景并生成摘要,无需额外配置。</div>
<div class="warn">⏱️ <strong>LLM 驱动接口</strong>:此接口调用大语言模型处理,预计耗时 5-15 秒,请适当设置客户端超时。</div>
<pre><code>curl -X POST https://memory.lsz.name/lifecycle/aggregate-scenes \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"agent_id": "researcher", "max_memories": 50}'
# {"ok": true, "data": {
# "scenarios": [
# {"id": "sc001", "name": "...", "summary": "...", "memory_ids": ["m1","m2"]},
# ...
# ],
# "count": 3
# }}
# 参数:
# agent_id string 必填 Agent 标识
# max_memories int 可选 分析的最大记忆条数(默认 50)
# 响应字段:
# scenarios array 场景列表,每条含 id/name/summary/memory_ids
# count int 场景数量</code></pre>
</div>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/lifecycle/scenarios/{agent_id}</span><span class="tag auth">需认证</span>
<div class="desc">获取指定 Agent 已存储的场景块。</div>
<pre><code>curl https://memory.lsz.name/lifecycle/scenarios/researcher \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;'
# {"ok": true, "data": [
# {"id": "sc001", "name": "...", "summary": "...", "memory_ids": [...], "created_at": "..."},
# ...
# ]}</code></pre>
</div>
<div class="endpoint delete">
<span class="method delete">DELETE</span><span class="path">/lifecycle/scenarios/{id}</span><span class="tag auth">需认证</span>
<div class="desc">删除指定场景块(需属于同一团队)。</div>
<pre><code>curl -X DELETE https://memory.lsz.name/lifecycle/scenarios/sc001 \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;'
# {"ok": true, "data": {"deleted": "sc001"}}</code></pre>
</div>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/lifecycle/generate-persona</span><span class="tag auth">需认证</span>
<div class="desc">从记忆和场景中生成用户画像。由系统内置 LLM 自动分析历史记忆,输出结构化画像(偏好、习惯、擅长领域、沟通风格、摘要),无需额外配置。</div>
<div class="warn">⏱️ <strong>LLM 驱动接口</strong>:此接口调用大语言模型处理,预计耗时 5-15 秒,请适当设置客户端超时。</div>
<pre><code>curl -X POST https://memory.lsz.name/lifecycle/generate-persona \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{"agent_id": "researcher", "max_items": 30}'
# {"ok": true, "data": {
# "persona": {
# "id": "p001",
# "preferences": ["...", "..."],
# "habits": ["...", "..."],
# "expertise": ["...", "..."],
# "communication_style": "...",
# "summary": "..."
# }
# }}
# 参数:
# agent_id string 必填 Agent 标识
# max_items int 可选 分析的最大记忆+场景条数(默认 30)
# 响应字段:
# persona.preferences array 用户偏好列表
# persona.habits array 用户习惯列表
# persona.expertise array 擅长领域列表
# persona.communication_style string 沟通风格描述
# persona.summary string 画像摘要</code></pre>
</div>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/lifecycle/persona/{agent_id}</span><span class="tag auth">需认证</span>
<div class="desc">获取指定 Agent 的最新用户画像。</div>
<pre><code>curl https://memory.lsz.name/lifecycle/persona/researcher \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;'
# {"ok": true, "data": {
# "id": "p001", "agent_id": "researcher", "preferences": [...], ...
# }}</code></pre>
</div>
<div class="endpoint delete">
<span class="method delete">DELETE</span><span class="path">/lifecycle/persona/{id}</span><span class="tag auth">需认证</span>
<div class="desc">删除指定用户画像(需属于同一团队)。</div>
<pre><code>curl -X DELETE https://memory.lsz.name/lifecycle/persona/p001 \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;'
# {"ok": true, "data": {"deleted": "p001"}}</code></pre>
</div>
<div class="endpoint get">
<span class="method get">GET</span><span class="path">/lifecycle/auto-config</span><span class="tag auth">需认证</span>
<div class="desc">查看当前 Pipeline 自动化配置(压缩规则、清理规则、暖机规则)。</div>
<pre><code>curl https://memory.lsz.name/lifecycle/auto-config \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;'
# {"ok": true, "data": {
# "team_id": "hermes1",
# "compress_every_n": 10,
# "compress_target_count": 5,
# "cleanup_idle_days": 30,
# "cleanup_min_importance": 0.2,
# "enabled": true,
# "warmup_max_memories": 5,
# "warmup_compress_every_n": 2
# }}</code></pre>
</div>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/lifecycle/auto-config</span><span class="tag auth">需认证</span>
<div class="desc">设置 Pipeline 自动化规则。可配置自动压缩阈值、清理策略和暖机规则。</div>
<pre><code>curl -X POST https://memory.lsz.name/lifecycle/auto-config \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{
"compress_every_n": 10,
"compress_target_count": 5,
"cleanup_idle_days": 30,
"cleanup_min_importance": 0.2,
"enabled": true,
"warmup_max_memories": 5,
"warmup_compress_every_n": 2
}'
# {"ok": true, "data": {...}}
# 参数:
# compress_every_n int 可选 工作记忆达到 N 条时自动压缩(0=禁用)
# compress_target_count int 可选 压缩后保留的目标条数(默认 5)
# cleanup_idle_days int 可选 自动清理未访问天数(0=禁用)
# cleanup_min_importance float 可选 清理的重要性阈值(默认 0.2)
# enabled bool 可选 是否启用自动规则(默认 true)
# warmup_max_memories int 可选 暖机阈值:个人记忆少于 N 条时进入暖机模式(0=禁用)
# warmup_compress_every_n int 可选 暖机模式下的压缩阈值(默认 1)</code></pre>
</div>
<div class="endpoint post">
<span class="method post">POST</span><span class="path">/lifecycle/auto-run</span><span class="tag auth">需认证</span>
<div class="desc">手动触发一次自动清理。根据当前配置清理过期和低重要性记忆。</div>
<pre><code>curl -X POST https://memory.lsz.name/lifecycle/auto-run \
-H 'Content-Type: application/json' \
-H 'X-API-Key: your_api_key_here' \
-H 'X-Timestamp: &lt;unix-ts&gt;' \
-H 'X-Signature: &lt;signature&gt;' \
-d '{}'
# {"ok": true, "data": {"results": [...]}}</code></pre>
</div>
<div class="card">
<p><strong>last_accessed 字段维护说明</strong></p>
<p>系统在以下操作时自动更新记忆的 <code>last_accessed</code> 时间戳:</p>
<ul>
<li><strong>语义检索命中</strong><code>POST /memories/personal/search</code><code>POST /memories/team/search</code></li>
<li><strong>读取最近记忆</strong><code>GET /memories/personal/recent/{id}</code><code>GET /memories/team/recent</code></li>
<li><strong>更新记忆内容</strong><code>PUT /memories/personal/{id}</code><code>PUT /memories/team/{id}</code></li>
</ul>
<p>新创建的记忆 <code>last_accessed</code> 初始为 NULLcleanup 时回退到 <code>created_at</code>。因此从未被访问过的记忆也会被纳入清理范围。</p>
</div>
<!-- ==================== Section 5: Integration Examples ==================== -->
<h2 id="examples">5. 对接示例</h2>
<p>以下示例均使用前文的 <code>MemoryClient</code> 类。请将 <code>API_KEY</code><code>SECRET</code> 替换为实际值。</p>
<h3 id="ex-openclaw">5.1 OpenClaw 对接</h3>
<p>场景:AI 助手 <strong>OpenClaw</strong> 记住用户偏好和团队知识。</p>
<pre><code>from memory_client import MemoryClient # 假设已封装为模块
client = MemoryClient("your_api_key_here", "your_secret_here")
# --- 1. 创建团队和 Agent ---
client.post("/teams", {"team_id": "openclaw_team", "name": "OpenClaw 团队"})
client.post("/agents", {"agent_id": "assistant", "name": "AI 助手", "role": "assistant"})
# --- 2. 写入个人记忆(用户偏好)---
client.post("/memories/personal", {
"agent_id": "assistant",
"content": "用户偏好简洁直接的回复,不喜欢冗长的解释",
"importance": 0.9
})
client.post("/memories/personal", {
"agent_id": "assistant",
"content": "用户的技术栈:Java 后端 + WSL2 + 腾讯云新加坡",
"importance": 0.7
})
# --- 3. 写入团队记忆(编码规范)---
client.post("/memories/team", {
"content": "Java 接口规范:ResultBean 统一返回格式,@ControllerAdvice 全局异常处理",
"importance": 0.8
})
client.post("/memories/team", {
"content": "部署规范:代码变更后必须三同步——代码逻辑、内部文档、公网文档",
"importance": 0.95
})
# --- 4. 语义检索(个人记忆)---
r = client.post("/memories/personal/search", {
"agent_id": "assistant",
"query": "用户的沟通风格偏好",
"limit": 3,
"min_score": 0.3
})
results = r.json()["data"]
for m in results:
print(f"[{m['score']:.2f}] {m['content']}")
# 输出:
# [0.75] 用户偏好简洁直接的回复,不喜欢冗长的解释
# [0.52] 用户的技术栈:Java 后端 + WSL2 + 腾讯云新加坡
# --- 5. 语义检索(团队记忆)---
r = client.post("/memories/team/search", {
"query": "接口返回格式规范",
"limit": 3,
"min_score": 0.3
})
for m in r.json()["data"]:
print(f"[{m['score']:.2f}] {m['content']}")
# [0.82] Java 接口规范:ResultBean 统一返回格式...
</code></pre>
<h3 id="ex-hermes">5.2 Hermes 对接</h3>
<p>场景:量化策略系统 <strong>Hermes</strong> 的多个 Agent 积累策略研究知识。</p>
<pre><code>from memory_client import MemoryClient
client = MemoryClient("your_api_key_here", "your_secret_here")
# --- 1. 创建团队和 Agent ---
client.post("/teams", {"team_id": "hermes", "name": "Hermes 策略研究部"})
client.post("/agents", {"agent_id": "cloud_strategist", "name": "云策", "role": "决策调度"})
client.post("/agents", {"agent_id": "backtest_engineer", "name": "弈回", "role": "回测工程师"})
client.post("/agents", {"agent_id": "review_analyst", "name": "弈析", "role": "复盘分析"})
# --- 2. 多 Agent 写入记忆 ---
client.post("/memories/personal", {
"agent_id": "cloud_strategist",
"content": "趋势策略在震荡行情中表现不佳,需要增加行情识别层",
"importance": 0.9
})
client.post("/memories/personal", {
"agent_id": "backtest_engineer",
"content": "回测数据源:Binance USDT 永续合约,1h K线,2024-01 至今",
"importance": 0.7
})
client.post("/memories/personal", {
"agent_id": "review_analyst",
"content": "2025-06 策略复盘:最大回撤 12%,主要损失来自 6/3 闪崩",
"importance": 0.85
})
client.post("/memories/team", {
"content": "风控规则:单策略最大仓位 30%,总杠杆不超过 3x",
"importance": 0.95
})
# --- 3. 查看统计 ---
r = client.get("/stats")
stats = r.json()["data"]
print(f"个人记忆: {stats['personal_memories']} 条")
print(f"团队记忆: {stats['team_memories']} 条")
print(f"Agent 数: {stats['agents']}")
print(f"Redis: {'OK' if stats['redis_ok'] else 'DOWN'}")
print(f"Embedding: {'OK' if stats['embedding_ok'] else 'DOWN'}")
# 个人记忆: 3 条
# 团队记忆: 1 条
# Agent 数: 3
# Redis: OK
# Embedding: OK
# --- 4. 获取最近记忆 ---
r = client.get("/memories/personal/recent/cloud_strategist", params={"limit": 5})
for m in r.json()["data"]:
print(f"[{m['importance']:.1f}] {m['content'][:50]}...")
# [0.9] 趋势策略在震荡行情中表现不佳,需要增加行情识别层...
</code></pre>
<h3 id="ex-claude">5.3 Claude Code 对接</h3>
<p>场景:<strong>Claude Code</strong> 使用工作记忆记录当前编码上下文,任务完成后压缩为长期记忆。</p>
<pre><code>from memory_client import MemoryClient
client = MemoryClient("your_api_key_here", "your_secret_here")
# --- 1. 创建团队和 Agent ---
client.post("/teams", {"team_id": "claude_code", "name": "Claude Code 团队"})
client.post("/agents", {"agent_id": "coder", "name": "编码助手", "role": "developer"})
# --- 2. 写入工作记忆(当前任务上下文)---
working_items = [
"正在重构 memory_system.py 中的 compress 方法",
"目标:将返回值从 {summary: string} 改为结构化 dict",
"需要同步更新:routes.py, docs/index.html, README_INTERNAL.html",
"测试方法:E2E curl 验证响应格式",
]
for item in working_items:
client.post("/memories/working", {
"agent_id": "coder",
"content": item
})
# --- 3. 查看工作记忆 ---
r = client.get("/memories/working/coder")
print(f"工作记忆: {len(r.json()['data'])} 条")
# 工作记忆: 4 条
# --- 4. 任务完成后,压缩为长期记忆 ---
r = client.post("/lifecycle/compress", {
"agent_id": "coder",
"target_count": 1
})
result = r.json()["data"]
print(f"压缩: {result['compressed']} 条 → {result['remaining']} 条")
print(f"摘要: {result['summary']}")
# 压缩: 4 条 → 1 条
# 摘要: 重构 memory_system.py 的 compress 方法,将返回值改为结构化 dict...
# --- 5. 验证:语义检索 ---
r = client.post("/memories/personal/search", {
"agent_id": "coder",
"query": "compress 方法重构",
"limit": 3,
"min_score": 0.3
})
for m in r.json()["data"]:
print(f"[{m['score']:.2f}] {m['content'][:60]}...")
# [0.71] 重构 memory_system.py 的 compress 方法...
</code></pre>
<h3 id="ex-codex">5.4 Codex 对接</h3>
<p>场景:<strong>Codex</strong> Agent 记忆管理 + 验证跨团队隔离。</p>
<pre><code>from memory_client import MemoryClient
client = MemoryClient("your_api_key_here", "your_secret_here")
# --- 1. 创建团队和 Agent ---
client.post("/teams", {"team_id": "codex_team", "name": "Codex 团队"})
client.post("/agents", {"agent_id": "codex_agent", "name": "Codex 助手", "role": "coder"})
# --- 2. 写入记忆 ---
client.post("/memories/personal", {
"agent_id": "codex_agent",
"content": "Codex 默认使用 gpt-4 模型,CLI 支持 --model 参数切换",
"importance": 0.7
})
client.post("/memories/personal", {
"agent_id": "codex_agent",
"content": "Codex 配置文件位于 ~/.codex/config.toml",
"importance": 0.6
})
# --- 3. 验证隔离:用当前 Key 查询其他团队 ---
# 尝试访问不存在的团队(预期:403)
r = client.get("/teams/other_team")
print(f"跨团队查询: {r.status_code} - {r.json().get('code')}")
# 跨团队查询: 403 - FORBIDDEN_CROSS_TEAM
# 尝试查询其他团队的 Agent(预期:403)
r = client.get("/agents/other_team/some_agent")
print(f"跨团队Agent: {r.status_code} - {r.json().get('code')}")
# 跨团队Agent: 403 - FORBIDDEN_CROSS_TEAM
</code></pre>
<h3 id="ex-isolation">5.5 跨团队隔离验证</h3>
<p>验证不同团队之间的数据完全隔离:</p>
<pre><code>from memory_client import MemoryClient
# 两个不同团队的客户端
team_a = MemoryClient("team_a_api_key", "team_a_secret")
team_b = MemoryClient("team_b_api_key", "team_b_secret")
# --- Team A 写入记忆 ---
team_a.post("/teams", {"team_id": "team_a", "name": "Team Alpha"})
team_a.post("/agents", {"agent_id": "agent_1", "name": "Agent 1"})
team_a.post("/memories/personal", {
"agent_id": "agent_1",
"content": "这是 Team A 的私密记忆",
"importance": 0.9
})
# --- Team B 写入记忆 ---
team_b.post("/teams", {"team_id": "team_b", "name": "Team Beta"})
team_b.post("/agents", {"agent_id": "agent_1", "name": "Agent B1"})
team_b.post("/memories/personal", {
"agent_id": "agent_1",
"content": "这是 Team B 的私密记忆",
"importance": 0.8
})
# --- 验证隔离 ---
# Team A 搜索自己的记忆
r = team_a.post("/memories/personal/search", {
"agent_id": "agent_1", "query": "私密记忆", "limit": 10
})
a_results = [m["content"] for m in r.json()["data"]]
print(f"Team A sees: {a_results}")
# Team A sees: ["这是 Team A 的私密记忆"] ← 看不到 Team B 的
# Team A 搜索团队记忆(也看不到 Team B 的)
r = team_a.post("/memories/team/search", {
"query": "私密", "limit": 10
})
print(f"Team A team search: {len(r.json()['data'])} 条")
# Team A team search: 0 条 ← Team B 的团队记忆不可见
# Team A 的 Stats 只包含自己的数据
r = team_a.get("/stats")
stats = r.json()["data"]
print(f"Team A stats: {stats['personal_memories']} personal, {stats['team_memories']} team")
# Team A stats: 1 personal, 0 team ← 只看到自己的
print("\n✅ 跨团队隔离验证通过")
</code></pre>
<h3 id="ex-lifecycle">5.6 记忆生命周期完整示例</h3>
<p>场景:完整的记忆生命周期 —— 从工作记忆写入,到原子事实提取、场景聚合、画像生成,最后配置自动化 Pipeline。</p>
<pre><code>from memory_client import MemoryClient
client = MemoryClient("your_api_key_here", "your_secret_here")
# --- 1. 创建团队和 Agent ---
client.post("/teams", {"team_id": "research_lab", "name": "研究实验室"})
client.post("/agents", {"agent_id": "researcher", "name": "研究员", "role": "研究分析"})
# --- 2. 写入工作记忆(模拟日常交互)---
wm_items = [
"今天决定把量化策略的回测周期从 3 个月改为 6 个月,因为短期数据噪声太大",
"发现 DeepSeek V4 在代码生成任务上比 MiMo 快约 40%,但中文理解稍弱",
"用户偏好简洁直接的回复风格,不喜欢冗长的解释",
"Redis 缓存命中率从 75% 提升到 92% 后,API 响应时间从 120ms 降到 45ms",
"下周一需要和团队讨论 Q3 的风控模型升级方案",
]
for item in wm_items:
client.post("/memories/working", {"agent_id": "researcher", "content": item})
print(f"工作记忆: {len(wm_items)} 条")
# --- 3. 原子事实提取 ---
# 从工作记忆中提取结构化事实,每条事实存为独立的长期记忆
r = client.extract_facts("researcher", max_memories=20, delete_after=False)
facts = r.json()["data"]["facts"]
print(f"\n提取了 {len(facts)} 条原子事实:")
for f in facts:
print(f" [{f['importance']:.1f}] [{f['category']}] {f['content'][:60]}...")
# --- 4. 场景聚合 ---
# 将长期记忆按主题聚合为场景块
r = client.post("/memories/personal", {
"agent_id": "researcher",
"content": "2025-06 策略复盘:最大回撤 12%,主要损失来自 6/3 闪崩",
"importance": 0.85
})
r = client.post("/memories/personal", {
"agent_id": "researcher",
"content": "回测数据源:Binance USDT 永续合约,1h K线,2024-01 至今",
"importance": 0.7
})
r = client.post("/memories/team", {
"content": "风控规则:单策略最大仓位 30%,总杠杆不超过 3x",
"importance": 0.95
})
r = client.aggregate_scenarios("researcher", max_memories=50)
scenarios = r.json()["data"]["scenarios"]
print(f"\n聚合了 {len(scenarios)} 个场景:")
for s in scenarios:
print(f" [{s['name']}] {s['summary'][:60]}... (关联 {len(s['memory_ids'])} 条记忆)")
# --- 5. 查看已存储场景 ---
r = client.get_scenarios("researcher")
print(f"\n已存储场景: {len(r.json()['data'])} 个")
# --- 6. 用户画像生成 ---
# 从记忆和场景中提取用户画像
r = client.generate_persona("researcher", max_items=30)
persona = r.json()["data"]["persona"]
print(f"\n用户画像:")
print(f" 偏好: {persona.get('preferences', [])}")
print(f" 习惯: {persona.get('habits', [])}")
print(f" 擅长: {persona.get('expertise', [])}")
print(f" 沟通风格: {persona.get('communication_style', '')}")
print(f" 摘要: {persona.get('summary', '')[:80]}...")
# --- 7. 查看用户画像 ---
r = client.get_persona("researcher")
print(f"\n画像已存储, ID: {r.json()['data'].get('id')}")
# --- 8. 配置 Pipeline 自动化 ---
# 设置自动压缩:每 10 条工作记忆自动压缩
# 设置自动清理:60 天未访问且重要性 < 0.2 的记忆自动清理
# 设置暖机 Agent 5 条记忆使用更频繁的压缩
r = client.set_pipeline_config(
compress_every_n=10,
compress_target_count=5,
cleanup_idle_days=60,
cleanup_min_importance=0.2,
enabled=True,
warmup_max_memories=5,
warmup_compress_every_n=2
)
print(f"\nPipeline 配置: {r.json()['data']}")
# --- 9. 查看 Pipeline 配置 ---
r = client.get_pipeline_config()
config = r.json()["data"]
print(f"自动压缩: {config.get('compress_every_n')} 条触发")
print(f"自动清理: {config.get('cleanup_idle_days')} 天未访问, 重要性 < {config.get('cleanup_min_importance')}")
print(f"暖机模式: {config.get('warmup_max_memories')} 条记忆启用")
# --- 10. 语义检索验证所有数据可检索---
r = client.post("/memories/personal/search", {
"agent_id": "researcher",
"query": "策略回测",
"limit": 5,
"min_score": 0.3
})
print(f"\n搜索 '策略回测': {len(r.json()['data'])} 条结果")
for m in r.json()["data"]:
src = m.get("metadata", {}).get("source", "normal")
print(f" [{m['score']:.2f}] [{src}] {m['content'][:60]}...")
print("\n 记忆生命周期完整示例结束")
</code></pre>
<!-- ==================== Section 6: Data Management ==================== -->
<h2 id="data-mgmt">6. 数据管理</h2>
<h3 id="persistence">6.1 数据持久化说明</h3>
<table>
<tr><th>数据类型</th><th>存储介质</th><th>持久化</th><th>说明</th></tr>
<tr><td>个人长期记忆</td><td>MySQL (InnoDB)</td><td>磁盘持久化</td><td>含内容、向量(BLOB)、重要性、元数据</td></tr>
<tr><td>团队共享记忆</td><td>MySQL (InnoDB)</td><td>磁盘持久化</td><td>同上,团队内所有 Agent 可检索</td></tr>
<tr><td>工作记忆</td><td>Redis</td><td>纯内存</td><td>TTL 自动过期,服务重启可能丢失</td></tr>
<tr><td>API Key 元数据</td><td>MySQL</td><td>磁盘持久化</td><td>Key、Secret Hash、状态等</td></tr>
<tr><td>Agent / Team 元数据</td><td>MySQL</td><td>磁盘持久化</td><td>名称、角色、配置等</td></tr>
<tr><td>场景块 (Scenarios)</td><td>MySQL</td><td>磁盘持久化</td><td>场景名、摘要、关联记忆 ID 列表(JSON)</td></tr>
<tr><td>用户画像 (Persona)</td><td>MySQL</td><td>磁盘持久化</td><td>偏好、习惯、擅长、沟通风格(JSON</td></tr>
<tr><td>Pipeline 配置</td><td>MySQL</td><td>磁盘持久化</td><td>自动压缩/清理/暖机规则(per-team)</td></tr>
</table>
<div class="warn">工作记忆存储在 Redis 中,TTL 到期后自动删除,且不保证服务重启后的持久性。重要内容请通过 <code>/lifecycle/compress</code> 及时转为长期记忆。</div>
<h3 id="cleanup-strategy">6.2 记忆清理与压缩策略</h3>
<h4>工作记忆压缩(/lifecycle/compress</h4>
<p>将 Redis 中的工作记忆压缩为 MySQL 长期记忆:</p>
<ul>
<li>从 Redis 读取指定 Agent 的全部工作记忆</li>
<li>调用 LLM 生成摘要</li>
<li>摘要写入 MySQL 长期记忆</li>
<li>原始工作记忆从 Redis 删除</li>
</ul>
<h4>长期记忆清理(/lifecycle/cleanup</h4>
<p>基于双维度清理过期的长期记忆:</p>
<ul>
<li><strong>时间维度</strong><code>max_age_days</code> — 清理超过指定天数未访问的记忆</li>
<li><strong>重要性维度</strong><code>min_importance</code> — 仅清理重要性评分低于阈值的记忆</li>
</ul>
<p>两个条件同时满足才会被清理:即 <strong>低重要性 + 长期未访问</strong> 的记忆才会被移除。高重要性的记忆即使长期未访问也不会被清理。</p>
<div class="card">
<h4>推荐清理策略</h4>
<table>
<tr><th>场景</th><th>max_age_days</th><th>min_importance</th><th>说明</th></tr>
<tr><td>保守清理</td><td>180</td><td>0.1</td><td>仅清理半年未访问的极低重要性记忆</td></tr>
<tr><td>常规清理</td><td>90</td><td>0.2</td><td>清理 3 个月未访问的低重要性记忆(推荐)</td></tr>
<tr><td>积极清理</td><td>30</td><td>0.3</td><td>清理 1 个月未访问的中低重要性记忆</td></tr>
</table>
</div>
<h3>6.3 智能生命周期策略</h3>
<h4>原子事实提取(/lifecycle/extract-facts</h4>
<p>替代简单压缩,从工作记忆中提取独立的结构化事实:</p>
<ul>
<li>LLM 分析工作记忆内容,识别独立的、长期有价值的观察、决策和知识</li>
<li>每条事实作为独立的长期记忆存储,便于精确检索</li>
<li>适合需要精确回忆的场景(如技术决策、用户偏好、关键数据)</li>
<li>相比压缩,事实提取保留了信息的独立性和可检索性</li>
<li>系统对 LLM 返回结果和工作记忆条目均有类型安全校验,非标准格式数据会被自动跳过并记录警告日志</li>
</ul>
<h4>场景聚合(/lifecycle/aggregate-scenes</h4>
<p>将相关的长期记忆按主题聚合为场景块:</p>
<ul>
<li>LLM 分析多条记忆,识别共同主题并生成场景摘要</li>
<li>场景块保留原始记忆 ID 列表,支持溯源</li>
<li>适合将碎片化的记忆整合为完整的上下文(如项目进展、研究发现)</li>
</ul>
<h4>用户画像生成(/lifecycle/generate-persona</h4>
<p>从记忆和场景中提取结构化用户画像:</p>
<ul>
<li>输出:偏好列表、习惯列表、擅长领域、沟通风格、综合摘要</li>
<li>适合为 Agent 提供长期的用户/上下文理解</li>
<li>建议定期更新(如每周或每月),画像会随记忆积累而丰富</li>
</ul>
<h4>Pipeline 自动化(/lifecycle/auto-config</h4>
<p>配置自动化的记忆管理规则,无需手动触发:</p>
<table>
<tr><th>规则</th><th>配置项</th><th>说明</th></tr>
<tr><td>自动压缩</td><td><code>compress_every_n</code></td><td>工作记忆达到 N 条时自动触发压缩(0=禁用)</td></tr>
<tr><td>自动清理</td><td><code>cleanup_idle_days</code></td><td>超过 N 天未访问且低重要性的记忆自动清理(0=禁用)</td></tr>
<tr><td>暖机模式</td><td><code>warmup_max_memories</code></td><td>新 Agent 个人记忆少于 N 条时,使用更低的压缩阈值(更频繁压缩)</td></tr>
<tr><td>暖机压缩</td><td><code>warmup_compress_every_n</code></td><td>暖机模式下的压缩触发条数(默认 1</td></tr>
</table>
<p>Pipeline 在每次写入工作记忆时自动检查是否达到压缩阈值,无需手动调用。</p>
<h3 id="last-accessed">6.4 last_accessed 字段维护</h3>
<p>系统自动维护每条长期记忆的 <code>last_accessed</code> 时间戳,确保清理策略基于"最近使用时间"而非"创建时间"</p>
<table>
<tr><th>操作</th><th>更新 last_accessed</th><th>说明</th></tr>
<tr><td>语义检索命中</td><td></td><td><code>POST /memories/personal/search</code><code>POST /memories/team/search</code> 返回的结果自动 touch</td></tr>
<tr><td>读取最近列表</td><td></td><td><code>GET /memories/personal/recent/{id}</code><code>GET /memories/team/recent</code> 返回的结果自动 touch</td></tr>
<tr><td>更新记忆内容</td><td></td><td><code>PUT /memories/personal/{id}</code><code>PUT /memories/team/{id}</code> 自动 touch</td></tr>
<tr><td>写入新记忆</td><td></td><td>新记忆 <code>last_accessed</code> 初始为 NULLcleanup 时回退到 <code>created_at</code></td></tr>
</table>
<div class="tip">这意味着:只要 Agent 定期通过 search 或 recent 接口读取记忆,这些记忆就不会被清理策略移除。频繁使用的记忆会一直保留。</div>
<!-- ==================== Section 7: FAQ ==================== -->
<h2 id="faq">7. 常见问题</h2>
<div class="card">
<p><strong>Q: 一个 Key 可以跨多个团队吗?</strong></p>
<p>A: 不能。每个 Key 绑定一个 <code>team_id</code>,只能操作该团队的数据。跨团队访问返回 403 <code>FORBIDDEN_CROSS_TEAM</code></p>
</div>
<div class="card">
<p><strong>Q: 不存在的 agent_id 会报错吗?</strong></p>
<p>A: 查询类接口(GET 列表、搜索)返回空列表 <code>[]</code>;写入类接口返回 <code>AGENT_NOT_FOUND</code> 错误。</p>
</div>
<div class="card">
<p><strong>Q: BGE 为什么输出 512 维?</strong></p>
<p>A: BGE-small-zh-v1.5 的 PyTorch 模型输出 384 维。本系统使用 Xenova 社区预转换的 ONNX 量化版 (model_quantized.onnx),输出 512 维。这是 ONNX 转换差异,不影响语义检索质量。</p>
</div>
<div class="card">
<p><strong>Q: TTL 过期后工作记忆能恢复吗?</strong></p>
<p>A: 不能。Redis 到期自动删除整个 List。请在过期前使用 <code>/lifecycle/compress</code> 将重要记忆转为长期记忆。</p>
</div>
<div class="card">
<p><strong>Q: 语义搜索的最低相似度阈值是多少?</strong></p>
<p>A: 默认 <code>min_score=0.3</code>。值域为 [0.0, 1.0],越高越严格。建议从 0.3 开始,根据实际效果调整。</p>
</div>
<div class="card">
<p><strong>Q: 如何监控服务健康状态?</strong></p>
<p>A: 定期调用 <code>GET /health</code>(无需认证)检查服务存活。调用 <code>GET /stats</code>(需认证)查看记忆条数、Agent 数量、Redis 和 Embedding 服务状态。</p>
</div>
<div class="card">
<p><strong>Q: 原子事实提取和压缩有什么区别?</strong></p>
<p>A: <strong>压缩</strong>将多条工作记忆合并为一条摘要,丢失了原始信息的独立性。<strong>事实提取</strong>将工作记忆分解为独立的结构化事实,每条事实作为独立的长期记忆存储,保留了精确的可检索性。建议:对于需要精确回忆的场景(如技术决策、用户偏好),使用事实提取;对于需要概括性总结的场景,使用压缩。</p>
</div>
<div class="card">
<p><strong>Q: Pipeline 自动化如何工作?</strong></p>
<p>A: 通过 <code>POST /lifecycle/auto-config</code> 配置自动规则后,系统会在每次写入工作记忆时自动检查是否达到压缩阈值。暖机模式下,新 Agent 使用更低的阈值(更频繁压缩),帮助新 Agent 快速积累长期记忆。</p>
</div>
<div class="card">
<p><strong>Q: 使用场景聚合和画像生成需要什么前置条件?</strong></p>
<p>A: 场景聚合和画像生成功能由系统内置 LLM 驱动,您只需调用对应的 API 端点即可,无需额外配置任何 LLM 参数。系统会自动完成事实提取、场景聚合和画像生成。</p>
</div>
<div class="card">
<p><strong>Q: 场景聚合和用户画像有什么区别?</strong></p>
<p>A: <strong>场景聚合</strong>将多条相关记忆按主题分组并生成摘要(如"策略回测系列讨论"),保留原始记忆 ID 列表支持溯源。<strong>用户画像</strong>从全局记忆中提取用户的结构化特征(偏好、习惯、擅长领域),是对用户整体的描述。场景是"事情"的聚合,画像是"人"的描述。</p>
</div>
<div class="footer">
<p>Memory System v3.0.2 | <a href="https://memory.lsz.name">https://memory.lsz.name</a> | 最后更新:2026-06-22 (v3.0.2)</p>
<p>如有问题,请联系系统管理员</p>
</div>
</div><!-- end .content -->
<!-- Mermaid -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
themeVariables: {
darkMode: true,
primaryColor: '#1f6feb',
primaryTextColor: '#c9d1d9',
primaryBorderColor: '#30363d',
lineColor: '#58a6ff',
secondaryColor: '#161b22',
tertiaryColor: '#0d1117',
background: '#161b22',
mainBkg: '#161b22',
nodeBorder: '#30363d',
clusterBkg: '#161b22',
clusterBorder: '#30363d',
edgeLabelBackground: '#161b22',
fontSize: '20px'
},
flowchart: { curve: 'basis', padding: 20 }
});
</script>
<!-- Scroll Spy -->
<script>
(function() {
const sidebar = document.querySelector('.sidebar');
const nav = document.querySelector('.sidebar-nav');
const links = Array.from(nav.querySelectorAll('a[href^="#"]'));
const sections = links.map(a => document.querySelector(a.getAttribute('href'))).filter(Boolean);
function setActive(id) {
links.forEach(a => {
if (a.getAttribute('href') === '#' + id) {
a.classList.add('active');
// Scroll sidebar to keep active link visible
const navRect = sidebar.getBoundingClientRect();
const linkRect = a.getBoundingClientRect();
if (linkRect.top < navRect.top || linkRect.bottom > navRect.bottom) {
a.scrollIntoView({ block: 'center', behavior: 'smooth' });
}
} else {
a.classList.remove('active');
}
});
}
// Use IntersectionObserver for efficient scroll tracking
const observer = new IntersectionObserver((entries) => {
// Find the topmost visible section
const visible = entries
.filter(e => e.isIntersecting)
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
if (visible.length > 0) {
setActive(visible[0].target.id);
}
}, {
rootMargin: '-80px 0px -60% 0px',
threshold: 0
});
sections.forEach(s => observer.observe(s));
// Also handle scroll to bottom (FAQ at end)
window.addEventListener('scroll', function() {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 100) {
const last = sections[sections.length - 1];
if (last) setActive(last.id);
}
}, { passive: true });
})();
</script>
</body>
</html>