【内容摘要】大模型内容摘要实战 会议摘要 提示词技巧

之前写过一篇文章,介绍了一个提示词技巧:【RAG效果提升】 大模型RAG 长文本图像分类 文本分类 舆情分析 内容审核。今天介绍一下内容摘要的一个新技巧。

可以参考 sage(SAGE: A Framework of Precise Retrieval for RAG)、Meeting-Minutes。

背景

我有一些喜马拉雅软件的文稿,这些文稿是由录音转化成的,非常零碎,甚至会有一些完整的句子备份到两行中。

我大概用了一块钱,总结了一篇8000字的经管文章,总结出来的内容基本上能全篇看懂,我与喜马拉雅原始的ai解读做了对比,发现各有优势。

忠实传递价值,解读全球好书。
你好,这里是喜马讲书,今天为你解读的书是稀缺。
副标题是我们是如何陷入贫穷与忙碌的,贫穷与忙碌听上去毫无关系。
一个人穷得没有钱吃饭,和一个人忙到没时间吃饭,难道是一回事吗?
如果你深入探究,就能发现二者之间有一个共同点,那就是拥有少于需要的感觉。
有意思的是,就算给忙碌的人更多的时间给穷人更多的钱,他们仍会处于忙碌和贫穷的状态中。
这种现象就是我们今天要说的稀缺稀缺资源的稀缺。
这里的稀缺不仅是指缺少时间和金钱这种实质上的资源的匮乏,还指一种心态比如说,老板要求你下周一要提交一份报告,现在已经是周六晚上了,你还没有开始做,于是你变得非常焦虑,决定周日哪儿都不去专心在家里赶报告。
这时候任何事情都无法干扰你,这就是稀缺心态,就是当你缺少时间或者金钱时,注意力全部盯着眼下的某物或者某事的状态。
这一现象是美国一个跨学科团队研究调査得出的,这项研究被誉为心理学行为经济学和政策研究学协作的典范。
这个跨学科团队是由美国哈佛大学终身教授,经济学家塞德希尔,穆莱纳森团队和普林斯顿大学教授认知心理学家埃尔德沙菲尔团队组成的,。
其中主导者是塞德希尔穆莱纳森。

这与真实的会议录音有些像,因此需要先对这些句子进行段落合并,然后进行两次摘要。会议摘要后期会补上,目前需要和同事一起参加一个比赛。

步骤1

这一步主要是把句子合并成段落。我的技巧就在下面。

首先,内容需要处理成这样,增加标号,引导模型显式的思考。

1】忠实传递价值,解读全球好书。
【2】你好,这里是喜马讲书,今天为你解读的书是稀缺。
【3】副标题是我们是如何陷入贫穷与忙碌的,贫穷与忙碌听上去毫无关系。
【4】一个人穷得没有钱吃饭,和一个人忙到没时间吃饭,难道是一回事吗?
【5】如果你深入探究,就能发现二者之间有一个共同点,那就是拥有少于需要的感觉。
【6】有意思的是,就算给忙碌的人更多的时间给穷人更多的钱,他们仍会处于忙碌和贫穷的状态中。
【7】这种现象就是我们今天要说的稀缺稀缺资源的稀缺。
【8】这里的稀缺不仅是指缺少时间和金钱这种实质上的资源的匮乏,还指一种心态比如说,老板要求你下周一要提交一份报告,现在已经是周六晚上了,你还没有开始做,于是你变得非常焦虑,决定周日哪儿都不去专心在家里赶报告。
【9】这时候任何事情都无法干扰你,这就是稀缺心态,就是当你缺少时间或者金钱时,注意力全部盯着眼下的某物或者某事的状态。
【10】这一现象是美国一个跨学科团队研究调査得出的,这项研究被誉为心理学行为经济学和政策研究学协作的典范。
【11】这个跨学科团队是由美国哈佛大学终身教授,经济学家塞德希尔,穆莱纳森团队和普林斯顿大学教授认知心理学家埃尔德沙菲尔团队组成的,。
【12】其中主导者是塞德希尔穆莱纳森。

