- 排除 .env / *.bak / 内部运维文档(README_INTERNAL.html) - config.py 默认密码已替换为占位符 CHANGE_ME_* - init_db.sql 移除生产数据库用户 GRANT 段 - README.html 数据库用户名已脱敏 - 保留:源码 + 公网 API 文档 + 建表 SQL(无授权语句)
1810 lines
83 KiB
HTML
1810 lines
83 KiB
HTML
<!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 < 20ms,读取 P99 < 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 SDK(MemoryClient)</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 + Secret,Key 绑定到对应 <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: <signature>"
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>" \
|
||
-H "X-Signature: <signature>"
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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 < min_importance
|
||
# 且 last_accessed > 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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>'
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>'
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>'
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>'
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>'
|
||
# {"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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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: <unix-ts>' \
|
||
-H 'X-Signature: <signature>' \
|
||
-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> 初始为 NULL,cleanup 时回退到 <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> 初始为 NULL,cleanup 时回退到 <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>
|