add feature memory
This commit is contained in:
parent
26f244183c
commit
1925de0355
55
.features/memory/MEMORY.md
Normal file
55
.features/memory/MEMORY.md
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
feature: "memory"
|
||||
scope: "Agent 长期记忆能力(基于 Mem0 + pgvector),跨会话回忆与事实提取存储"
|
||||
updated_at: "2026-06-01"
|
||||
status: active
|
||||
---
|
||||
|
||||
# Memory(记忆功能)
|
||||
|
||||
## 当前状态
|
||||
Agent 的长期记忆能力,底层使用 **Mem0** 库 + **pgvector**(PostgreSQL 向量存储)。
|
||||
在 agent 执行前 `recall` 相关记忆并注入 system prompt,在执行后于后台线程异步提取并存储新事实。
|
||||
按 `(user_id, agent_id)` 多租户隔离,每个 `agent_id` 一张 `mem0_{agent_id}` 集合表。
|
||||
|
||||
> 注意:API/配置字段历史上叫 `memori`,为兼容性保留命名,内部实际用的是 **Mem0**。
|
||||
|
||||
## 配置开关
|
||||
| 层级 | 字段 | 默认 | 位置 |
|
||||
|------|------|------|------|
|
||||
| 全局总开关 | `MEM0_ENABLED` (env) | `true` | `utils/settings.py:80` |
|
||||
| Agent 配置 | `enable_memori: bool` | `False` | `agent/agent_config.py:47` |
|
||||
| API 请求 | `enable_memory: bool` | `False` | `utils/api_models.py:56` |
|
||||
| 召回数量 | `memori_semantic_search_top_k: int` | `20` | `agent/agent_config.py:48` |
|
||||
| 召回数量(env) | `MEM0_SEMANTIC_SEARCH_TOP_K` | `20` | `utils/settings.py:84` |
|
||||
| 连接池大小 | `MEM0_POOL_SIZE` (env) | `50` | `utils/settings.py:61` |
|
||||
|
||||
开启路径:V1 走请求体 `enable_memory`,V2 走 bot 配置 `enable_memory`;两者都受全局 `MEM0_ENABLED` 限制。
|
||||
中间件注册在 `agent/deep_assistant.py:270`(`if config.enable_memori:`)。
|
||||
|
||||
## 核心文件
|
||||
- `agent/mem0_manager.py` — Mem0 客户端管理器:实例创建/LRU 缓存(最多 50)、连接池管理、`recall_memories` / `add_memory` / `delete_all`、多租户隔离、`CustomMem0Embedding`、`json_repair` 补丁
|
||||
- `agent/mem0_middleware.py` — 中间件:`before_agent` 召回并写入 `config._mem0_context`(行 114/155);`after_agent` 后台异步提取存储
|
||||
- `agent/mem0_config.py` — Mem0 配置类:user/agent/session id、记忆提示模板、自定义提取 prompt 加载(`PreMemoryPrompt` hook)
|
||||
- `routes/memory.py` — 内存管理 API(GET/POST/DELETE,供前端管理用户记忆)
|
||||
- `drop_mem0_tables.py` — 清理脚本,删除所有 `mem0_*` 表(重置/清脏数据)
|
||||
|
||||
## 数据流
|
||||
**写入**:User+Assistant 消息 → `after_agent`(后台线程)→ `add_memory` → `Mem0.add()`(LLM 提取事实)→ pgvector 向量化存入 `mem0_{agent_id}`。
|
||||
**读取**:User query → `before_agent` → `recall_memories` → `Mem0.search()`(向量相似 top_k)→ 格式化后写入 `config._mem0_context` → 注入 system prompt(也供思考功能 [[../thinking/MEMORY|thinking]] 使用)。
|
||||
|
||||
## 关键设计决策
|
||||
- 复用项目已加载的 embedding 模型(`CustomMem0Embedding`),避免 Mem0 重复加载 SentenceTransformer → `decisions/2026-06-custom-embedding.md`
|
||||
- 连接池主动释放 + LRU 缓存实例,防连接池耗尽 → `decisions/2026-06-connection-pool.md`
|
||||
|
||||
## Gotchas(开发必读)
|
||||
- **命名陷阱**:配置叫 `enable_memori`(无 y),API 叫 `enable_memory`,内部实现是 Mem0,三个名字别混。
|
||||
- **连接池耗尽**:Mem0 PGVector `__init__` 取连接、`__del__` 释放;必须在每次操作后主动 `_release_connection()`,否则高并发会打满 `MEM0_POOL_SIZE`。
|
||||
- **JSON 脆弱**:LLM 提取事实返回的 JSON 常有尾逗号/单引号,已 monkey patch 成 `json_repair.loads`,不要改回原生解析。
|
||||
- **表膨胀**:每个 `agent_id` 一张表,多 bot 长期运行会产生大量表,定期用 `drop_mem0_tables.py` 清理。
|
||||
- **Embedding 维度**:`paraphrase-multilingual-MiniLM-L12-v2`,384 维;换模型需同步 pgvector 列维度,否则写入报错。
|
||||
|
||||
## 索引
|
||||
- 设计决策:`decisions/`
|
||||
- 变更历史:`changelog/`
|
||||
- 相关文档:`docs/`
|
||||
6
.features/memory/changelog/2026-Q2.md
Normal file
6
.features/memory/changelog/2026-Q2.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Changelog 2026 Q2 — Memory
|
||||
|
||||
## 2026-06-01
|
||||
- 初始化 feature memory 文档。
|
||||
- 记录现状:Mem0 + pgvector 长期记忆,`before_agent` 召回注入 / `after_agent` 后台提取存储。
|
||||
- 归档设计决策:自定义 embedding 复用(custom-embedding)、连接池主动释放 + LRU(connection-pool)。
|
||||
25
.features/memory/decisions/2026-06-connection-pool.md
Normal file
25
.features/memory/decisions/2026-06-connection-pool.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
date: "2026-06-01"
|
||||
status: adopted
|
||||
topic: "connection-pool"
|
||||
impact: [memory, performance, stability]
|
||||
---
|
||||
|
||||
# 连接池主动释放 + Mem0 实例 LRU 缓存
|
||||
|
||||
## 背景
|
||||
Mem0 的 PGVector 后端在实例 `__init__` 时从连接池取一个连接,理论上在 `__del__` 时归还。
|
||||
但 Python GC 时机不确定,高并发下连接迟迟不归还会迅速打满 `MEM0_POOL_SIZE`(默认 50),导致后续请求阻塞。
|
||||
同时若为每个 `(user_id, agent_id)` 都新建 Mem0 实例且不回收,也会无限占用连接。
|
||||
|
||||
## 决策
|
||||
1. `Mem0Manager` 用 `OrderedDict` 维护最多 50 个 Mem0 实例的 LRU 缓存,超出淘汰最旧的。
|
||||
2. 每次记忆操作(recall/add)后调用 `_release_connection()` 立即把连接归还连接池,不等 GC。
|
||||
|
||||
## 影响
|
||||
- 连接池不再被慢 GC 拖垮,高并发稳定。
|
||||
- 实例数量有上界,内存可控。
|
||||
|
||||
## Gotchas
|
||||
- 不要在操作链路里持有 Mem0 实例的连接跨多个 await,会绕过释放逻辑。
|
||||
- LRU 上限(50)与 `MEM0_POOL_SIZE`(50)相关联,调整其一时需一并评估。
|
||||
22
.features/memory/decisions/2026-06-custom-embedding.md
Normal file
22
.features/memory/decisions/2026-06-custom-embedding.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
date: "2026-06-01"
|
||||
status: adopted
|
||||
topic: "custom-embedding"
|
||||
impact: [memory, performance]
|
||||
---
|
||||
|
||||
# 复用项目 embedding 模型而非 Mem0 自带 SentenceTransformer
|
||||
|
||||
## 背景
|
||||
Mem0 默认会自行加载一个 SentenceTransformer 做 embedding。项目本身已经通过 `GlobalModelManager`
|
||||
加载了 `paraphrase-multilingual-MiniLM-L12-v2`(384 维)。若放任 Mem0 自加载,会出现同一模型在内存中加载两份,浪费显存/内存。
|
||||
|
||||
## 决策
|
||||
在 `agent/mem0_manager.py` 实现 `CustomMem0Embedding`,把 Mem0 的 embedder 接到项目已加载的全局模型上,复用同一份权重。
|
||||
|
||||
## 影响
|
||||
- 内存占用显著下降(不重复加载模型)。
|
||||
- embedding 维度固定为 384,与项目主模型一致;换模型时 pgvector 列维度必须同步调整。
|
||||
|
||||
## 备注
|
||||
相关连接池/实例缓存策略见 [[2026-06-connection-pool]]。
|
||||
0
.features/memory/docs/.gitkeep
Normal file
0
.features/memory/docs/.gitkeep
Normal file
52
.features/thinking/MEMORY.md
Normal file
52
.features/thinking/MEMORY.md
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
feature: "thinking"
|
||||
scope: "Agent 思考功能(基于 GuidelineMiddleware 的前置辅助推理),在主回答前生成一次 <think> 内容"
|
||||
updated_at: "2026-06-01"
|
||||
status: active
|
||||
---
|
||||
|
||||
# Thinking(思考功能)
|
||||
|
||||
## 当前状态
|
||||
思考功能通过自定义的 **`GuidelineMiddleware`** 实现:在主 agent 执行前,先用业务指引 prompt 调一次模型做"思考",
|
||||
把结果包成 `<think>...</think>` 标签并打上 `message_tag: "THINK"` 元数据,供前端识别/折叠展示。
|
||||
|
||||
> 重要:这是"主请求前的一次辅助请求",**不是** Qwen 模型内置的 reasoning/extended-thinking 模式,因此与具体模型无关,任何 LLM 都能用。对标 OpenAI o1 / Claude thinking,但实现更轻。
|
||||
|
||||
## 配置开关
|
||||
| 层级 | 字段 | 默认 | 位置 |
|
||||
|------|------|------|------|
|
||||
| Agent 配置 | `enable_thinking: bool` | `False` | `agent/agent_config.py:26` |
|
||||
| API 请求 | `enable_thinking: bool` | `False` | `utils/api_models.py:54` |
|
||||
|
||||
开启路径:V1 走请求体 `enable_thinking`,V2 走 bot 配置 `enable_thinking`。
|
||||
中间件注册在 `agent/deep_assistant.py:294`:`if config.enable_thinking: middleware.append(GuidelineMiddleware(...))`。
|
||||
|
||||
## 核心文件
|
||||
- `agent/guideline_middleware.py` — 思考主逻辑。`get_guideline_prompt`(行 53+)组装指引 prompt;`before_agent`/`abefore_agent` 调模型生成思考,包 `<think>` 标签并标 `THINK`(行 120-124 / 146-149)。
|
||||
- `agent/deep_assistant.py:294-295` — 按 `enable_thinking` 注册中间件。
|
||||
|
||||
## 数据流
|
||||
1. `before_agent` 加载指引(system prompt 中的 Guidelines 块)。
|
||||
2. 从 system prompt 提取 guidelines / tool_description / scenarios / terms_list。
|
||||
3. 组装 `guideline_prompt` = 业务规则 + 聊天历史 + **记忆上下文** + 工具描述 + 场景 + 术语分析。
|
||||
4. 调模型一次:`SystemMessage(guideline_prompt)` + 用户最后一条消息 → 得到思考内容。
|
||||
5. 内容包成 `<think>...</think>`,`additional_kwargs["message_tag"] = "THINK"`。
|
||||
6. 追加一条空 `HumanMessage`(兼容"最后必须是 user 消息"的模型)。
|
||||
7. 主 agent 继续执行,产出正式回答。
|
||||
|
||||
## 与记忆功能的耦合
|
||||
`guideline_middleware.py:63` 读取 `config._mem0_context`(由 [[../memory/MEMORY|memory]] 的 `before_agent` 写入)。
|
||||
即:思考阶段会把已召回的长期记忆纳入指引 prompt,从而基于记忆做更好的分析。
|
||||
**顺序依赖**:memory 中间件需在 thinking 之前执行,`_mem0_context` 才有值。
|
||||
|
||||
## Gotchas(开发必读)
|
||||
- **思考是非流式的**:思考内容在 `before_agent` 一次性完整生成,只有正式回答才流式输出。前端靠 `<think>` 标签 + `message_tag:"THINK"` 折叠展示。
|
||||
- **额外一次模型调用**:每次开启都多打一次 LLM 请求,增加延迟和成本,按场景权衡。
|
||||
- **不是模型原生 reasoning**:别误以为依赖 `enable_thinking` 透传给 Qwen,它是中间件层的自定义实现。
|
||||
- **空 HumanMessage 收尾**:思考消息后会补一条空 user 消息,改消息列表处理逻辑时勿误删。
|
||||
- **依赖记忆上下文顺序**:若调整中间件注册顺序,确认 memory 仍在 thinking 之前。
|
||||
|
||||
## 索引
|
||||
- 设计决策:`decisions/`
|
||||
- 变更历史:`changelog/`
|
||||
7
.features/thinking/changelog/2026-Q2.md
Normal file
7
.features/thinking/changelog/2026-Q2.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Changelog 2026 Q2 — Thinking
|
||||
|
||||
## 2026-06-01
|
||||
- 初始化 feature memory 文档。
|
||||
- 记录现状:`GuidelineMiddleware` 在 `before_agent` 生成 `<think>` 思考内容,标 `message_tag:"THINK"`。
|
||||
- 归档设计决策:用中间件实现而非模型原生 reasoning(middleware-thinking)。
|
||||
- 记录与 memory 功能的顺序耦合(依赖 `_mem0_context`)。
|
||||
@ -0,0 +1,28 @@
|
||||
---
|
||||
date: "2026-06-01"
|
||||
status: adopted
|
||||
topic: "middleware-thinking"
|
||||
impact: [thinking, model-compat]
|
||||
---
|
||||
|
||||
# 用中间件实现思考,而非依赖模型原生 reasoning
|
||||
|
||||
## 背景
|
||||
"思考功能"可以有两种实现:
|
||||
A. 透传 `enable_thinking` 给底层模型,依赖模型自带的 reasoning/extended-thinking 能力。
|
||||
B. 在主请求前自己加一次"指引思考"的辅助 LLM 调用。
|
||||
|
||||
模型 A 路线要求底层模型支持原生 reasoning,且不同模型行为/输出格式不一致,难以统一前端处理。
|
||||
|
||||
## 决策
|
||||
采用 B:实现 `GuidelineMiddleware`,在 `before_agent` 阶段用业务指引 prompt 调一次模型生成思考,
|
||||
统一包成 `<think>...</think>` + `message_tag:"THINK"`。
|
||||
|
||||
## 影响
|
||||
- 与具体模型解耦,任何 LLM(OpenAI/Claude/Qwen)都能用。
|
||||
- 思考阶段可注入业务规则、工具描述、术语分析、记忆上下文,可控性强。
|
||||
- 代价:每次多一次 LLM 调用(延迟 + 成本);思考内容非流式。
|
||||
|
||||
## Gotchas
|
||||
- 思考依赖 `config._mem0_context`,需保证 memory 中间件先于本中间件执行。
|
||||
- 思考后补空 `HumanMessage` 以兼容"末条须为 user"的模型,勿删。
|
||||
Loading…
Reference in New Issue
Block a user