长文档压缩实战:三种策略帮你节省50-75% tokens,附完整代码
在信息呈指数级增长的时代,在保持原意的前提下自动压缩内容的能力,对于高效获取信息至关重要,以下三种“面向任务”压缩策略:
一、LLM summarization with constraints(带约束的抽取式压缩)目标:将检索到的每个相关章节压缩为聚焦要点(仅包含与 query 相关的定价信息)。
实现思路:先用 LLM 提取结构化要点;再用 gensim 的 TextRank 在这些要点内进行抽取式压缩,确保语义聚焦与客观可复现。
Python 示例:
from openai import OpenAIimport gensim.summarization as gsclient = OpenAI()defconstraint_llm_summary(text: str, query: str) -> str: prompt = ("请仅提取与定价相关的要点,忽略认证、示例及其他无关内容。"f"Query: {query}\n\n内容:\n{text}" ) resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.0, )return resp.choices[0].message.content.strip()defcompress_with_textrank(summary_text: str, ratio: float = 0.4) -> str:# ratio 控制压缩比例,例如保留约40%的句子/要点return gs.summarizer(summary_text, ratio=ratio)# 假设检索到的三个相关章节(各约10页文本)sections = ["section1.txt", "section2.txt", "section3.txt"]query = "只提取 Plan A 与 Plan B 的 rate limits 和 quotas,包含具体数值"compressed_summaries = []for sec in sections: raw = open(sec).read() step1 = constraint_llm_summary(raw, query) # 先由 LLM 提取聚焦要点 step2 = compress_with_textrank(step1, ratio=0.4) # 再做抽取式压缩 compressed_summaries.append(step2)# 最终将三个压缩后的摘要与 query 一并送入下游生成
说明:
-
• TextRank 的抽取式特性可保留原文中包含确切数值的句子,适合与 LLM 约束提取配合。 -
• 使用 gs.summarizer 时注意其底层是句子级抽取,建议 ratio 不要过低,避免丢失关键数字。 -
• 与 README 对应:Graph-based 与 Feature-based 的抽取方法在 README 的 Extractive 章节(graph-based methods、feature-based methods)有更详细背景。
二、Sentence-level scoring(句子级评分过滤)目标:基于 query 计算每句相关性,保留前 20% 的句子(Context-Preserving Compression)。
实现思路:使用 BERT 变体计算句子与 query 的相似度,按分数截断。
Python 示例:
from sentence_transformers import SentenceTransformerimport numpy as npmodel = SentenceTransformer("all-MiniLM-L6-v2")defsentence_level_filter(text: str, query: str, keep_ratio: float = 0.2) -> str: lines = [l.strip() for l in text.split("\n") if l.strip()]ifnot lines:return"" query_emb = model.encode([query]) line_embs = model.encode(lines) scores = (line_embs * query_emb).sum(axis=1) # 点积近似余弦 k = max(1, int(len(lines) * keep_ratio)) top_idxs = np.argpartition(scores, -k)[-k:] # 快速取Top-K top_idxs = sorted(top_idxs)return"\n".join([lines[i] for i in top_idxs])filtered = sentence_level_filter(long_doc, query, keep_ratio=0.2)
说明:
-
• 此处“句子级评分”对应 README 中 Feature-based 方法的核心思路:用特征/表示为句子打分并选择。更多细节见 feature-based methods。
三、Hierarchical summarization(层次化摘要)目标:对超长文档先按章节摘要,再做元摘要(meta-summary),形成三层:全文 → 章节摘要 → 最终摘要。
实现思路:按章节切分后使用基于 LSA 的主题抽取方法(如使用 gensim 的 LSI)得到章节主题摘要;再用 LLM 将各章节摘要聚合为最终摘要。
Python 示例:
from gensim import corpora, modelsfrom gensim.summarization import summarize as gs_summarizeimport redefsplit_into_chapters(text: str, chapter_pattern: str = r"^#+\s+.+$") -> list: lines = text.split("\n") chapters, cur = [], []for l in lines:if re.match(chapter_pattern, l):if cur: chapters.append("\n".join(cur)) cur = [] cur.append(l)else: cur.append(l)if cur: chapters.append("\n".join(cur))return chaptersdefchapter_lsa_summary(chapter: str, num_topics: int = 2) -> str:# 转换为句子列表,去除空行 sentences = [s.strip() for s in chapter.split("\n") if s.strip()] tokenized = [[w.lower() for w in s.split()] for s in sentences] dictionary = corpora.Dictionary(tokenized) corpus = [dictionary.doc2bow(t) for t in tokenized] lsi = models.LsiModel(corpus, id2word=dictionary, num_topics=num_topics)# 以每句对主导主题的贡献加权选择句子 corpus_lsi = lsi[corpus] topic_weights = np.array([sum(w for _, w in vec) for vec in corpus_lsi]) top_k = max(1, int(len(sentences) * 0.3)) # 保留约30%主题高相关句子 top_idxs = np.argsort(topic_weights)[-top_k:][::-1]return"\n".join([sentences[i] for i insorted(top_idxs)])defhierarchical_summary(text: str, query: str): chapters = split_into_chapters(text) chapter_summaries = [chapter_lsa_summary(ch) for ch in chapters] meta = "\n\n".join(chapter_summaries) final = constraint_llm_summary(meta, query)return finalfinal_summary = hierarchical_summary(very_long_doc, query)
权衡与落地建议:
-
Summarization 会增加额外 LLM 调用(每个检索文档一次)。对于超过约 2000 tokens 的文档,压缩在成本与效果上通常更划算;建议根据实际 token 限制与延迟要求动态选择策略。 -
在需要严格保留数字与结构化信息的场景(如定价配额、错误码与重置规则),结合 TextRank 等抽取式方法更为稳妥;在需要语义融合与连贯表述的场景,可加重生成式或混合方法的权重。
夜雨聆风