之后,模型会做出如下的思考,判断每一行应该如何合并成段落,例如[1,2]#表示第1、2两行应该合并成一个段落:

1. **[1,2]**:
   - 句子【1】介绍了节目的宗旨,句子【2】则是节目的开场白,两者共同构成了节目的引入部分。

2. **[3,4,5]**:
   - 句子【3】提出了书的副标题,句子【4】进一步提问,句子【5】对问题进行了解答,这三句共同探讨了贫穷与忙碌的关系。

3. **[6,7]**:
   - 句子【6】描述了即使增加资源,状态依然不变的现象,句子【7】引出了“稀缺”这一核心概念,两者紧密相连。

4. **[8,9]**:
   - 句子【8】详细解释了“稀缺”的含义,句子【9】通过例子进一步阐述“稀缺心态”,两者共同深化了对“稀缺”的理解。

5. **[10,11,12,13]**:
   - 句子【10】介绍了研究的背景,句子【11】和【12】介绍了研究团队及其主导者,句子【13】详细描述了主导者的成就和研究的影响力,这四句共同构成了对研究团队的详细介绍。

6. **[14]**:
   - 句子【14】提出了本书要解答的两个核心问题,单独成段,引出下文的具体讨论。

7. **[15,16,17,18,19,20]**:
   - 句子【15】提出了第一个原因,句子【16】解释了“管窥”的含义,句子【17】提问什么是管窥效应,句子【18】和【19】通过例子解释了管窥效应,句子【20】提出了一个反问,这六句共同探讨了“管窥效应”这一概念。

### 最终输出
[1,2]#[3,4,5]#[6,7]#[8,9]#[10,11,12,13]#[14]#[15,16,17,18,19,20]

分段结果长这样,相关内容到了一个段落中:

忠实传递价值,解读全球好书。你好,这里是喜马讲书,今天为你解读的书是稀缺。

副标题是我们是如何陷入贫穷与忙碌的,贫穷与忙碌听上去毫无关系。一个人穷得没有钱吃饭,和一个人忙到没时间吃饭,难道是一回事吗?如果你深入探究,就能发现二者之间有一个共同点,那就是拥有少于需要的感觉。

有意思的是,就算给忙碌的人更多的时间给穷人更多的钱,他们仍会处于忙碌和贫穷的状态中。这种现象就是我们今天要说的稀缺稀缺资源的稀缺。

这里的稀缺不仅是指缺少时间和金钱这种实质上的资源的匮乏,还指一种心态比如说,老板要求你下周一要提交一份报告,现在已经是周六晚上了,你还没有开始做,于是你变得非常焦虑,决定周日哪儿都不去专心在家里赶报告。这时候任何事情都无法干扰你,这就是稀缺心态,就是当你缺少时间或者金钱时,注意力全部盯着眼下的某物或者某事的状态。

这一现象是美国一个跨学科团队研究调査得出的,这项研究被誉为心理学行为经济学和政策研究学协作的典范。这个跨学科团队是由美国哈佛大学终身教授,经济学家塞德希尔,穆莱纳森团队和普林斯顿大学教授认知心理学家埃尔德沙菲尔团队组成的,。其中主导者是塞德希尔穆莱纳森。

在会议纪要中,也可以把整理好的发言人段落进行标号,再让模型过滤短的段落、保留长段的精华内容。

基本处理代码:

input_file = r'E:\代码\喜马拉雅内容摘要\饱食穷民.txt'         # 原始文件路径
sentences_member = r'E:\代码\喜马拉雅内容摘要\饱食穷民_段落划分.txt'  # 输出文件路径

with open(input_file, 'r', encoding='utf-8') as f:
    lines = f.readlines()

with open(sentences_member, 'w', encoding='utf-8') as f:
    for idx, line in enumerate(lines, start=1):
        new_line = f'【{idx}{line}'
        f.write(new_line)

from zhipuai import ZhipuAI
client = ZhipuAI(api_key="xxx")  # 请填写您自己的APIKey

这段代码的功能,在提示词里写了:

