// modules.mbt — core algorithms (mirrors Python `modules.py`)
//
// Classification, deduplication, conflict detection, priority recomputation,
// self-evolution and graded retrieval. Time is handled as epoch milliseconds.
///|
/// Keyword hints per memory type, used by `classify`.
let _TYPE_KEYWORDS : Array[(MemoryType, Array[String])] = [
(
Instruction,
[
"必须", "不要", "禁止", "总是", "务必", "指令", "规则", "不允许",
"要求", "应当",
],
),
(
Preference,
["偏好", "喜欢", "习惯", "倾向", "更愿意", "风格", "口味"],
),
(
Fact,
[
"成立于", "位于", "定义", "数据", "统计", "出生于", "总部",
"发布于", "创始于",
],
),
]
///|
/// Polarity pairs used by conflict detection.
let _POLARITY : Array[(String, String)] = [
("支持", "反对"),
("是", "不是"),
("允许", "禁止"),
("喜欢", "讨厌"),
("正确", "错误"),
("启用", "禁用"),
]
///|
fn is_punct(c : Char) -> Bool {
let s = c.to_string()
s == "," ||
s == "。" ||
s == "、" ||
s == "!" ||
s == "?" ||
s == "." ||
s == "," ||
s == "!" ||
s == "?"
}
///|
/// Validate raw input (mirrors `validate_input`).
pub fn validate_input(content : String) -> (Bool, String) {
let cleaned = content.trim()
if cleaned.length() == 0 {
return (false, "内容为空")
}
if cleaned.length() < 4 {
return (false, "内容过短,缺少明确主体")
}
let mut all_punct = true
for c in cleaned.iter() {
if !is_punct(c) {
all_punct = false
break
}
}
if all_punct {
return (false, "内容为纯标点噪声")
}
(true, "")
}
///|
/// Classify content into a memory type by keyword scoring.
pub fn classify(content : String) -> MemoryType {
let mut best = CommonSense
let mut best_score = 0
for item in _TYPE_KEYWORDS {
let (t, kws) = item
let mut sc = 0
for kw in kws {
if content.contains(kw) {
sc = sc + 1
}
}
if sc > best_score {
best_score = sc
best = t
}
}
if best_score == 0 {
CommonSense
} else {
best
}
}
///|
/// Find the most similar existing entry above `threshold` (duplicate check).
pub fn find_duplicate(
content : String,
candidates : Array[MemoryEntry],
threshold? : Double = 0.92,
) -> MemoryEntry? {
let mut best = None
let mut best_sim = 0.0
for c in candidates {
let s = similarity(content, c.content)
if s > best_sim {
best_sim = s
best = Some(c)
}
}
if best_sim >= threshold {
best
} else {
None
}
}
///|
fn has_opposite_polarity(a : String, b : String) -> Bool {
for item in _POLARITY {
let (pos, neg) = item
if (a.contains(pos) && b.contains(neg)) ||
(a.contains(neg) && b.contains(pos)) {
return true
}
}
false
}
///|
/// Detect conflicting entries (similar + opposite polarity).
pub fn detect_conflict(
new_entry : MemoryEntry,
candidates : Array[MemoryEntry],
threshold? : Double = 0.55,
) -> Array[MemoryEntry] {
let conflicts : Array[MemoryEntry] = []
for c in candidates {
if c.id != new_entry.id {
let sim = similarity(new_entry.content, c.content)
if sim >= threshold && has_opposite_polarity(new_entry.content, c.content) {
conflicts.push(c)
}
}
}
conflicts
}
///|
fn fmin(a : Double, b : Double) -> Double {
if a < b {
a
} else {
b
}
}
///|
fn fmax(a : Double, b : Double) -> Double {
if a > b {
a
} else {
b
}
}
///|
fn round4(x : Double) -> Double {
@math.round(x * 10000.0) / 10000.0
}
///|
/// Age in days from a last-accessed timestamp (epoch ms) to `now` (epoch ms).
fn age_days(last_ms : Int64, now : Int64) -> Double {
if last_ms == 0L {
return 9999.0
}
let days = (now - last_ms).to_double() / 86400000.0
if days < 0.0 {
0.0
} else {
days
}
}
///|
/// Recompute an entry's priority score.
pub fn recompute_priority(
entry : MemoryEntry,
now : Int64,
w? : (Double, Double, Double) = (0.5, 0.3, 0.2),
half_life? : Double = 30.0,
) -> Double {
let (w1, w2, w3) = w
let freq_norm = fmin(entry.access_count.to_double() / 20.0, 1.0)
let age = age_days(entry.last_accessed, now)
let recency = @math.pow(0.5, age / half_life)
let feedback_norm = if entry.feedback_score != 0.0 {
(fmax(fmin(entry.feedback_score, 1.0), -1.0) + 1.0) / 2.0
} else {
0.0
}
round4(w1 * freq_norm + w2 * recency + w3 * feedback_norm)
}
///|
/// Self-evolution: recompute priorities and deprecate stale low-value entries.
pub fn evolve(
storage : Storage,
now? : Int64 = now_ms(),
deprecate_below? : Double = 0.15,
deprecate_age? : Double = 30.0,
) -> Map[String, Json] raise @fs.IOError {
let n = now
let mut changed = 0
let mut deprecated = 0
for entry in storage.all_entries() {
if entry.status == MemoryStatus::Archived.value() {
continue
}
let np = recompute_priority(entry, n)
let age = age_days(entry.last_accessed, n)
let mut status = entry.status
if np < deprecate_below && age > deprecate_age {
status = MemoryStatus::Deprecated.value()
deprecated = deprecated + 1
}
let updated = { ..entry, priority: np, status }
storage.put(updated)
changed = changed + 1
}
let res : Map[String, Json] = Map([], capacity=2)
res.set("recomputed", Json::number(changed.to_double()))
res.set("deprecated", Json::number(deprecated.to_double()))
res
}
///|
/// Graded retrieval: rank active entries by similarity x priority x recency.
pub fn graded_retrieval(
storage : Storage,
query : String,
top_k? : Int = 5,
now? : Int64 = now_ms(),
) -> Array[(MemoryEntry, Double)] {
let n = now
let results : Array[(MemoryEntry, Double)] = []
for entry in storage.all_entries() {
if entry.status != MemoryStatus::Active.value() {
continue
}
let sim = similarity(query, entry.content)
if sim <= 0.0 {
continue
}
let age = age_days(entry.last_accessed, n)
let recency = @math.pow(0.5, age / 30.0)
let score = sim * entry.priority * (0.5 + 0.5 * recency)
results.push((entry, round4(score)))
}
results.sort_by(fn(p, q) {
if q.1 > p.1 {
1
} else if q.1 < p.1 {
-1
} else {
0
}
})
let n = if top_k < results.length() { top_k } else { results.length() }
results.view(end=n).to_owned()
}