// 注入点:Extractor。宿主实现事实提取;缺省是 RawExtractor。
// RawExtractor 把原文以 kind=Unstructured 入库,不调用模型。
///|
/// 提取器产出的一条结构化事实。
pub(all) struct ExtractedFact {
content : String
kind : EntryKind
/// 原文依据 span(LLM 提取器填写;raw 模式为 None)
source_span : String?
/// 本条事实是否为「降级产物」(LLM 失败 → 退回原文 unstructured)。
/// RawExtractor 的原文直通是设计形态,恒为 false;只有失败回退才置 true。
degraded : Bool
/// 降级原因(degraded=true 时非 None),供 notes/metadata 透传
degrade_reason : String?
}
///|
/// 手写 Show(原 derive(Show) 输出格式保持不变:{content: ..., kind: ..., source_span: ...})。
/// 新增字段 degraded / degrade_reason 故意不进入 Show 输出,保持既有断言稳定。
pub impl Show for ExtractedFact with fn output(self, logger) {
let span = match self.source_span {
Some(s) => "Some(\{s})"
None => "None"
}
logger.write_string(
"{content: \{self.content}, kind: \{self.kind}, source_span: \{span}}",
)
}
///|
pub extend ExtractedFact with Show::{to_string, output}
///|
/// 事实提取抽象。结构化提取由宿主实现并注入。
pub(open) trait Extractor {
/// 只保留可复用信息;不改写事实本身;宁少勿错。
/// 返回空数组 = 纯闲聊,不入库。
fn extract(self : Self, user_id : String, text : String) -> Result[
Array[ExtractedFact],
MoomemError,
]
/// 提取模式标识:"raw" | "llm"。记入 stats 与 export。
fn mode(self : Self) -> String
}
///|
/// 缺省提取器:原文直通(kind=Unstructured)。
/// 空白文本返回 0 条事实,不入库。
pub struct RawExtractor {}
///|
pub fn RawExtractor::new() -> RawExtractor {
RawExtractor::{ }
}
///|
pub impl Extractor for RawExtractor with fn extract(_, _, text) {
if is_blank_text(text) {
return Ok([])
}
Ok([
ExtractedFact::{
content: text,
kind: Unstructured,
source_span: None,
degraded: false,
degrade_reason: None,
},
])
}
///|
pub impl Extractor for RawExtractor with fn mode(_) {
"raw"
}
///|
pub extend RawExtractor with Extractor::{extract, mode}
///|
fn is_blank_text(text : String) -> Bool {
text.trim().length() == 0
}