iflow-mcp_hanw39_reasoning-bank-mcp 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. iflow_mcp_hanw39_reasoning_bank_mcp-0.2.0.dist-info/METADATA +599 -0
  2. iflow_mcp_hanw39_reasoning_bank_mcp-0.2.0.dist-info/RECORD +55 -0
  3. iflow_mcp_hanw39_reasoning_bank_mcp-0.2.0.dist-info/WHEEL +4 -0
  4. iflow_mcp_hanw39_reasoning_bank_mcp-0.2.0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_hanw39_reasoning_bank_mcp-0.2.0.dist-info/licenses/LICENSE +21 -0
  6. src/__init__.py +16 -0
  7. src/__main__.py +6 -0
  8. src/config.py +266 -0
  9. src/deduplication/__init__.py +19 -0
  10. src/deduplication/base.py +88 -0
  11. src/deduplication/factory.py +60 -0
  12. src/deduplication/strategies/__init__.py +1 -0
  13. src/deduplication/strategies/semantic_dedup.py +187 -0
  14. src/default_config.yaml +121 -0
  15. src/initializers/__init__.py +50 -0
  16. src/initializers/base.py +196 -0
  17. src/initializers/embedding_initializer.py +22 -0
  18. src/initializers/llm_initializer.py +22 -0
  19. src/initializers/memory_manager_initializer.py +55 -0
  20. src/initializers/retrieval_initializer.py +32 -0
  21. src/initializers/storage_initializer.py +22 -0
  22. src/initializers/tools_initializer.py +48 -0
  23. src/llm/__init__.py +10 -0
  24. src/llm/base.py +61 -0
  25. src/llm/factory.py +75 -0
  26. src/llm/providers/__init__.py +12 -0
  27. src/llm/providers/anthropic.py +62 -0
  28. src/llm/providers/dashscope.py +76 -0
  29. src/llm/providers/openai.py +76 -0
  30. src/merge/__init__.py +22 -0
  31. src/merge/base.py +89 -0
  32. src/merge/factory.py +60 -0
  33. src/merge/strategies/__init__.py +1 -0
  34. src/merge/strategies/llm_merge.py +170 -0
  35. src/merge/strategies/voting_merge.py +108 -0
  36. src/prompts/__init__.py +21 -0
  37. src/prompts/formatters.py +74 -0
  38. src/prompts/templates.py +184 -0
  39. src/retrieval/__init__.py +8 -0
  40. src/retrieval/base.py +37 -0
  41. src/retrieval/factory.py +55 -0
  42. src/retrieval/strategies/__init__.py +8 -0
  43. src/retrieval/strategies/cosine_retrieval.py +47 -0
  44. src/retrieval/strategies/hybrid_retrieval.py +155 -0
  45. src/server.py +306 -0
  46. src/services/__init__.py +5 -0
  47. src/services/memory_manager.py +403 -0
  48. src/storage/__init__.py +45 -0
  49. src/storage/backends/json_backend.py +290 -0
  50. src/storage/base.py +150 -0
  51. src/tools/__init__.py +8 -0
  52. src/tools/extract_memory.py +285 -0
  53. src/tools/retrieve_memory.py +139 -0
  54. src/utils/__init__.py +7 -0
  55. src/utils/similarity.py +54 -0