sentences_member = r'E:\代码\喜马拉雅内容摘要\饱食穷民_段落划分.txt'  # 输出文件路径
sentences_merge = r'E:\代码\喜马拉雅内容摘要\饱食穷民_段落组合.txt'  # 输出文件路径
paragraphs = r'E:\代码\喜马拉雅内容摘要\饱食穷民_段落.txt'  # 输出文件路径


def mock_large_model(sentences: list[str], start_index: int) -> list[list[int]]:

    print("#############")
    content=["".join(sentences)]
    print(content)
    response = client.chat.completions.create(
        model="glm-4-plus",  # 请填写您要调用的模型名称
        messages=[
            {"role": "user", "content": """
    **任务**:
     请按照顺序将句子进行合并,不能打乱原始顺序。
     请输出你的思考过程,解释为什么相邻的句子可以划分到一个段落中。
    **输出要求**:
     在最后一行换行输出,仅输出由数字、“#”和“[”、“]”组成的内容,例如:
     [1,2]#[3,4,5]#[6,7,8]
    **原文**:
    {}""".format(content)},
        ],
    )
    print(response.choices[0].message.content)
    return response.choices[0].message.content.split("\n")[-1]

window_size = 20
stride = 20

with open(sentences_member, 'r', encoding='utf-8') as f:
    all_sentences = [line.strip() for line in f if line.strip()]

total_sentences = len(all_sentences)
current = 0

while current < total_sentences:
    window_sentences = all_sentences[current: current + window_size]
    if not window_sentences:
        break

    start_index = current + 1
    segmentation = mock_large_model(window_sentences, start_index)

    # 将分段结果写入输出文件
    with open(sentences_merge, 'a', encoding='utf-8') as f_out:
       
        f_out.write(segmentation)
        f_out.write("\n")

    current += stride

这段代码主要对[1,2]#[3,4,5],进行句子还原:

import re

def restore_by_structure(structure_file: str, x_file: str, output_file: str):
    # 读取原始句子
    with open(x_file, 'r', encoding='utf-8') as f:
        sentences = [line.strip() for line in f if line.strip()]

    # 编号 -> 内容(去掉【编号】)
    sentence_map = {}
    for line in sentences:
        match = re.match(r'^【(\d+)】\s*(.*)', line)
        if match:
            idx = int(match.group(1))
            content = match.group(2)
            sentence_map[idx] = content
        else:
            print(f"⚠️ 格式错误,跳过:{line}")

    # 读取段落结构
    with open(structure_file, 'r', encoding='utf-8') as f:
        structure_line = f.read().strip()

    # 解析结构:[1,2]#[3,4,5]
    raw_paragraphs = structure_line.split('#')
    paragraphs = []
    for group in raw_paragraphs:
        group = group.strip()
        if group.startswith('[') and group.endswith(']'):
            numbers = group[1:-1].split(',')
            try:
                ids = [int(num.strip()) for num in numbers if num.strip().isdigit()]
                paragraph = [sentence_map[i] for i in ids if i in sentence_map]
                paragraphs.append(paragraph)
            except Exception as e:
                print(f"⚠️ 错误解析段落:{group}, 错误:{e}")
        else:
            print(f"⚠️ 非法段落格式:{group}")

    # 写入输出
    with open(output_file, 'w', encoding='utf-8') as f:
        for paragraph in paragraphs:
            for sentence in paragraph:
                f.write(sentence)
            f.write('\n\n')  # 段落间空行

# 示例调用
restore_by_structure(sentences_merge, sentences_member, paragraphs)

步骤2

在这一步,主要是针对一个“语义单元”进行摘要,语义单元有可能是一个或多个段落,我在这里的提示词中,有这样一句:要求1:当你检测到话题发生变化时,就用“#”起一个新标题,用于结构划分。这是这个步骤的关键。

我还做了最大长度限制(基于段落,不会把一个段落拆成两个),1000字效果还可以。

这里没有用步骤1中的技巧,你可以加上,头几个段落的摘要如下:

**# 忠实传递价值,解读全球好书**

**# 稀缺:贫穷与忙碌的共同点**

- 贫穷与忙碌看似无关,但共同点是“拥有少于需要的感觉”。
- 稀缺不仅是资源匮乏,更是一种心态,如临近截止日期的焦虑。

