大模型生成PPT大纲优化方案:基于 nVidia NIM 平台的递归结构化生成
待解决的问题
生成PPT大纲是一种大模型在办公场景下应用的常见需求。
然而:
- 目前直接让大模型生成大纲往往是非结构化的,输出格式多样,难以统一和规范,这使得大纲后续的分析与利用变得复杂。
- 生成过程中的层级深度和每层的颗粒度无法灵活控制,导致生成内容有时过于简单,有时又过于复杂,缺乏可控性。
- 由于大模型是基于自回归的,在生成细粒度大纲时首层大纲并没有生成完,和人类规划大纲自顶向下的方法不同,导致大纲的层级之间往往存在模糊和重叠,无法有效传达不同层次的重点内容。
为了解决上述问题,本文提出了一种递归结构化生成的方法,通过分层递归控制大模型生成大纲的过程,不仅能使大纲结构清晰,而且可以灵活控制各层的深度和颗粒度。这样生成的大纲不仅层次分明,内容也更加贴合实际应用需求,避免了层级模糊和内容重叠的现象。
本项目基于NVIDIA NIM平台,使用 LLaMA 3.1 70B 作为基座模型。NVIDIA NIM 平台提供了强大的自然语言处理等人工智能能力,能够方便、高效地调用各种预训练模型,甚至进行定制化的推理。
核心方案
该项目的核心是递归生成PPT大纲,逐层递进,确保大纲的层次清晰、内容具体,避免生成结果的模糊和重叠问题。核心流程如下:
- 第一层生成:首先,通过模型生成第一层的PPT大纲内容,确保初始大纲框架具有明确的层次。
- 递归生成:在生成初始层之后,递归生成每个子级的内容,逐层递进。每一级的生成都是基于上一层的大纲内容进行细化和补充,保证了层次结构的清晰和逻辑性。
- 递归终止条件:当生成到达指定的层级时,递归终止,输出完整的结构化大纲。
具体的 prompt 将在下一节讲解。
第一层大纲生成 (generate_first_level_outlines
)
这个函数负责生成大纲的第一层内容,基于用户输入的主题调用大模型生成初始的大纲框架。
python
def generate_first_level_outlines(idea):
first_level_template = FIRST_LEVEL_PROMPT.format(
few_shot=FIRST_LEVEL_EXAMPLE,
idea=idea,
)
response = client.chat.completions.create(
model="meta/llama-3.1-70b-instruct",
messages=[{"role": "user", "content": first_level_template}],
temperature=0.7,
max_tokens=1024,
)
return extract_outlines(response.choices[0].message.content.strip())
注意,调用模型后,通过extract_outlines
函数对生成的结果进行处理,去掉不必要的空白行,得到大纲的第一层结构化内容。extract_outlines
函数具体详见下一节。
递归生成下一级大纲 (generate_next_level_outline
)
当生成了第一层的大纲后,系统需要递归生成各个子级内容。generate_next_level_outline
函数根据上一层的大纲内容,生成当前层的细化内容。
模型生成后,如果当前层级达到了目标层(target_layer),则返回当前层的大纲内容。否则,函数将继续递归调用自己,为每个子项生成更深层的大纲内容,直到达到目标层。
python
def generate_next_level_outline(
idea: str,
current_level: int,
previous_outline: str,
previous_list: list[str],
target_layer: int,
):
formatted_previous_list = ', '.join(previous_list)
next_level_template = NEXT_LEVEL_PROMPT.format(
few_shot=NEXT_LEVEL_EXAMPLE,
idea=idea,
formatted_previous_list=formatted_previous_list,
previous_outline=previous_outline,
current_level=current_level,
)
response = client.chat.completions.create(
model="meta/llama-3.1-70b-instruct",
messages=[{"role": "user", "content": next_level_template}],
temperature=0.7,
max_tokens=1024,
)
outlines = extract_outlines(response.choices[0].message.content.strip())
if target_layer == current_level:
return outlines
else:
return {
text: generate_next_level_outline(
idea, current_level + 1, text, outlines, target_layer
) for text in outlines
}
注意,prompt 中会将上一级的内容和当前的层次信息整合,让模型有更充分的信息生成该层的大纲内容。
递归结构化大纲生成 (recursive_outline
)
这个函数是整个递归生成过程的核心,通过逐层调用generate_next_level_outline
函数,生成每一层的大纲,直到生成目标层的所有内容。
python
def recursive_outline(idea, target_layer: int):
first_level = generate_first_level_outlines(idea)
return {
text: generate_next_level_outline(
idea, 2, text, first_level, target_layer
) for text in first_level
}
实现细节
prompt
本项目的 prompt 明确告知模型生成PPT大纲的任务,提供主题信息(idea)和所需层级(layer),确保模型能够聚焦于特定主题,并生成相应层次的内容。此外,通过提供多个生成示例,向模型展示了预期的输出格式和内容结构。通过示例提供了明确的上下文,同时引入了对重复内容的避免,还引入了上一层的内容,帮助模型了解需要避免重复的信息。
python
BASE_PROMPT = """请你根据主题:"{idea}"生成{layer}层的PPT大纲:\n"""
FIRST_LEVEL_EXAMPLE = """请你根据
技术是一把双刃剑,它既能推动社会向前发展,也可能因不当使用而造成伤害。比如数字鸿沟现象。
生成大纲第1层,一行一个条目,生成在<>中,无需其他解释和内容:
<信息爆炸的时代>
<科技革命与产业变革>
<社会影响日益凸显>
<人类应未雨绸缪,趋利避害>
<发展趋势及前景,结论>
请你根据
为什么我们要好好学习?在快速变化的世界中,持续学习是实现自我价值和社会贡献的关键。
生成大纲第1层,一行一个条目,生成在<>中,无需其他解释和内容:
<引言>
<学习对个人成长的意义>
<学习对社会的责任>
<如何有效学习>
请你根据
在单身的黄金年代我们如何面对爱情:要勇敢地去追求爱情
生成大纲第1层,一行一个条目,生成在<>中,无需其他解释和内容:
<何为单身的黄金时代>
<爱情对人们的意义>
<目前人的生存和处境更加复杂>
<爱情存在肌无力>
<最温暖的东西是人和人之间的感情>
<要勇敢地追求爱情>
"""
FIRST_LEVEL_PROMPT = """{few_shot}
上面是几个生成大纲第一层的例子。请你根据
{idea}
生成大纲第1层,一行一个条目,生成在<>中,无需其他解释和内容:"""
NEXT_LEVEL_EXAMPLE = """请你根据
技术是一把双刃剑,它既能推动社会向前发展,也可能因不当使用而造成伤害。比如数字鸿沟现象。
不要和上一层的 信息爆炸的时代, 科技革命与产业变革, 人类应未雨绸缪,趋利避害, 发展趋势及前景,结论 重复,
生成"社会影响日益凸显"下的大纲第2层内容,一行一个条目,生成在<>中,无需其他解释和内容:
<举例:数字鸿沟现象>
<各种问题普遍存在>
<对人类各方面产生深远影响>
请你根据
在单身的黄金年代我们如何面对爱情:要勇敢地去追求爱情
不要和上一层的 何为单身的黄金时代, 爱情对人们的意义, 目前人的生存和处境更加复杂, 爱情存在肌无力, 最温暖的东西是人和人之间的感情 重复,
生成"要勇敢地追求爱情"下的大纲第2层内容,一行一个条目,生成在<>中,无需其他解释和内容:
<必须树立目标>
<提升心灵的力量>
<走合法的途径去获取>
请你根据
为什么我们要好好学习?在快速变化的世界中,持续学习是实现自我价值和社会贡献的关键。
不要和上一层的 理解自己的权利与义务, 正确价值观和社会责任感 重复,
生成"知识是推动社会发展的关键力量"下的大纲第3层内容,一行一个条目,生成在<>中,无需其他解释和内容:
<科技创新与经济发展>
<社会治理与公共政策>
<文化传承与发展>
"""
NEXT_LEVEL_PROMPT = """{few_shot}
上面是几个生成大纲具体层的例子。请你根据
{idea}
不要和上一层的 {formatted_previous_list} 重复,
生成"{previous_outline}"下的大纲第{current_level}层内容,一行一个条目,生成在<>中,无需其他解释和内容:
"""
大纲文本提取
简单的提取方法,将模型输出按行分割并去除空白行和格式字符。
python
def extract_outlines(model_output: str):
return [
line.strip().strip('<').strip('>')
for line in model_output.split('\n')
if line.strip()
]
baseline 对比、全局变量和简易用户界面
简易的 web 用户界面是基于 gradio 构建的。复现时记得更换为自己的NVIDIA_API_KEY
。
借助Gradio构建用户友好的交互界面,用户只需输入一个主题和层数,本项目会生成两个版本的PPT大纲,一个是直接生成的,一个是递归生成的,并将两者进行对比展示。
大纲生成的基本过程通过调用 nVidia NIM 平台 的接口,将主题与层级信息传入模型生成器,控制输出大纲的层级深度和层次内容。
python
import gradio as gr
from openai import OpenAI
import json
client = OpenAI(
base_url="https://integrate.api.nvidia.com/v1",
api_key=NVIDIA_API_KEY
)
def generate_outline(input_text, layer: int):
prompt = BASE_PROMPT.format(idea=input_text, layer=layer)
completion = client.chat.completions.create(
model="meta/llama-3.1-70b-instruct",
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
max_tokens=1024,
)
return completion.choices[0].message.content.strip()
def compare_outlines(input_text, layers):
direct = generate_outline(input_text, int(layers))
recursive = recursive_outline(input_text, int(layers))
return direct, json.dumps(recursive, ensure_ascii=False, indent=8)
iface = gr.Interface(
fn=compare_outlines,
inputs=["text", gr.Number()],
outputs=["text", "text"],
title="PPT大纲生成对比",
description="输入一个句子,比较直接生成和递归生成的PPT大纲效果。"
)
iface.launch()
项目效果
运行后,在浏览器中打开项目前端,输入要生成的主题和期望的层数,可以得到普通方法生成和本方法生成的结果。
如图所示,递归结构化生成的效果不仅结构清晰、方便利用,而且相对于普通方法,生成了正确的层次深度,大纲对于主题的分析更加深入和全面,有更多的信息量和参考价值。
通过采用递归结构化生成的方法,本项目能够有效地解决传统PPT大纲生成中遇到的各种问题,为用户提供更清晰、有条理的内容。希望本项目的实践经验能够为更多相关应用的探索与优化提供启示和借鉴。
完整代码:
python
import gradio as gr
from openai import OpenAI
from __env import NVIDIA_API_KEY
import json
client = OpenAI(
base_url="https://integrate.api.nvidia.com/v1",
api_key=NVIDIA_API_KEY
)
BASE_PROMPT = """请你根据主题:"{idea}"生成{layer}层的PPT大纲:\n"""
FIRST_LEVEL_EXAMPLE = """请你根据
技术是一把双刃剑,它既能推动社会向前发展,也可能因不当使用而造成伤害。比如数字鸿沟现象。
生成大纲第1层,一行一个条目,生成在<>中,无需其他解释和内容:
<信息爆炸的时代>
<科技革命与产业变革>
<社会影响日益凸显>
<人类应未雨绸缪,趋利避害>
<发展趋势及前景,结论>
请你根据
为什么我们要好好学习?在快速变化的世界中,持续学习是实现自我价值和社会贡献的关键。
生成大纲第1层,一行一个条目,生成在<>中,无需其他解释和内容:
<引言>
<学习对个人成长的意义>
<学习对社会的责任>
<如何有效学习>
请你根据
在单身的黄金年代我们如何面对爱情:要勇敢地去追求爱情
生成大纲第1层,一行一个条目,生成在<>中,无需其他解释和内容:
<何为单身的黄金时代>
<爱情对人们的意义>
<目前人的生存和处境更加复杂>
<爱情存在肌无力>
<最温暖的东西是人和人之间的感情>
<要勇敢地追求爱情>
"""
FIRST_LEVEL_PROMPT = """{few_shot}
上面是几个生成大纲第一层的例子。请你根据
{idea}
生成大纲第1层,一行一个条目,生成在<>中,无需其他解释和内容:"""
NEXT_LEVEL_EXAMPLE = """请你根据
技术是一把双刃剑,它既能推动社会向前发展,也可能因不当使用而造成伤害。比如数字鸿沟现象。
不要和上一层的 信息爆炸的时代, 科技革命与产业变革, 人类应未雨绸缪,趋利避害, 发展趋势及前景,结论 重复,
生成"社会影响日益凸显"下的大纲第2层内容,一行一个条目,生成在<>中,无需其他解释和内容:
<举例:数字鸿沟现象>
<各种问题普遍存在>
<对人类各方面产生深远影响>
请你根据
在单身的黄金年代我们如何面对爱情:要勇敢地去追求爱情
不要和上一层的 何为单身的黄金时代, 爱情对人们的意义, 目前人的生存和处境更加复杂, 爱情存在肌无力, 最温暖的东西是人和人之间的感情 重复,
生成"要勇敢地追求爱情"下的大纲第2层内容,一行一个条目,生成在<>中,无需其他解释和内容:
<必须树立目标>
<提升心灵的力量>
<走合法的途径去获取>
请你根据
为什么我们要好好学习?在快速变化的世界中,持续学习是实现自我价值和社会贡献的关键。
不要和上一层的 理解自己的权利与义务, 正确价值观和社会责任感 重复,
生成"知识是推动社会发展的关键力量"下的大纲第3层内容,一行一个条目,生成在<>中,无需其他解释和内容:
<科技创新与经济发展>
<社会治理与公共政策>
<文化传承与发展>
"""
NEXT_LEVEL_PROMPT = """{few_shot}
上面是几个生成大纲具体层的例子。请你根据
{idea}
不要和上一层的 {formatted_previous_list} 重复,
生成"{previous_outline}"下的大纲第{current_level}层内容,一行一个条目,生成在<>中,无需其他解释和内容:
"""
def generate_outline(input_text, layer: int):
prompt = BASE_PROMPT.format(idea=input_text, layer=layer)
completion = client.chat.completions.create(
model="meta/llama-3.1-70b-instruct",
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
max_tokens=1024,
)
return completion.choices[0].message.content.strip()
def extract_outlines(model_output: str):
# 简单的提取方法,将模型输出按行分割并去除空白行
return [
line.strip().strip('<').strip('>')
for line in model_output.split('\n')
if line.strip()
]
def generate_first_level_outlines(idea):
first_level_template = FIRST_LEVEL_PROMPT.format(
few_shot=FIRST_LEVEL_EXAMPLE,
idea=idea,
)
response = client.chat.completions.create(
model="meta/llama-3.1-70b-instruct",
messages=[{"role": "user", "content": first_level_template}],
temperature=0.7,
max_tokens=1024,
)
return extract_outlines(response.choices[0].message.content.strip())
def generate_next_level_outline(
idea: str,
current_level: int,
previous_outline: str,
previous_list: list[str],
target_layer: int,
):
formatted_previous_list = ', '.join(previous_list)
next_level_template = NEXT_LEVEL_PROMPT.format(
few_shot=NEXT_LEVEL_EXAMPLE,
idea=idea,
formatted_previous_list=formatted_previous_list,
previous_outline=previous_outline,
current_level=current_level,
)
response = client.chat.completions.create(
model="meta/llama-3.1-70b-instruct",
messages=[{"role": "user", "content": next_level_template}],
temperature=0.7,
max_tokens=1024,
)
outlines = extract_outlines(response.choices[0].message.content.strip())
if target_layer == current_level:
return outlines
else:
return {
text: generate_next_level_outline(
idea, current_level + 1, text, outlines, target_layer
) for text in outlines
}
def recursive_outline(idea, target_layer: int):
first_level = generate_first_level_outlines(idea)
return {
text: generate_next_level_outline(
idea, 2, text, first_level, target_layer
) for text in first_level
}
def compare_outlines(input_text, layers):
direct = generate_outline(input_text, int(layers))
recursive = recursive_outline(input_text, int(layers))
return direct, json.dumps(recursive, ensure_ascii=False, indent=8)
iface = gr.Interface(
fn=compare_outlines,
inputs=["text", gr.Number()],
outputs=["text", "text"],
title="PPT大纲生成对比",
description="输入一个句子,比较直接生成和递归生成的PPT大纲效果。"
)
iface.launch()