@@ -0,0 +1,108 @@
1
+ """
2
+ Voting-based Merge Strategy
3
+
4
+ Selects the "best" memory from a group and removes the rest.
5
+ Does not create a new merged memory, just chooses a representative.
6
+ """
7
+
8
+ from typing import List, Dict, Any, Optional
9
+ from ..base import MergeStrategy
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class VotingMergeStrategy(MergeStrategy):
16
+ """
17
+ Voting merge: Select best memory, remove others.
18
+
19
+ Selection criteria (in priority order):
20
+ 1. Highest retrieval_count (most used)
21
+ 2. Success=true preferred over false
22
+ 3. Most recent timestamp
23
+ """
24
+
25
+ @property
26
+ def name(self) -> str:
27
+ return "voting"
28
+
29
+ def __init__(self, config: Dict[str, Any]):
30
+ super().__init__(config)
31
+ self.min_group_size = config.get("voting", {}).get("min_group_size", 2)
32
+
33
+ async def should_merge(
34
+ self,
35
+ memories: List[Dict[str, Any]],
36
+ agent_id: Optional[str] = None
37
+ ) -> bool:
38
+ """
39
+ Check if group meets minimum size requirement.
40
+ Also validates all memories belong to the same agent.
41
+ """
42
+ if len(memories) < self.min_group_size:
43
+ return False
44
+
45
+ # Validate all memories have same agent_id
46
+ if agent_id:
47
+ for mem in memories:
48
+ if mem.get("agent_id") != agent_id:
49
+ logger.warning(
50
+ f"Memory {mem.get('memory_id')} has different agent_id: "
51
+ f"{mem.get('agent_id')} != {agent_id}"
52
+ )
53
+ return False
54
+
55
+ return True
56
+
57
+ async def merge(
58
+ self,
59
+ memories: List[Dict[str, Any]],
60
+ agent_id: Optional[str] = None
61
+ ) -> Dict[str, Any]:
62
+ """
63
+ Select the "best" memory from the group.
64
+
65
+ Returns the selected memory with added merge metadata.
66
+ """
67
+ if not memories:
68
+ raise ValueError("Cannot merge empty memory list")
69
+
70
+ # Validate agent_id consistency
71
+ if agent_id:
72
+ for mem in memories:
73
+ if mem.get("agent_id") != agent_id:
74
+ raise ValueError(
75
+ f"Memory {mem.get('memory_id')} belongs to different agent"
76
+ )
77
+
78
+ # Sort by: retrieval_count (desc), success (desc), timestamp (desc)
79
+ # todo 是否不公平,对于新经验
80
+ def sort_key(mem):
81
+ return (
82
+ mem.get("retrieval_count", 0), # Higher is better
83
+ 1 if mem.get("success", False) else 0, # Success preferred
84
+ mem.get("timestamp", "") # More recent preferred
85
+ )
86
+
87
+ sorted_memories = sorted(memories, key=sort_key, reverse=True)
88
+ best_memory = sorted_memories[0]
89
+
90
+ logger.info(
91
+ f"Selected best memory: {best_memory.get('memory_id')} "
92
+ f"from group of {len(memories)} (agent_id={agent_id})"
93
+ )
94
+
95
+ # Return the best memory with merge metadata
96
+ merged_from = [ m["memory_id"] for m in memories if m["memory_id"] != best_memory["memory_id"] ]
97
+
98
+ return {
99
+ **best_memory,
100
+ "is_merged": True,
101
+ "merged_from": merged_from,
102
+ "merge_metadata": {
103
+ "merge_strategy": self.name,
104
+ "original_count": len(memories),
105
+ "selection_reason": "highest_usage",
106
+ "abstraction_level": 0 # Same level, just selected best
107
+ }
108
+ }
@@ -0,0 +1,21 @@
1
+ """提示词模块"""
2
+ from .templates import (
3
+ EXTRACT_SUCCESS_PROMPT,
4
+ EXTRACT_FAILURE_PROMPT,
5
+ JUDGE_TRAJECTORY_PROMPT,
6
+ get_extract_prompt,
7
+ get_judge_prompt,
8
+ get_merge_prompt,
9
+ )
10
+ from .formatters import format_trajectory, format_memory_for_prompt
11
+
12
+ __all__ = [
13
+ "EXTRACT_SUCCESS_PROMPT",
14
+ "EXTRACT_FAILURE_PROMPT",
15
+ "JUDGE_TRAJECTORY_PROMPT",
16
+ "get_extract_prompt",
17
+ "get_judge_prompt",
18
+ "get_merge_prompt",
19
+ "format_trajectory",
20
+ "format_memory_for_prompt",
21
+ ]
@@ -0,0 +1,74 @@
1
+ """轨迹格式化工具"""
2
+ from typing import List, Dict
3
+
4
+
5
+ def format_trajectory(trajectory: List[Dict]) -> str:
6
+ """
7
+ 将轨迹列表格式化为可读的文本
8
+
9
+ Args:
10
+ trajectory: 轨迹步骤列表,每个步骤包含:
11
+ - step: 步骤序号
12
+ - role: 角色 ("user" | "assistant" | "tool")
13
+ - content: 具体内容
14
+ - metadata: 额外信息(可选)
15
+
16
+ Returns:
17
+ 格式化的轨迹文本
18
+ """
19
+ if not trajectory:
20
+ return "(空轨迹)"
21
+
22
+ lines = []
23
+
24
+ for step_data in trajectory:
25
+ step_num = step_data.get("step", "?")
26
+ role = step_data.get("role", "unknown")
27
+ content = step_data.get("content", "")
28
+ metadata = step_data.get("metadata", {})
29
+
30
+ # 角色标签
31
+ role_label = {
32
+ "user": "User",
33
+ "assistant": "Assistant",
34
+ "tool": "Tool"
35
+ }.get(role, role.capitalize())
36
+
37
+ # 如果是 tool 角色且有工具名称
38
+ if role == "tool" and "tool_name" in metadata:
39
+ tool_name = metadata["tool_name"]
40
+ # todo action_type?
41
+ action_type = metadata.get("action_type", "")
42
+ if action_type:
43
+ role_label = f"Tool - {tool_name} ({action_type})"
44
+ else:
45
+ role_label = f"Tool - {tool_name}"
46
+
47
+ # 格式化步骤
48
+ line = f"步骤 {step_num} [{role_label}]: {content}"
49
+ lines.append(line)
50
+
51
+ return "\n".join(lines)
52
+
53
+
54
+ def format_memory_for_prompt(memories: List[Dict]) -> str:
55
+ """
56
+ 将检索到的记忆格式化为可直接用于 LLM 提示的文本
57
+
58
+ Args:
59
+ memories: 记忆项列表
60
+
61
+ Returns:
62
+ 格式化的提示文本
63
+ """
64
+ if not memories:
65
+ return ""
66
+
67
+ prompt = "以下是我从过去与环境的交互中积累的一些记忆项,可能有助于解决任务。当您觉得它们相关时可以使用它们。\n\n"
68
+
69
+ for i, mem in enumerate(memories, 1):
70
+ status = "✓ 成功经验" if mem.get("success", True) else "✗ 失败教训"
71
+ prompt += f"**记忆 {i} [{status}] - {mem['title']}**\n"
72
+ prompt += f"{mem['content']}\n\n"
73
+
74
+ return prompt.strip()
@@ -0,0 +1,184 @@
1
+ """提示词模板"""
2
+
3
+
4
+ # 成功轨迹提取提示词
5
+ EXTRACT_SUCCESS_PROMPT = """你是一个专业的AI经验总结专家。请分析以下成功完成的任务轨迹,并提取可复用的推理策略。
6
+
7
+ **任务查询:**
8
+ {query}
9
+
10
+ **成功的轨迹:**
11
+ {trajectory}
12
+
13
+ **分析目标:**
14
+ 1. 成功原因分析:解释该轨迹中关键的推理路径、决策点或信息利用方式,说明为何任务能被成功完成。
15
+ 2. 抽象可复用策略:从成功行为中提炼出可迁移的思维模式或操作步骤,而非表层动作。
16
+ 3. 形成记忆项(3条以内):每条代表一种通用策略或方法论,适合未来相似任务的快速复用。
17
+
18
+ **输出格式(JSON):**
19
+ ```json
20
+ {{
21
+ "memories": [
22
+ {{
23
+ "title": "策略标题(5-10字)",
24
+ "description": "一句话说明策略适用的典型场景",
25
+ "content": "详细说明策略的逻辑结构、关键判断点和可执行步骤"
26
+ }}
27
+ ]
28
+ }}
29
+ ```
30
+
31
+ **注意事项:**
32
+ - 聚焦于“如何思考”而不是“做了什么”
33
+ - 优先提取体现分解问题、假设验证、信息整合、动态调整等高层推理能力的内容
34
+ - 策略要具备跨任务适用性,避免与具体工具、网站或数据源绑定
35
+ - 输出应简洁、概念清晰、结构稳定,方便后续自动化学习或知识库吸收
36
+ - 避免冗余,每个记忆项应该关注不同的方面
37
+
38
+ 请按照上述格式输出,只输出JSON,不要包含其他内容。
39
+ """
40
+
41
+
42
+ # 失败轨迹提取提示词
43
+ EXTRACT_FAILURE_PROMPT = """你是一个专业的AI经验总结专家。请分析以下失败的任务轨迹,并提取教训和预防策略。
44
+
45
+ **任务查询:**
46
+ {query}
47
+
48
+ **失败的轨迹:**
49
+ {trajectory}
50
+
51
+ **要求:**
52
+ 1. 反思这个轨迹为何失败
53
+ 2. 识别导致失败的关键错误或陷阱
54
+ 3. 提取最多3个记忆项(教训),每个记忆项包含:
55
+ - **标题**:简短描述教训(5-10个字)
56
+ - **描述**:一句话说明这个错误的常见场景
57
+ - **内容**:详细说明错误原因、后果,以及如何避免
58
+
59
+ **注意事项:**
60
+ - 提取的教训应该具有警示作用,帮助避免类似错误
61
+ - 避免冗余,每个记忆项应该关注不同的失败原因
62
+ - 内容要包含"不要做X,应该做Y"的明确指导
63
+
64
+ **输出格式(JSON):**
65
+ ```json
66
+ {{
67
+ "memories": [
68
+ {{
69
+ "title": "教训标题",
70
+ "description": "错误场景描述",
71
+ "content": "详细的错误分析和避免方法"
72
+ }}
73
+ ]
74
+ }}
75
+ ```
76
+
77
+ 请按照上述格式输出,只输出JSON,不要包含其他内容。
78
+ """
79
+
80
+
81
+ # 轨迹判断提示词
82
+ JUDGE_TRAJECTORY_PROMPT = """你是一个专业的任务评估专家。请判断以下任务执行是否成功。
83
+
84
+ **任务查询:**
85
+ {query}
86
+
87
+ **执行轨迹:**
88
+ {trajectory}
89
+
90
+ **判断标准:**
91
+ - 是否完成了任务查询中要求的目标
92
+ - 最终结果是否准确、完整
93
+ - 执行过程是否达到了预期状态
94
+
95
+ **要求:**
96
+ 请仔细分析轨迹,判断任务是"成功"还是"失败",并给出简短的理由。
97
+
98
+ **输出格式(JSON):**
99
+ ```json
100
+ {{
101
+ "result": "success", // "success" 或 "failure"
102
+ "reason": "简短的判断理由(1-2句话)"
103
+ }}
104
+ ```
105
+
106
+ 请按照上述格式输出,只输出JSON,不要包含其他内容。
107
+ """
108
+
109
+
110
+ # 记忆合并提示词
111
+ MEMORY_MERGE_PROMPT = """你是一个经验提炼专家。以下是 {len(memories)} 条相似的经验,它们来自同一个AI Agent在不同任务中积累的知识。
112
+
113
+ {memories_text}
114
+
115
+ 请分析这些经验的**共同模式**,提炼出一条更通用、更深层的经验。
116
+
117
+ 要求:
118
+ 1. **title**: 5-15字的简洁标题,概括核心策略
119
+ 2. **description**: 一句话(20-40字)概括适用场景
120
+ 3. **content**: 详细描述通用策略(200-500字),包括:
121
+ - 这个策略解决什么问题
122
+ - 为什么这样做
123
+ - 如何应用到新场景
124
+ - 需要注意的事项
125
+ 4. **query**: 提炼出的通用场景描述(可以是"<通用场景:xxx>"格式)
126
+ 5. **abstraction_level**: 抽象层级
127
+ - 0 = 具体案例(特定问题的解决方案)
128
+ - 1 = 模式识别(一类问题的通用方法)
129
+ - 2 = 原则层面(跨领域的指导原则)
130
+
131
+ 请以JSON格式返回,只返回JSON,不要其他内容:
132
+ ```json
133
+ {{
134
+ "title": "...",
135
+ "description": "...",
136
+ "content": "...",
137
+ "query": "...",
138
+ "abstraction_level": 1
139
+ }}
140
+ ```
141
+ """
142
+
143
+
144
+ def get_extract_prompt(query: str, trajectory: str, success: bool) -> str:
145
+ """
146
+ 获取记忆提取提示词
147
+
148
+ Args:
149
+ query: 任务查询
150
+ trajectory: 格式化的轨迹文本
151
+ success: 是否成功
152
+
153
+ Returns:
154
+ 完整的提示词
155
+ """
156
+ template = EXTRACT_SUCCESS_PROMPT if success else EXTRACT_FAILURE_PROMPT
157
+ return template.format(query=query, trajectory=trajectory)
158
+
159
+
160
+ def get_judge_prompt(query: str, trajectory: str) -> str:
161
+ """
162
+ 获取轨迹判断提示词
163
+
164
+ Args:
165
+ query: 任务查询
166
+ trajectory: 格式化的轨迹文本
167
+
168
+ Returns:
169
+ 完整的提示词
170
+ """
171
+ return JUDGE_TRAJECTORY_PROMPT.format(query=query, trajectory=trajectory)
172
+
173
+
174
+ def get_merge_prompt(memories_text: str) -> str:
175
+ """
176
+ 获取记忆合并提示词
177
+
178
+ Args:
179
+ memories_text: 需要合并的记忆项
180
+
181
+ Returns:
182
+ 完整的提示词
183
+ """
184
+ return MEMORY_MERGE_PROMPT.format(memories_text=memories_text)
@@ -0,0 +1,8 @@
1
+ """检索模块"""
2
+ from .base import RetrievalStrategy
3
+ from .factory import RetrievalFactory
4
+
5
+ __all__ = [
6
+ "RetrievalStrategy",
7
+ "RetrievalFactory",
8
+ ]
src/retrieval/base.py ADDED
@@ -0,0 +1,37 @@
1
+ """检索策略抽象基类"""
2
+ from abc import ABC, abstractmethod
3
+ from typing import List, Tuple, Dict
4
+ import numpy as np
5
+
6
+
7
+ class RetrievalStrategy(ABC):
8
+ """检索策略抽象基类"""
9
+
10
+ @abstractmethod
11
+ async def retrieve(
12
+ self,
13
+ query: str,
14
+ query_embedding: np.ndarray,
15
+ storage_backend: 'StorageBackend',
16
+ top_k: int = 1,
17
+ **kwargs
18
+ ) -> List[Tuple[str, float]]:
19
+ """
20
+ 检索相关记忆
21
+
22
+ Args:
23
+ query: 查询文本
24
+ query_embedding: 查询的嵌入向量
25
+ storage_backend: 存储后端实例
26
+ top_k: 返回的记忆数量
27
+ **kwargs: 其他参数
28
+
29
+ Returns:
30
+ [(memory_id, score), ...] 按分数降序排列
31
+ """
32
+ pass
33
+
34
+ @abstractmethod
35
+ def get_name(self) -> str:
36
+ """返回策略名称"""
37
+ pass
@@ -0,0 +1,55 @@
1
+ """检索策略工厂"""
2
+ from typing import Dict
3
+ from .base import RetrievalStrategy
4
+ from .strategies import CosineRetrievalStrategy, HybridRetrievalStrategy
5
+
6
+
7
+ class RetrievalFactory:
8
+ """检索策略工厂"""
9
+
10
+ _strategies = {
11
+ "cosine": CosineRetrievalStrategy,
12
+ "hybrid": HybridRetrievalStrategy,
13
+ }
14
+
15
+ @classmethod
16
+ def create(cls, strategy_name: str, config: Dict = None) -> RetrievalStrategy:
17
+ """
18
+ 创建检索策略实例
19
+
20
+ Args:
21
+ strategy_name: 策略名称 ("cosine" | "hybrid")
22
+ config: 策略配置参数
23
+
24
+ Returns:
25
+ RetrievalStrategy 实例
26
+ """
27
+ if strategy_name not in cls._strategies:
28
+ raise ValueError(
29
+ f"Unknown retrieval strategy: {strategy_name}. "
30
+ f"Available strategies: {list(cls._strategies.keys())}"
31
+ )
32
+
33
+ strategy_class = cls._strategies[strategy_name]
34
+
35
+ # 根据策略类型传递配置
36
+ if strategy_name == "hybrid":
37
+ return strategy_class(config)
38
+ else:
39
+ return strategy_class()
40
+
41
+ @classmethod
42
+ def register_strategy(cls, name: str, strategy_class: type):
43
+ """
44
+ 注册新的检索策略(插件机制)
45
+
46
+ Args:
47
+ name: 策略名称
48
+ strategy_class: 策略类
49
+ """
50
+ cls._strategies[name] = strategy_class
51
+
52
+ @classmethod
53
+ def list_strategies(cls) -> list:
54
+ """返回所有可用的策略名称"""
55
+ return list(cls._strategies.keys())
@@ -0,0 +1,8 @@
1
+ """检索策略包"""
2
+ from .cosine_retrieval import CosineRetrievalStrategy
3
+ from .hybrid_retrieval import HybridRetrievalStrategy
4
+
5
+ __all__ = [
6
+ "CosineRetrievalStrategy",
7
+ "HybridRetrievalStrategy",
8
+ ]
@@ -0,0 +1,47 @@
1
+ """余弦相似度检索策略(论文基线方法)"""
2
+ from typing import List, Tuple
3
+ import numpy as np
4
+ from ..base import RetrievalStrategy
5
+ from ...utils.similarity import cosine_similarity
6
+
7
+
8
+ class CosineRetrievalStrategy(RetrievalStrategy):
9
+ """纯余弦相似度检索策略"""
10
+
11
+ async def retrieve(
12
+ self,
13
+ query: str,
14
+ query_embedding: np.ndarray,
15
+ storage_backend,
16
+ top_k: int = 1,
17
+ agent_id: str = None,
18
+ **kwargs
19
+ ) -> List[Tuple[str, float]]:
20
+ """
21
+ 使用余弦相似度检索记忆
22
+
23
+ 这是论文中使用的基线方法
24
+
25
+ Args:
26
+ agent_id: Agent ID,用于过滤记忆
27
+ """
28
+ # 获取所有记忆的嵌入(支持 agent_id 过滤)
29
+ memory_embeddings = await storage_backend.get_all_embeddings(agent_id)
30
+
31
+ if not memory_embeddings:
32
+ return []
33
+
34
+ # 计算相似度
35
+ similarities = []
36
+ for memory_id, memory_vec in memory_embeddings.items():
37
+ score = cosine_similarity(query_embedding, memory_vec)
38
+ similarities.append((memory_id, float(score)))
39
+
40
+ # 按分数降序排序
41
+ similarities.sort(key=lambda x: x[1], reverse=True)
42
+
43
+ # 返回 Top-K
44
+ return similarities[:top_k]
45
+
46
+ def get_name(self) -> str:
47
+ return "cosine"
@@ -0,0 +1,155 @@
1
+ """混合评分检索策略"""
2
+ from typing import List, Tuple, Dict
3
+ import numpy as np
4
+ from datetime import datetime, timezone
5
+ from ..base import RetrievalStrategy
6
+ from ...utils.similarity import cosine_similarity
7
+
8
+
9
+ class HybridRetrievalStrategy(RetrievalStrategy):
10
+ """
11
+ 混合评分检索策略
12
+
13
+ 综合考虑多个因素:
14
+ - 语义相似度(余弦相似度)
15
+ - 记忆置信度(基于检索次数)
16
+ - 成功/失败偏好
17
+ - 时间衰减
18
+ """
19
+
20
+ def __init__(self, config: Dict = None):
21
+ """
22
+ 初始化混合检索策略
23
+
24
+ Args:
25
+ config: 配置字典,包含权重和衰减参数
26
+ """
27
+ default_weights = {
28
+ "semantic": 0.6,
29
+ "confidence": 0.2,
30
+ "success": 0.15,
31
+ "recency": 0.05
32
+ }
33
+
34
+ if config:
35
+ self.weights = config.get("weights", default_weights)
36
+ self.time_decay_halflife = config.get("time_decay_halflife", 30)
37
+ else:
38
+ self.weights = default_weights
39
+ self.time_decay_halflife = 30
40
+
41
+ def _compute_time_decay(self, created_at: str) -> float:
42
+ """
43
+ 计算时间衰减因子
44
+
45
+ 使用指数衰减: decay = exp(-λ * t)
46
+ 其中 λ = ln(2) / halflife
47
+
48
+ Args:
49
+ created_at: ISO 8601 时间戳
50
+
51
+ Returns:
52
+ 衰减因子 [0, 1],越新越接近 1
53
+ """
54
+ try:
55
+ created_time = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
56
+ current_time = datetime.now(timezone.utc)
57
+ days_elapsed = (current_time - created_time).days
58
+
59
+ # 指数衰减
60
+ lambda_decay = np.log(2) / self.time_decay_halflife
61
+ decay = np.exp(-lambda_decay * days_elapsed)
62
+
63
+ return float(decay)
64
+ except Exception:
65
+ # 如果时间解析失败,返回默认值
66
+ return 1.0
67
+
68
+ def _compute_confidence_score(self, memory: Dict) -> float:
69
+ """
70
+ 计算记忆置信度
71
+
72
+ 基于检索次数,使用对数缩放防止过度偏向高频记忆
73
+
74
+ Args:
75
+ memory: 记忆项字典
76
+
77
+ Returns:
78
+ 置信度 [0.5, 1.0]
79
+ """
80
+ retrieval_count = memory.get("retrieval_count", 0)
81
+
82
+ # 使用 tanh 进行平滑映射
83
+ # confidence ∈ [0.5, 1.0]
84
+ confidence = 0.5 + 0.5 * np.tanh(retrieval_count / 10)
85
+
86
+ return float(confidence)
87
+
88
+ async def retrieve(
89
+ self,
90
+ query: str,
91
+ query_embedding: np.ndarray,
92
+ storage_backend,
93
+ top_k: int = 1,
94
+ agent_id: str = None,
95
+ **kwargs
96
+ ) -> List[Tuple[str, float]]:
97
+ """
98
+ 使用混合评分检索记忆
99
+
100
+ score = w1*semantic + w2*confidence + w3*success - w4*(1-recency)
101
+
102
+ Args:
103
+ agent_id: Agent ID,用于过滤记忆
104
+ """
105
+ # 获取所有记忆(支持 agent_id 过滤)
106
+ memory_embeddings = await storage_backend.get_all_embeddings(agent_id)
107
+ memories = await storage_backend.get_all_memories(agent_id)
108
+
109
+ if not memories or not memory_embeddings:
110
+ return []
111
+
112
+ # 创建 memory_id -> memory 的映射
113
+ memory_map = {m["memory_id"]: m for m in memories}
114
+
115
+ scores = []
116
+
117
+ for memory in memories:
118
+ memory_id = memory["memory_id"]
119
+
120
+ # 确保嵌入存在
121
+ if memory_id not in memory_embeddings:
122
+ continue
123
+
124
+ memory_vec = memory_embeddings[memory_id]
125
+
126
+ # 1. 语义相似度
127
+ semantic_sim = cosine_similarity(query_embedding, memory_vec)
128
+
129
+ # 2. 置信度
130
+ confidence = self._compute_confidence_score(memory)
131
+
132
+ # 3. 成功/失败偏好
133
+ success_bonus = 1.0 if memory.get("success", True) else -0.5
134
+
135
+ # 4. 时间衰减
136
+ time_decay = self._compute_time_decay(memory["timestamp"])
137
+
138
+ # 混合评分
139
+ final_score = (
140
+ self.weights["semantic"] * semantic_sim
141
+ + self.weights["confidence"] * confidence
142
+ + self.weights["success"] * success_bonus
143
+ - self.weights["recency"] * (1 - time_decay) # 越新越好
144
+ )
145
+
146
+ scores.append((memory_id, float(final_score)))
147
+
148
+ # 按分数降序排序
149
+ scores.sort(key=lambda x: x[1], reverse=True)
150
+
151
+ # 返回 Top-K
152
+ return scores[:top_k]
153
+
154
+ def get_name(self) -> str:
155
+ return "hybrid"