**# 跨学科团队的研究**

- 美国跨学科团队(哈佛大学经济学家塞德希尔·穆莱纳森和普林斯顿大学认知心理学家埃尔德·沙菲尔)研究稀缺现象。
- 研究成果发布于阿斯彭论坛和美国科学杂志,塞德希尔入选美国总统财务咨询委员会。
- 书籍《稀缺》未出版即被评为年度必读商业书籍。

代码实现:

import time
import os

paragraphs = r'E:\代码\喜马拉雅内容摘要\饱食穷民_段落.txt'  # 输出文件路径
abstract_1 = r'E:\代码\喜马拉雅内容摘要\饱食穷民_摘要_1.txt'

MAX_CHARS = 1000 # 最大字符数限制

def simulate_large_model_call(sentences):

    print("#############")
    print(sentences)
    response = client.chat.completions.create(
        model="glm-4-plus",  # 请填写您要调用的模型名称
        messages=[
            {"role": "user", "content": """
**任务**:
写摘要。
**要求**:
要求1:当你检测到话题发生变化时,就用“#”起一个新标题,用于结构划分。
要求2:当你读到具体事例时,请尽可能的简化并保留原始信息。
要求3:当你读到有意义的分析、观点时,请写到摘要中。
**原文**:
    {}""".format(sentences)},
        ],
    )
    print("****************")
    print(response.choices[0].message.content)
    return response.choices[0].message.content


# --- 主处理逻辑 ---
def process_file(input_file, output_file, max_chars):
    """
    读取输入文件,按段落处理,调用模拟模型,并将结果写入输出文件。
    """
    try:
        # 使用 'utf-8' 编码打开文件,更具通用性
        with open(input_file, 'r', encoding='utf-8') as infile, \
             open(output_file, 'w', encoding='utf-8') as outfile:
            
            current_chunk = ""
            current_char_count = 0
            paragraph_separator = "\n\n" # 用于在块内连接段落的分隔符

            # 读取整个文件并按段落分割(假设段落由空行分隔)
            # 使用 filter(None, ...) 移除因多个空行产生的空字符串
            paragraphs = filter(None, infile.read().split('\n\n'))

            for paragraph in paragraphs:
                paragraph = paragraph.strip() # 移除段落首尾可能存在的空白字符
                if not paragraph: # 如果处理后段落为空,则跳过
                    continue

                paragraph_char_count = len(paragraph)
                separator_len = len(paragraph_separator) if current_chunk else 0 # 只有当块非空时才需要分隔符

                # 检查:如果当前块非空,并且加入这个新段落(和分隔符)会超过限制
                if current_chunk and (current_char_count + separator_len + paragraph_char_count > max_chars):
                    # --- 达到限制,处理当前块 ---
                    print(f"块达到限制 ({current_char_count} chars),准备发送...")
                    model_response = simulate_large_model_call(current_chunk)
                    outfile.write(model_response + "\n") # 写入返回结果,并加一个换行符
                    outfile.flush() # 确保内容写入磁盘

                    # --- 重置块,并将当前段落作为新块的开始 ---
                    print(f"重置块,以新段落 (长度 {paragraph_char_count}) 开始。")
                    current_chunk = paragraph
                    current_char_count = paragraph_char_count
                
                # 检查:如果当前段落本身就超过了限制(即使块是空的)
                # 或者,如果当前块是空的,但这个段落小于等于限制
                # 或者,如果当前块非空,且加上这个段落不超限制
                elif paragraph_char_count > max_chars and not current_chunk:
                     # 如果一个段落本身就超限了,并且当前块是空的,直接处理这个段落
                    print(f"单个段落超限 ({paragraph_char_count} chars > {max_chars}),直接处理...")
                    model_response = simulate_large_model_call(paragraph)
                    outfile.write(model_response + "\n")
                    outfile.flush()
                    # 块保持为空
                    current_chunk = ""
                    current_char_count = 0
                else:
                    # --- 未达到限制,将当前段落添加到块中 ---
                    if current_chunk: # 如果块非空,先加分隔符再加段落
                        current_chunk += paragraph_separator + paragraph
                        current_char_count += separator_len + paragraph_char_count
                    else: # 如果块是空的,直接设置为当前段落
                        current_chunk = paragraph
                        current_char_count = paragraph_char_count
                    # print(f"段落已添加,当前块长度: {current_char_count}") # 可选:取消注释以查看累积过程

            # --- 文件读取完毕,处理缓冲区中剩余的任何内容 ---
            if current_chunk:
                print(f"文件读取完毕,处理最后剩余的块 ({current_char_count} chars)...")
                model_response = simulate_large_model_call(current_chunk)
                outfile.write(model_response + "\n")
                outfile.flush()

        print(f"\n处理完成!结果已写入 '{output_file}'。")

    except FileNotFoundError:
        print(f"错误:输入文件 '{input_file}' 未找到。")
    except Exception as e:
        print(f"处理过程中发生错误:{e}")


