diff --git a/docs/6-memory-system.md b/docs/6-memory-system.md index 55a185a..297f924 100644 --- a/docs/6-memory-system.md +++ b/docs/6-memory-system.md @@ -46,6 +46,7 @@ AG Core 已完成 Phase 0(LLM 调用周期)、Phase 1(提示词工程) | F10 | 消息条目级淘汰 | P1 | ConversationMemory 达到上限后删除最旧消息,而非仅压缩内容 | | F11 | 基于召回价值的淘汰 | P2 | 根据召回频率、匹配评分、时效性综合计算"记忆价值",淘汰价值最低的记忆 | | F12 | 召回统计记录 | P2 | MemoryStore 记录每次召回事件(recall_count + score),供淘汰策略使用 | +| F13 | 标签复用查询 | P1 | KnowledgeGraph 提供 find_tags() 接口,供调用方在创建实体时复用已有标签,避免同义标签膨胀 | ### 2.2 非功能需求 @@ -173,21 +174,193 @@ pub struct InMemoryKnowledgeStore { ```rust #[async_trait] pub trait KnowledgeGraph: Send + Sync { + // 实体管理 async fn add_entity(&self, entity: GraphEntity) -> Result<(), MemoryError>; async fn get_entity(&self, id: &str) -> Result, MemoryError>; async fn remove_entity(&self, id: &str) -> Result<(), MemoryError>; + + // 关系管理 async fn add_relation(&self, relation: GraphRelation) -> Result<(), MemoryError>; async fn remove_relation(&self, source_id: &str, target_id: &str, relation_type: &str) -> Result<(), MemoryError>; async fn get_related(&self, entity_id: &str, depth: usize) -> Result, MemoryError>; + + // 检索 async fn find_by_keywords(&self, keywords: &[String]) -> Result, MemoryError>; + + // 标签管理 + async fn find_tags(&self, prefix: &str) -> Result, MemoryError>; + async fn entity_count_by_tag(&self, tag: &str) -> Result; + async fn set_entity_tags(&self, entity_id: &str, tags: Vec) -> Result<(), MemoryError>; + fn tag_constraints(&self) -> TagConstraints; } ``` -- `find_by_keywords()` — 用关键词匹配实体的 `name`(精准/前缀匹配)和 `tags`(完全匹配),不模糊搜索全字段 +- `find_by_keywords()` — 用关键词匹配实体的 `name`(前缀匹配)和 `tags`(完全匹配),不模糊搜索全字段 - `get_related()` — 找到匹配实体后,按 `depth` 遍历关联实体,同时返回关联关系 +- `find_tags()` — 前缀匹配已有标签,供 Phase 4 Agent 做标签复用决策 +- `entity_count_by_tag()` — 查看某标签的实体关联数,判断标签的通用程度 +- `set_entity_tags()` — 替换实体的全部标签,调用方按关联度降序传入,内部自动截断到 `max_tags_per_entity` -- `get_related()` 按指定深度遍历邻居节点 -- `search_entities()` 按实体名称/类型搜索 +#### 标签规范与生成策略 + +标签是连接 `KnowledgeStore`(内容)和 `KnowledgeGraph`(实体)的检索桥梁。两条检索通道共享同一组关键词,通过标签匹配实现交叉发现。 + +##### 标签复用原则 + +标签不应随意增长。**优先复用已有标签,确无合适标签时才创建新标签**。这保证: + +- 同一概念永远使用同一标签(`"state-graph"` 不会出现 `"state-graph"` + `"state-graph-framework"` 并存) +- 共享标签的实体自然形成概念关联,增强图检索的连通性 +- 标签空间收敛,检索效率不随实体数量增长而退化 + +##### 标签注册与复用流程 + +KnowledgeGraph 提供标签查询接口供调用方(Phase 4 Agent)做复用决策: + +```rust +#[async_trait] +pub trait KnowledgeGraph: Send + Sync { + // ... 实体/关系管理方法 ... + + /// 查询已有标签(前缀匹配),用于标签复用决策 + async fn find_tags(&self, prefix: &str) -> Result, MemoryError>; + + /// 查询某标签关联的实体数量 + async fn entity_count_by_tag(&self, tag: &str) -> Result; +} +``` + +Phase 4 Agent 在 Ingest 时遵从这个流程创建实体标签: + +``` +LLM 提取候选标签 → ["LangGraph", "StateGraph", "state-machine", "graph-framework"] + │ + ┌─────────────────────┼──────────────────────┐ + │ for each candidate: │ │ + │ │ │ + │ graph.find_tags(candidate.lowercase()) │ + │ │ │ + │ ├─ 命中已有标签 → 复用(用已有标签字符串) │ + │ │ "state-machine" → 已有 "state-machine" │ + │ │ 用 "state-machine" 而非创建新标签 │ + │ │ │ + │ └─ 无匹配 → 注册新标签 │ + │ "graph-framework" → 全新标签,注册 │ + │ │ + └─────────────────────────────────────────────┘ + +最终 entity.tags = ["langgraph", "state-graph", "state-machine", "graph-framework"] + ↑ 复用了已有标签 +``` + +##### 标签容量与精炼 + +每个实体的标签数量有上限,不能无限增长。超出上限时,保留关联度最高的标签。 + +```rust +pub struct GraphEntity { + pub id: String, + pub name: String, + pub entity_type: String, + pub description: String, + pub tags: Vec, // 检索标签,按关联度降序排列(索引越小越关键) +} + +pub struct TagConstraints { + pub max_tags_per_entity: usize, // 每个实体最多标签数,默认 8 +} +``` + +KnowledgeGraph 提供标签集替换接口,由调用方(Phase 4 Agent)按关联度排序后写入,内部自动截断: + +```rust +#[async_trait] +pub trait KnowledgeGraph: Send + Sync { + // ... 其他方法 ... + + /// 替换实体的全部标签 + /// 调用方确保 tags 已按关联度降序排列 + /// 内部自动截断到 max_tags_per_entity + async fn set_entity_tags(&self, entity_id: &str, tags: Vec) -> Result<(), MemoryError>; + + /// 获取标签容量约束 + fn tag_constraints(&self) -> TagConstraints; +} +``` + +标签精炼流程(Phase 4 Agent 负责编排): + +``` +LLM 从内容中提取候选标签(可能有 15-20 个) + │ + ├─ 1. 评估每个标签与实体的关联度 + │ → "langgraph": 0.95 | "graph-framework": 0.3 | "computer": 0.1 + │ + ├─ 2. 按关联度降序排列 + │ → ["langgraph", "state-graph", "state-machine", "graph", ...] + │ + ├─ 3. 复用已有标签(find_tags) + │ → 将同义候选替换为已有标签 + │ + ├─ 4. 保留 top-8(受 max_tags_per_entity 约束) + │ → ["langgraph", "state-graph", "state-machine", "graph", + │ "agent", "workflow", "llm", "orchestration"] + │ + └─ 5. set_entity_tags(entity_id, top_8) + → KnowledgeGraph 内部截断到 max_tags_per_entity +``` + +##### 标签规范 + +| 规则 | 说明 | 示例 | +|------|------|------| +| 原子性 | 每个标签是一个独立的关键词,不包含空格或标点 | `"state-graph"` ✅ / `"State Graph framework"` ❌ | +| 小写化 | 统一小写存储,匹配时大小写不敏感 | `"langgraph"` | +| 单数优先 | 优先使用单数形式 | `"agent"` 而非 `"agents"` | +| 领域受限 | 3-8 个标签/实体,聚焦可检索性而非描述性 | `["langgraph", "state-graph", "graph", "state-machine"]` | + +**匹配行为(find_by_keywords):** + +| 字段 | 匹配方式 | 说明 | +|------|---------|------| +| `name` | **前缀匹配** | `"lang"` 匹配 `"LangGraph"`、`"LangChain"`,支持大小写不敏感 | +| `tags` | **完全匹配** | `"state-graph"` 只匹配 `"state-graph"`,不匹配 `"state"` | +| `entity_type` | 完全匹配(精确值) | `"framework"` 匹配 `"framework"` | + +**标签生成方式(Phase 4 Agent 层面):** + +``` +Ingest 新内容 + │ + ├─ LLM 提取标签(主要方式) + │ → 分析内容 → 提取 3-8 个关键概念作为标签 + │ → 同时写入 KnowledgePage.tags 和 GraphEntity.tags + │ + ├─ 规则提取(辅助方式) + │ → 从 entity.name 中按 CamelCase / kebab-case 拆分 + │ → "StateGraph" → ["state", "graph", "state-graph"] + │ + └─ 标签继承(关联同步) + → KnowledgePage 的标签自动同步到关联的 GraphEntity + → 页面标签变更时,同步更新关联实体的标签 +``` + +**标签在检索中的作用:** + +``` +检索关键词: ["langgraph", "state-graph"] + │ + ├─ KnowledgeStore 匹配: 页面 title(前缀) / tags(完全) / summary(包含) + │ → 命中 KnowledgePage { tags: ["langgraph", "graph", "state-machine"] } ← tags 完全匹配 + │ → 命中 KnowledgePage { title: "LangGraph StateGraph Guide" } ← title 前缀匹配 + │ + └─ KnowledgeGraph 匹配: 实体 name(前缀) / tags(完全) + → 命中 GraphEntity { name: "LangGraph", tags: ["langgraph", "agent-framework"] } ← 都匹配 + → 命中 GraphEntity { name: "StateGraph", tags: ["langgraph", "state-graph"] } ← tags 完全匹配 + → get_related("StateGraph") → 遍历关联实体 "StateMachine", "Graph" 等 +``` + +`tags` 定位为**精准的检索锚点**,`name` 作为**松散的检索入口**,两者配合确保召回既有广度又有精度。 #### InMemoryGraph — 知识图谱默认实现 @@ -383,6 +556,7 @@ pub struct GraphEntity { pub name: String, pub entity_type: String, // "person" | "concept" | "project" | ... pub description: String, + pub tags: Vec, // 检索标签(全小写,原子词,按关联度降序排列) } pub struct GraphRelation { @@ -841,16 +1015,21 @@ async fn maybe_evict(&self) { - 单元测试:页面 CRUD、index 一致性、搜索 - 验收:`cargo build` + `cargo test` 通过 -### Step 4:KnowledgeGraph +### Step 4:KnowledgeGraph(含标签匹配) **文件**:`src/memory/graph.rs` -- 定义 `GraphEntity`、`GraphRelation` 类型 +- 定义 `GraphEntity`(含 `tags: Vec`)、`GraphRelation` 类型 - 定义 `KnowledgeGraph` trait - 实现 `InMemoryGraph` - BFS/DFS 图遍历 - - search_entities 按名称/类型匹配 -- 单元测试:实体 CRUD、关系添加/删除、图遍历、搜索 + - `find_by_keywords()`:关键词前缀匹配 `name`,完全匹配 `tags`,大小写不敏感 + - `find_tags(prefix)`:标签复用查询,前缀匹配已有标签 + - `set_entity_tags()`:替换实体的全部标签,按传入顺序保留前 `max_tags_per_entity` 个 + - `tag_constraints()`:返回 `TagConstraints { max_tags_per_entity: 8 }` + - 内部维护 `tag_index: HashMap>` 实现标签到实体的快速检索 +- 标签规范在源码中以常量或文档注释形式明确(小写化、原子性、优先复用已有、每实体 ≤8 个) +- 单元测试:实体 CRUD、关系添加/删除、标签匹配(大小写不敏感、前缀/完全区分)、标签复用查询、标签容量截断、图遍历 - 验收:`cargo build` + `cargo test` 通过 ### Step 5:MemoryRetriever(关键词提取 + 评分机制)+ 模块整合 @@ -900,6 +1079,10 @@ async fn maybe_evict(&self) { - [ ] `ConversationMemory` 正确复用 `llm::compact` 的压缩逻辑 - [ ] `KnowledgeStore` trait + `InMemoryKnowledgeStore` 支持页面 CRUD 和 index 维护 - [ ] `KnowledgeGraph` trait + `InMemoryGraph` 支持实体 CRUD、关系管理和图遍历 +- [ ] `KnowledgeGraph::find_tags(prefix)` 支持前缀匹配查询已有标签 +- [ ] `KnowledgeGraph::set_entity_tags()` 替换全部标签,截断到 `max_tags_per_entity` +- [ ] 每个实体的标签数不超过 `TagConstraints::max_tags_per_entity`(默认 8) +- [ ] 标签复用原则在方案文档和源码规范中明确,下游实现可据此遵守 - [ ] `MemoryRetriever` 组合 KnowledgeStore + KnowledgeGraph 返回统一检索结果 - [ ] 无 embedding 相关依赖(不引入 fastembed、pgvector、qdrant 等 crate) - [ ] 模块结构符合项目惯例:`memory.rs` + `memory/` 目录 + `pub use` 重导出