recall离线项目记忆
§ · Nested detail page

TF-IDF + TextRank 本地摘要器 深度解析

本页只展开一个模块,把导入契约、核心实现、流程和失败模式拆成可回溯行号。

§ 00 · IMPORT CHAIN

导入链与契约

入口代码说明该模块接受什么输入、输出什么结构,以及它依赖哪些相邻模块。

▸入口片段

src/scripts/summarizer.py · L54-89 repo: scripts/summarizer.pyPython
  54def summarize(text, k=8):
  55    sentences = split_sentences(text)
  56    if len(sentences) <= k:
  57        return sentences
  58    if len(sentences) > _MAX_RANK:
  59        sentences = sentences[-_MAX_RANK:]  # recency-biased; keeps it fast
  60
  61    tokens = [_tokenize(s) for s in sentences]
  62    if not any(tokens):
  63        return sentences[:k]
  64
  65    idf = _idf(tokens)
  66    try:
  67        import numpy  # noqa: F401
  68        scores = _textrank_numpy(tokens, idf)
  69    except Exception:
  70        scores = _textrank_python(tokens, idf)
  71
  72    return _select(sentences, scores, k)
  73
  74
  75def _select(sentences, scores, k):
  76    """Top-k sentences by score, returned in original order. Scores are rounded
  77    before ranking and ties are broken by position, so the sub-epsilon numerical
  78    difference between the numpy and pure-Python cores can never change which
  79    sentences are chosen — both backends always produce the same summary (a
  80    committed context.md must not depend on whether numpy happens to be present)."""
  81    order = sorted(range(len(sentences)),
  82                   key=lambda i: (round(scores[i], 10), -i), reverse=True)
  83    return [sentences[i] for i in sorted(order[:k])]
  84
  85
  86def backend_name():
  87    try:
  88        import numpy  # noqa: F401
  89        return "TextRank (vendored, numpy-accelerated)"

源码索引:src/scripts/summarizer.py:54 · src/scripts/summarizer.py:55 · src/scripts/summarizer.py:56 · src/scripts/summarizer.py:57 · src/scripts/summarizer.py:58 · src/scripts/summarizer.py:59 · src/scripts/summarizer.py:60 · src/scripts/summarizer.py:61 · src/scripts/summarizer.py:62 · src/scripts/summarizer.py:63 · src/scripts/summarizer.py:64 · src/scripts/summarizer.py:65 · src/scripts/summarizer.py:66 · src/scripts/summarizer.py:67 · src/scripts/summarizer.py:68 · src/scripts/summarizer.py:69 · src/scripts/summarizer.py:70 · src/scripts/summarizer.py:71 · src/scripts/summarizer.py:72 · src/scripts/summarizer.py:73 · src/scripts/summarizer.py:74 · src/scripts/summarizer.py:75 · src/scripts/summarizer.py:76 · src/scripts/summarizer.py:77 · src/scripts/summarizer.py:78 · src/scripts/summarizer.py:79 · src/scripts/summarizer.py:80 · src/scripts/summarizer.py:81 · src/scripts/summarizer.py:82 · src/scripts/summarizer.py:83 · src/scripts/summarizer.py:84 · src/scripts/summarizer.py:85 · src/scripts/summarizer.py:86 · src/scripts/summarizer.py:87 · src/scripts/summarizer.py:88 · src/scripts/summarizer.py:89 · src/scripts/summarizer.py:90 · src/scripts/summarizer.py:91 · src/scripts/summarizer.py:92 · src/scripts/summarizer.py:93 · src/scripts/summarizer.py:94 · src/scripts/summarizer.py:95 · src/scripts/summarizer.py:96 · src/scripts/summarizer.py:97 · src/scripts/summarizer.py:98

§ 01 · CORE IMPL

核心实现,逐段拆

▸中段状态转换

这一段通常体现最关键的状态转换:解析、选择、打分、写入或安全检查。

src/scripts/summarizer.py · L90-129 repo: scripts/summarizer.pyPython
  90    except ImportError:
  91        return "TextRank (vendored, pure Python)"
  92
  93
  94# --- shared TF-IDF ----------------------------------------------------------
  95
  96def _idf(tokens):
  97    n = len(tokens)
  98    df = Counter()
  99    for toks in tokens:
 100        for w in set(toks):
 101            df[w] += 1
 102    return {w: math.log((1.0 + n) / (1.0 + d)) + 1.0 for w, d in df.items()}
 103
 104
 105# --- pure-Python core -------------------------------------------------------
 106
 107def _tfidf_sparse(tokens, idf):
 108    vecs = []
 109    for toks in tokens:
 110        if not toks:
 111            vecs.append({})
 112            continue
 113        tf = Counter(toks)
 114        length = len(toks)
 115        vec = {w: (c / length) * idf[w] for w, c in tf.items()}
 116        norm = math.sqrt(sum(v * v for v in vec.values())) or 1.0
 117        vecs.append({w: v / norm for w, v in vec.items()})
 118    return vecs
 119
 120
 121def _cosine(a, b):
 122    if len(a) > len(b):
 123        a, b = b, a
 124    return sum(val * b.get(w, 0.0) for w, val in a.items())
 125
 126
 127def _textrank_python(tokens, idf):
 128    vecs = _tfidf_sparse(tokens, idf)
 129    n = len(vecs)

▸末段收束

收束段决定失败是否显式返回、结果是否稳定落盘,以及调用者能否继续恢复。

src/scripts/summarizer.py · L120-155 repo: scripts/summarizer.pyPython
 120
 121def _cosine(a, b):
 122    if len(a) > len(b):
 123        a, b = b, a
 124    return sum(val * b.get(w, 0.0) for w, val in a.items())
 125
 126
 127def _textrank_python(tokens, idf):
 128    vecs = _tfidf_sparse(tokens, idf)
 129    n = len(vecs)
 130    sim = [[0.0] * n for _ in range(n)]
 131    for i in range(n):
 132        for j in range(i + 1, n):
 133            c = _cosine(vecs[i], vecs[j])
 134            if c:
 135                sim[i][j] = sim[j][i] = c
 136    row_sum = [sum(row) for row in sim]
 137
 138    scores = [1.0 / n] * n
 139    base = (1.0 - _DAMPING) / n
 140    for _ in range(_MAX_ITERS):
 141        new = [base] * n
 142        for i in range(n):
 143            if row_sum[i] == 0.0:
 144                continue
 145            share = _DAMPING * scores[i] / row_sum[i]
 146            row = sim[i]
 147            for j in range(n):
 148                w = row[j]
 149                if w:
 150                    new[j] += share * w
 151        if sum(abs(new[i] - scores[i]) for i in range(n)) < _TOL:
 152            scores = new
 153            break
 154        scores = new
 155    return scores
§ 02 · KEY FLOW

关键流程

▸流程拆解

input → normalize → validate → output
failure → explicit fallback / error record → caller decision

如果后续改动触碰这些行,主页面对应的风险判断也要重跑。

§ 03 · EDGE CASES

边界 / 失败模式

▸边界列表

  • 空输入或缺失字段必须被显式处理。
  • 外部命令、模型调用或文件写入失败不能覆盖原始上下文。
  • 路径别名仅用于索引兼容;实际源码位置见每个代码块的 repo 标注。
Sub-page · 2026-06-29Source · raiyanyahya/recall↑ recall 主页面