if __name__ == "__main__":
    process_file(paragraphs, abstract_1, MAX_CHARS)

步骤3

这一步在第二步的基础上再一次摘要,原因是第二步做了切割,有可能导致两个相关的段落被分开,这里再做一次融合。如果对于效果可以接受,那么不做这步也可以。同样,没有使用步骤1的技巧,你可以加上。
效果(这里展示正文的一个段落吧,不展示开头的内容了):

在扶贫方面,国家政策支持至关重要,但简单分钱无效。例如,美国福利救济限制领取次数,因穷人常用于解决眼前问题,而非长远规划。扶贫项目效果不佳的原因在于未解决根本问题,建议改进策略,注重提升技能和找稳定工作。

**稀缺心态与扶贫激励**:
研究显示,政府免费扶贫项目效果不佳,因学员稀缺心态导致低出勤和高退课。建议政府利用稀缺心态设计激励政策,如印度用扁豆奖励疫苗接种,效果显著。

**避免稀缺的金融措施**:
穷人易因小事件陷入稀缺,忽视预见性问题。建议政府提供金融产品,帮助构建储蓄余闲,或将丰收收入平摊至每月。

**简化财务节约带宽**:
经济学家在多米尼克帮助小商贩简化财务,存现金入收银机,每月固定工资,节约带宽。

**企业余闲管理**:
企业和组织应加强余闲管理,避免满负荷工作问题,类比道路70%占用率时事故致交通瘫痪。

代码实现(除了提示词,基本都一样):

# 对完成段落划分的内容进行第一次摘要
abstract_1 = r'E:\代码\喜马拉雅内容摘要\饱食穷民_摘要_1.txt'  # 输出文件路径
abstract_2 = r'E:\代码\喜马拉雅内容摘要\饱食穷民_摘要_2.txt'

MAX_CHARS = 500 # 最大字符数限制,这里最好短一些

def extract_answer_content(text):
  """
  使用正则表达式提取字符串中所有被 ... 包裹的内容。

  Args:
    text (str): 包含  标签的输入字符串。

  Returns:
    list: 一个包含所有提取到的内容的列表(字符串形式)。
          如果找不到匹配项,则返回空列表。
  """
  pattern_ignore_case = r"(.*?)"
  matches = re.findall(pattern_ignore_case, text, re.DOTALL | re.IGNORECASE)
  return matches

def simulate_large_model_call(sentences):

    print("#############")
    print(sentences)
    response = client.chat.completions.create(
        model="glm-4-plus",  # 请填写您要调用的模型名称
        messages=[
            {"role": "user", "content": """
**任务**:
逐段阅读文本,合并、化简。

**要求**
要求1:思考每一个段落是否有可能与其他块合并。如果合并,请保留原始信息。
要求2:简化每个段落的表述,保留核心信息。

**步骤**:
步骤1:请先展示你的思考过程,分析每个段落的内容,然后判断是否需要执行要求1和要求2。思考、判断中不要引述原文,请展示你简明的思考过程。
步骤2:最后统一输出修改后的内容。为了便于用户切分内容,请将这一段内容用包裹
    {}""".format(sentences)},
        ],
    )
    print("****************")
    print(extract_answer_content(response.choices[0].message.content))
    return extract_answer_content(response.choices[0].message.content)[0]


