mindforge-memory 6.4.1__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.
- MindForge.py +20 -0
- mindforge/__init__.py +111 -0
- mindforge/adapters/__init__.py +18 -0
- mindforge/adapters/claude_adapter.py +84 -0
- mindforge/adapters/crewai_memory.py +76 -0
- mindforge/adapters/generic_api.py +183 -0
- mindforge/adapters/langgraph_store.py +463 -0
- mindforge/adapters/openclaw_adapter.py +91 -0
- mindforge/api/__init__.py +1 -0
- mindforge/api/server.py +1567 -0
- mindforge/cli/__init__.py +1 -0
- mindforge/cli/main.py +13607 -0
- mindforge/core/__init__.py +37 -0
- mindforge/core/embedding.py +847 -0
- mindforge/core/encryption.py +597 -0
- mindforge/core/indexer.py +363 -0
- mindforge/core/mindforge.py +6298 -0
- mindforge/core/paths.py +43 -0
- mindforge/core/query.py +501 -0
- mindforge/core/storage.py +18656 -0
- mindforge/core/types.py +314 -0
- mindforge/core/version.py +56 -0
- mindforge/daemon/__init__.py +0 -0
- mindforge/daemon/server.py +277 -0
- mindforge/mcp/__init__.py +4 -0
- mindforge/mcp/server.py +2559 -0
- mindforge/modules/__init__.py +138 -0
- mindforge/modules/categorizer.py +151 -0
- mindforge/modules/conflict_detector.py +356 -0
- mindforge/modules/connectors.py +331 -0
- mindforge/modules/context_window.py +337 -0
- mindforge/modules/event_bus.py +572 -0
- mindforge/modules/evolution.py +210 -0
- mindforge/modules/experience.py +402 -0
- mindforge/modules/federated.py +722 -0
- mindforge/modules/federated_acl.py +393 -0
- mindforge/modules/follow_up.py +315 -0
- mindforge/modules/hybrid_search.py +565 -0
- mindforge/modules/importer.py +374 -0
- mindforge/modules/integrator.py +196 -0
- mindforge/modules/intent_router.py +359 -0
- mindforge/modules/knowledge_graph.py +395 -0
- mindforge/modules/memory_compressor.py +592 -0
- mindforge/modules/memory_decay.py +474 -0
- mindforge/modules/memory_passport.py +250 -0
- mindforge/modules/multi_agent.py +534 -0
- mindforge/modules/multimodal.py +304 -0
- mindforge/modules/outcome_learning.py +186 -0
- mindforge/modules/personality.py +275 -0
- mindforge/modules/principled_forgetting.py +258 -0
- mindforge/modules/privacy.py +736 -0
- mindforge/modules/recall.py +263 -0
- mindforge/modules/session_focus.py +305 -0
- mindforge/modules/share_conflict.py +521 -0
- mindforge/modules/skill_extractor.py +340 -0
- mindforge/modules/skill_pack.py +205 -0
- mindforge/modules/timeline_viz.py +206 -0
- mindforge/modules/v6.py +931 -0
- mindforge_memory-6.4.1.dist-info/METADATA +281 -0
- mindforge_memory-6.4.1.dist-info/RECORD +64 -0
- mindforge_memory-6.4.1.dist-info/WHEEL +5 -0
- mindforge_memory-6.4.1.dist-info/entry_points.txt +3 -0
- mindforge_memory-6.4.1.dist-info/licenses/LICENSE +21 -0
- mindforge_memory-6.4.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MindForge 意图分类路由引擎
|
|
3
|
+
================================
|
|
4
|
+
三层路由:规则正则 → 关键词加权 → (可选)LLM 兜底
|
|
5
|
+
支持 10+ 种业务意图,带置信度与降级路径。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import logging
|
|
11
|
+
import re
|
|
12
|
+
import time
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
15
|
+
|
|
16
|
+
# v6.1.2 修复:本模块第 286 行的降级分支(LLM 意图识别失败 → 回退启发式路由)
|
|
17
|
+
# 一直在调用 `logger.warning(...)`,但模块从未导入 logging、也未定义 logger。
|
|
18
|
+
# 于是「优雅降级」路径本身抛 NameError: name 'logger' is not defined,把可恢复
|
|
19
|
+
# 的失败变成硬崩溃。同项目的 importer.py 用的是正确写法(`logging.getLogger`),
|
|
20
|
+
# 证明这里只是遗漏。
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# 内置意图定义
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
INTENT_DEFS: Dict[str, Dict[str, Any]] = {
|
|
28
|
+
"memory_store": {
|
|
29
|
+
"label": "记忆存储",
|
|
30
|
+
"description": "用户要求记住、保存、记录某事",
|
|
31
|
+
"examples": ["记住这件事", "帮我保存", "记录一下", "别忘了这个", "写入记忆"],
|
|
32
|
+
"keywords_pos": ["记住", "保存", "记录", "别忘了", "写入", "记下", "存起来", "备忘"],
|
|
33
|
+
"keywords_neg": [],
|
|
34
|
+
"patterns": [r"(记|保存|记录|忘?记)住|存(起来|储)?|备?忘"],
|
|
35
|
+
"priority": 85,
|
|
36
|
+
},
|
|
37
|
+
"memory_recall": {
|
|
38
|
+
"label": "记忆检索",
|
|
39
|
+
"description": "用户查询历史、搜索记忆、回忆某事",
|
|
40
|
+
"examples": ["我以前说过什么", "帮我找一下", "搜索记忆", "上次提到", "回忆一下"],
|
|
41
|
+
"keywords_pos": ["搜索", "查找", "找一下", "回忆", "以前", "上次", "历史", "检索", "提过"],
|
|
42
|
+
"keywords_neg": ["怎么", "为什么", "如何", "教程", "方案"],
|
|
43
|
+
"patterns": [r"(搜索|查找|找|回忆|检索)(记忆|记录|历史|一下)?"],
|
|
44
|
+
"priority": 80,
|
|
45
|
+
},
|
|
46
|
+
"question_answer": {
|
|
47
|
+
"label": "知识问答",
|
|
48
|
+
"description": "用户提出事实类/方法类问题",
|
|
49
|
+
"examples": ["这是什么意思", "怎么实现", "为什么会这样", "解释一下", "原理是什么"],
|
|
50
|
+
"keywords_pos": ["为什么", "怎么", "如何", "什么", "解释", "原理", "意思", "能否"],
|
|
51
|
+
"keywords_neg": [],
|
|
52
|
+
"patterns": [r"(为什么|怎么|如何|是?什么|解释|原理)(样|呢|吧|啊|吗|一下)?$"],
|
|
53
|
+
"priority": 70,
|
|
54
|
+
},
|
|
55
|
+
"task_planning": {
|
|
56
|
+
"label": "任务规划",
|
|
57
|
+
"description": "制定计划、拆解步骤、排期安排",
|
|
58
|
+
"examples": ["我该怎么开始", "制定计划", "分几步", "排期", "下一步做什么"],
|
|
59
|
+
"keywords_pos": ["计划", "步骤", "排期", "安排", "下一步", "拆解", "流程", "方案", "todo", "待办"],
|
|
60
|
+
"keywords_neg": [],
|
|
61
|
+
"patterns": [r"(计划|步骤|排期|安排|todo|待办|下一步|怎么(做|开始|办))"],
|
|
62
|
+
"priority": 75,
|
|
63
|
+
},
|
|
64
|
+
"drama_creation": {
|
|
65
|
+
"label": "短剧创作",
|
|
66
|
+
"description": "短剧/剧本/角色/台词创作相关",
|
|
67
|
+
"examples": ["写个短剧", "生成剧本", "加一段台词", "设计角色", "剧情怎么走"],
|
|
68
|
+
"keywords_pos": ["短剧", "剧本", "台词", "角色", "剧情", "剧集", "场景", "写戏", "拍剧"],
|
|
69
|
+
"keywords_neg": [],
|
|
70
|
+
"patterns": [r"(短剧|剧本|台词|角色|剧情|剧集|场景)(创作|设计|生成|写|加|分析)?"],
|
|
71
|
+
"priority": 90,
|
|
72
|
+
},
|
|
73
|
+
"agent_control": {
|
|
74
|
+
"label": "Agent 控制",
|
|
75
|
+
"description": "控制 Agent 行为、切换人格、记忆管理",
|
|
76
|
+
"examples": ["切换模式", "换个人格", "清空记忆", "导出数据", "停止分析"],
|
|
77
|
+
"keywords_pos": ["切换", "人格", "模式", "清空", "删除", "导出", "导入", "备份", "停止", "重启"],
|
|
78
|
+
"keywords_neg": [],
|
|
79
|
+
"patterns": [r"(切换|清空|删除|导出|导入|备份|重启|停止|设置|改)(模式|人格|记忆|数据|配置)?"],
|
|
80
|
+
"priority": 88,
|
|
81
|
+
},
|
|
82
|
+
"emotion_support": {
|
|
83
|
+
"label": "情感支持",
|
|
84
|
+
"description": "用户表达情绪、倾诉、寻求安慰",
|
|
85
|
+
"examples": ["我好难过", "太烦了", "压力大", "不开心", "求安慰"],
|
|
86
|
+
"keywords_pos": ["难过", "烦", "压力", "不开心", "伤心", "生气", "累", "焦虑", "安慰", "鼓励"],
|
|
87
|
+
"keywords_neg": ["怎么", "为什么", "解决"],
|
|
88
|
+
"patterns": [r"(好?(难|烦|累|伤)?(过|心|人)|(焦|担)虑|(求|需?要)?安慰|鼓励|委屈|崩溃|抑?郁)"],
|
|
89
|
+
"priority": 82,
|
|
90
|
+
},
|
|
91
|
+
"code_development": {
|
|
92
|
+
"label": "代码开发",
|
|
93
|
+
"description": "写代码、调试、重构、review",
|
|
94
|
+
"examples": ["写一个Python脚本", "帮我debug", "优化这段代码", "解释这个函数", "重构一下"],
|
|
95
|
+
"keywords_pos": ["代码", "函数", "类", "debug", "重构", "优化", "bug", "报错", "python", "java", "script"],
|
|
96
|
+
"keywords_neg": [],
|
|
97
|
+
"patterns": [r"(写|生成|优?化?|重构|debug|修|解释|review).*(代码|函数|类|脚本|bug|报错|方法)"],
|
|
98
|
+
"priority": 78,
|
|
99
|
+
},
|
|
100
|
+
"information_extract": {
|
|
101
|
+
"label": "信息抽取",
|
|
102
|
+
"description": "从文本中抽取实体、关系、结构",
|
|
103
|
+
"examples": ["从这段提取关键词", "总结要点", "列出人物", "抽取参数"],
|
|
104
|
+
"keywords_pos": ["抽取", "提取", "总结", "要点", "实体", "关键词", "标签", "结构化", "摘要"],
|
|
105
|
+
"keywords_neg": [],
|
|
106
|
+
"patterns": [r"(抽取|提取|总结|要点|实体|关键词|标签|结构化|摘要|列(出|取)?)"],
|
|
107
|
+
"priority": 76,
|
|
108
|
+
},
|
|
109
|
+
"creative_writing": {
|
|
110
|
+
"label": "创意写作",
|
|
111
|
+
"description": "文案、故事、诗、营销稿创作",
|
|
112
|
+
"examples": ["写首诗", "帮我写个文案", "写个故事", "润色一下", "slogan"],
|
|
113
|
+
"keywords_pos": ["文案", "诗", "故事", "润色", "slogan", "标题", "软文", "广告语", "小说"],
|
|
114
|
+
"keywords_neg": [],
|
|
115
|
+
"patterns": [r"(写|创作|润色|生成|构思|起).*(文案|诗|故事|slogan|标题|软文|广告|小说|散文)"],
|
|
116
|
+
"priority": 72,
|
|
117
|
+
},
|
|
118
|
+
"chit_chat": {
|
|
119
|
+
"label": "闲聊对话",
|
|
120
|
+
"description": "日常寒暄、问候、无明确任务意图",
|
|
121
|
+
"examples": ["你好", "在吗", "哈哈", "天气真好", "谢谢"],
|
|
122
|
+
"keywords_pos": ["你好", "在吗", "嗨", "哈哈", "谢谢", "再见", "早", "晚安", "天气"],
|
|
123
|
+
"keywords_neg": [],
|
|
124
|
+
"patterns": [r"^(你好|嗨|hi|hello|在?吗|哈哈|嘿嘿|谢谢|感谢|再见|晚安|早|好啊)$"],
|
|
125
|
+
"priority": 50,
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
DEFAULT_INTENT = "chit_chat"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
# 数据类
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class IntentResult:
|
|
138
|
+
intent: str
|
|
139
|
+
label: str
|
|
140
|
+
confidence: float # 0.0 ~ 1.0
|
|
141
|
+
routing: str # 建议的路由目标
|
|
142
|
+
matched_rules: List[str] = field(default_factory=list)
|
|
143
|
+
keyword_hits: Dict[str, int] = field(default_factory=dict)
|
|
144
|
+
candidates: List[Tuple[str, float]] = field(default_factory=list)
|
|
145
|
+
latency_ms: float = 0.0
|
|
146
|
+
fallback: bool = False # 是否走了 LLM/默认兜底
|
|
147
|
+
|
|
148
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
149
|
+
return {
|
|
150
|
+
"intent": self.intent,
|
|
151
|
+
"label": self.label,
|
|
152
|
+
"confidence": round(self.confidence, 4),
|
|
153
|
+
"routing": self.routing,
|
|
154
|
+
# 路由层级:0=规则正则 1=关键词加权 2=LLM 路由 3=兜底
|
|
155
|
+
# v5.5.8 修复:原表达式 `2 if self.fallback else 2` 两支相同,
|
|
156
|
+
# 导致兜底与正常 LLM 路由无法区分,level 分级失效。
|
|
157
|
+
"level": 0 if self.matched_rules else (1 if self.keyword_hits else (3 if self.fallback else 2)),
|
|
158
|
+
"matched_rules": self.matched_rules,
|
|
159
|
+
"keyword_hits": self.keyword_hits,
|
|
160
|
+
"top_keywords_hits": [(k, w) for k, w in list(self.keyword_hits.items())[:5]],
|
|
161
|
+
"top_candidates": self.candidates[:5],
|
|
162
|
+
"latency_ms": round(self.latency_ms, 2),
|
|
163
|
+
"fallback": self.fallback,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ---------------------------------------------------------------------------
|
|
168
|
+
# 路由引擎
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
class IntentRouter:
|
|
172
|
+
"""三层意图路由引擎"""
|
|
173
|
+
|
|
174
|
+
def __init__(self,
|
|
175
|
+
llm_classifier: Optional[Callable[[str], List[Tuple[str, float]]]] = None,
|
|
176
|
+
custom_intents: Optional[Dict[str, Dict[str, Any]]] = None,
|
|
177
|
+
min_confidence: float = 0.35,
|
|
178
|
+
cache_size: int = 4096):
|
|
179
|
+
self.intents = dict(INTENT_DEFS)
|
|
180
|
+
if custom_intents:
|
|
181
|
+
self.intents.update(custom_intents)
|
|
182
|
+
self.llm_classifier = llm_classifier
|
|
183
|
+
self.min_confidence = min_confidence
|
|
184
|
+
self._cache: Dict[str, Tuple[str, float, List[Tuple[str, float]]]] = {}
|
|
185
|
+
self._cache_size = cache_size
|
|
186
|
+
self._compiled_patterns: Dict[str, List[re.Pattern]] = {
|
|
187
|
+
name: [re.compile(p, re.IGNORECASE) for p in d.get("patterns", [])]
|
|
188
|
+
for name, d in self.intents.items()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# -- scoring primitives -------------------------------------------------
|
|
192
|
+
|
|
193
|
+
def _keyword_score(self, text: str, intent: str) -> Tuple[float, Dict[str, int]]:
|
|
194
|
+
d = self.intents[intent]
|
|
195
|
+
hits: Dict[str, int] = {}
|
|
196
|
+
pos = d.get("keywords_pos", [])
|
|
197
|
+
neg = d.get("keywords_neg", [])
|
|
198
|
+
score = 0.0
|
|
199
|
+
for kw in pos:
|
|
200
|
+
if kw and kw in text:
|
|
201
|
+
cnt = text.count(kw)
|
|
202
|
+
hits[kw] = cnt
|
|
203
|
+
score += cnt * 0.12
|
|
204
|
+
for kw in neg:
|
|
205
|
+
if kw and kw in text:
|
|
206
|
+
score -= 0.18
|
|
207
|
+
return min(score, 0.95), hits
|
|
208
|
+
|
|
209
|
+
def _pattern_score(self, text: str, intent: str) -> Tuple[float, List[str]]:
|
|
210
|
+
matched: List[str] = []
|
|
211
|
+
score = 0.0
|
|
212
|
+
for p in self._compiled_patterns.get(intent, []):
|
|
213
|
+
m = p.search(text)
|
|
214
|
+
if m:
|
|
215
|
+
matched.append(m.group(0))
|
|
216
|
+
score += 0.22
|
|
217
|
+
return min(score, 0.95), matched
|
|
218
|
+
|
|
219
|
+
def _length_and_shape_bonus(self, text: str, intent: str) -> float:
|
|
220
|
+
"""简单的形状启发式:问题多含问号、命令较短、闲聊很短。"""
|
|
221
|
+
t = text.strip()
|
|
222
|
+
if not t:
|
|
223
|
+
return 0.0
|
|
224
|
+
bonus = 0.0
|
|
225
|
+
if intent == "question_answer" and ("?" in t or "?" in t):
|
|
226
|
+
bonus += 0.15
|
|
227
|
+
if intent == "chit_chat" and len(t) <= 8:
|
|
228
|
+
bonus += 0.2
|
|
229
|
+
if intent == "agent_control" and t.endswith(("一下", "吧", "啊")):
|
|
230
|
+
bonus += 0.05
|
|
231
|
+
if intent == "creative_writing" and len(t) >= 6:
|
|
232
|
+
bonus += 0.05
|
|
233
|
+
return bonus
|
|
234
|
+
|
|
235
|
+
# -- classify -----------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
def classify(self, text: str, force_override: Optional[str] = None) -> IntentResult:
|
|
238
|
+
t0 = time.perf_counter()
|
|
239
|
+
src = (text or "").strip()
|
|
240
|
+
|
|
241
|
+
if force_override and force_override in self.intents:
|
|
242
|
+
d = self.intents[force_override]
|
|
243
|
+
return IntentResult(
|
|
244
|
+
intent=force_override, label=d["label"],
|
|
245
|
+
confidence=0.99, routing=force_override,
|
|
246
|
+
matched_rules=["force_override"], latency_ms=0.1, fallback=False,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# cache key (ignore whitespace)
|
|
250
|
+
cache_key = hashlib.sha256(re.sub(r"\s+", "", src).encode("utf-8")).hexdigest() # P1-007: MD5→SHA-256
|
|
251
|
+
if cache_key in self._cache:
|
|
252
|
+
intent, conf, cands = self._cache[cache_key]
|
|
253
|
+
return IntentResult(
|
|
254
|
+
intent=intent, label=self.intents[intent]["label"],
|
|
255
|
+
confidence=conf, routing=intent, candidates=cands,
|
|
256
|
+
latency_ms=(time.perf_counter() - t0) * 1000,
|
|
257
|
+
matched_rules=["cache"], fallback=False,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
scores: List[Tuple[str, float, Dict[str, int], List[str]]] = []
|
|
261
|
+
for name in self.intents:
|
|
262
|
+
kw_s, kw_hits = self._keyword_score(src, name)
|
|
263
|
+
pat_s, pat_matched = self._pattern_score(src, name)
|
|
264
|
+
shape_bonus = self._length_and_shape_bonus(src, name)
|
|
265
|
+
priority = self.intents[name].get("priority", 50) / 1000.0
|
|
266
|
+
total = kw_s + pat_s + shape_bonus + priority
|
|
267
|
+
scores.append((name, total, kw_hits, pat_matched))
|
|
268
|
+
|
|
269
|
+
scores.sort(key=lambda x: x[1], reverse=True)
|
|
270
|
+
top_name, top_score, top_kw, top_pat = scores[0]
|
|
271
|
+
candidates = [(n, round(s, 4)) for n, s, _, _ in scores[:5]]
|
|
272
|
+
fallback = False
|
|
273
|
+
|
|
274
|
+
# LLM 兜底(若配置且置信度低于阈值或与第二名差距小)
|
|
275
|
+
# v5.4.8 P2-003 修复:添加最低 LLM 置信度阈值,防止低质量 LLM 结果被接受
|
|
276
|
+
llm_accepted = False
|
|
277
|
+
min_llm_confidence = 0.5 # LLM 结果必须达到此阈值才被接受
|
|
278
|
+
|
|
279
|
+
if top_score < self.min_confidence and self.llm_classifier is not None:
|
|
280
|
+
try:
|
|
281
|
+
llm_scores = self.llm_classifier(src) or []
|
|
282
|
+
if llm_scores:
|
|
283
|
+
llm_scores.sort(key=lambda x: x[1], reverse=True)
|
|
284
|
+
first = llm_scores[0]
|
|
285
|
+
# 验证:意图存在 + 分数高于规则 + 分数达到最低阈值
|
|
286
|
+
if (first[0] in self.intents and
|
|
287
|
+
first[1] > top_score and
|
|
288
|
+
first[1] >= min_llm_confidence):
|
|
289
|
+
top_name, top_score = first[0], first[1]
|
|
290
|
+
candidates = [(n, round(s, 4)) for n, s in llm_scores[:5]]
|
|
291
|
+
fallback = True
|
|
292
|
+
llm_accepted = True
|
|
293
|
+
except Exception as _llm_err:
|
|
294
|
+
logger.warning("LLM 意图识别失败,回退启发式路由: %s", _llm_err)
|
|
295
|
+
|
|
296
|
+
# 二次兜底:默认意图(仅在 LLM 未接受时)
|
|
297
|
+
if top_score < self.min_confidence and not llm_accepted:
|
|
298
|
+
top_name = DEFAULT_INTENT
|
|
299
|
+
top_score = max(top_score, 0.25)
|
|
300
|
+
fallback = True
|
|
301
|
+
|
|
302
|
+
latency = (time.perf_counter() - t0) * 1000
|
|
303
|
+
result = IntentResult(
|
|
304
|
+
intent=top_name,
|
|
305
|
+
label=self.intents[top_name]["label"],
|
|
306
|
+
confidence=min(max(top_score, 0.0), 1.0),
|
|
307
|
+
routing=top_name,
|
|
308
|
+
matched_rules=list(dict.fromkeys(top_pat))[:6],
|
|
309
|
+
keyword_hits=top_kw,
|
|
310
|
+
candidates=candidates,
|
|
311
|
+
latency_ms=latency,
|
|
312
|
+
fallback=fallback,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# cache 写入(简单 LRU:超出就清空一半)
|
|
316
|
+
if len(self._cache) >= self._cache_size:
|
|
317
|
+
keys = list(self._cache.keys())[:self._cache_size // 2]
|
|
318
|
+
for k in keys:
|
|
319
|
+
self._cache.pop(k, None)
|
|
320
|
+
self._cache[cache_key] = (top_name, result.confidence, candidates)
|
|
321
|
+
return result
|
|
322
|
+
|
|
323
|
+
# v5.4.7 修复:添加 classify_intent 别名,与 README 文档保持一致
|
|
324
|
+
def classify_intent(self, text: str, force_override: Optional[str] = None) -> IntentResult:
|
|
325
|
+
"""classify() 的别名方法,与 README 文档中的 API 名称保持一致。"""
|
|
326
|
+
return self.classify(text, force_override)
|
|
327
|
+
|
|
328
|
+
# -- batch --------------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
def classify_batch(self, texts: List[str]) -> List[IntentResult]:
|
|
331
|
+
return [self.classify(t) for t in texts]
|
|
332
|
+
|
|
333
|
+
def add_intent(self, name: str, definition: Dict[str, Any]) -> None:
|
|
334
|
+
if not isinstance(definition, dict):
|
|
335
|
+
raise ValueError("definition must be dict")
|
|
336
|
+
self.intents[name] = definition
|
|
337
|
+
self._compiled_patterns[name] = [
|
|
338
|
+
re.compile(p, re.IGNORECASE) for p in definition.get("patterns", [])
|
|
339
|
+
]
|
|
340
|
+
|
|
341
|
+
def routing_target(self, intent: str) -> str:
|
|
342
|
+
"""给出该意图对应的下一步路由目标(handler 名 / API endpoint)。"""
|
|
343
|
+
mapping = {
|
|
344
|
+
"memory_store": "memory.add",
|
|
345
|
+
"memory_recall": "memory.search",
|
|
346
|
+
"question_answer": "llm.answer",
|
|
347
|
+
"task_planning": "agent.plan",
|
|
348
|
+
"drama_creation": "drama.compose",
|
|
349
|
+
"agent_control": "agent.control",
|
|
350
|
+
"emotion_support": "agent.empathy",
|
|
351
|
+
"code_development": "llm.code",
|
|
352
|
+
"information_extract": "ie.extract",
|
|
353
|
+
"creative_writing": "llm.write",
|
|
354
|
+
"chit_chat": "agent.chat",
|
|
355
|
+
}
|
|
356
|
+
return mapping.get(intent, f"handler.{intent}")
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
__all__ = ["IntentRouter", "IntentResult", "INTENT_DEFS", "DEFAULT_INTENT"]
|