# --- 主处理逻辑 ---
def process_file(input_file, output_file, max_chars):
    """
    读取输入文件,按段落处理,调用模拟模型,并将结果写入输出文件。
    """
    try:
        # 使用 'utf-8' 编码打开文件,更具通用性
        with open(input_file, 'r', encoding='utf-8') as infile, \
             open(output_file, 'w', encoding='utf-8') as outfile:
            
            current_chunk = ""
            current_char_count = 0
            paragraph_separator = "\n\n" # 用于在块内连接段落的分隔符

            # 读取整个文件并按段落分割(假设段落由空行分隔)
            # 使用 filter(None, ...) 移除因多个空行产生的空字符串
            paragraphs = filter(None, infile.read().split('\n\n'))

            for paragraph in paragraphs:
                paragraph = paragraph.strip() # 移除段落首尾可能存在的空白字符
                if not paragraph: # 如果处理后段落为空,则跳过
                    continue

                paragraph_char_count = len(paragraph)
                separator_len = len(paragraph_separator) if current_chunk else 0 # 只有当块非空时才需要分隔符

                # 检查:如果当前块非空,并且加入这个新段落(和分隔符)会超过限制
                if current_chunk and (current_char_count + separator_len + paragraph_char_count > max_chars):
                    # --- 达到限制,处理当前块 ---
                    print(f"块达到限制 ({current_char_count} chars),准备发送...")
                    model_response = simulate_large_model_call(current_chunk)
                    outfile.write(model_response + "\n") # 写入返回结果,并加一个换行符
                    outfile.flush() # 确保内容写入磁盘

                    # --- 重置块,并将当前段落作为新块的开始 ---
                    print(f"重置块,以新段落 (长度 {paragraph_char_count}) 开始。")
                    current_chunk = paragraph
                    current_char_count = paragraph_char_count
                
                # 检查:如果当前段落本身就超过了限制(即使块是空的)
                # 或者,如果当前块是空的,但这个段落小于等于限制
                # 或者,如果当前块非空,且加上这个段落不超限制
                elif paragraph_char_count > max_chars and not current_chunk:
                     # 如果一个段落本身就超限了,并且当前块是空的,直接处理这个段落
                    print(f"单个段落超限 ({paragraph_char_count} chars > {max_chars}),直接处理...")
                    model_response = simulate_large_model_call(paragraph)
                    outfile.write(model_response + "\n")
                    outfile.flush()
                    # 块保持为空
                    current_chunk = ""
                    current_char_count = 0
                else:
                    # --- 未达到限制,将当前段落添加到块中 ---
                    if current_chunk: # 如果块非空,先加分隔符再加段落
                        current_chunk += paragraph_separator + paragraph
                        current_char_count += separator_len + paragraph_char_count
                    else: # 如果块是空的,直接设置为当前段落
                        current_chunk = paragraph
                        current_char_count = paragraph_char_count
                    # print(f"段落已添加,当前块长度: {current_char_count}") # 可选:取消注释以查看累积过程

            # --- 文件读取完毕,处理缓冲区中剩余的任何内容 ---
            if current_chunk:
                print(f"文件读取完毕,处理最后剩余的块 ({current_char_count} chars)...")
                model_response = simulate_large_model_call(current_chunk)
                outfile.write(model_response + "\n")
                outfile.flush()

        print(f"\n处理完成!结果已写入 '{output_file}'。")

    except FileNotFoundError:
        print(f"错误:输入文件 '{input_file}' 未找到。")
    except Exception as e:
        print(f"处理过程中发生错误:{e}")


if __name__ == "__main__":
    process_file(abstract_1, abstract_2, MAX_CHARS)

与会议摘要的联系

会议摘要与发言人相关,且在我们的场景中,需要给出每个人的意见、观点、结论和未来具体工作等,甚至还要做出脑图,这需要把好多功能结合到一起做了。

人工撰写都需要好多次润色修改,因此我觉得需要好好设计一下流程。

你可能感兴趣的:(大模型,数据挖掘,数据挖掘,自然语言处理)