Qwen/QwQ-32B 基础模型上构建agent实现ppt自动生成
关心Qwen/QwQ-32B 性能测试结果可以参考下
https://zhuanlan.zhihu.com/p/28600079208https://zhuanlan.zhihu.com/p/28600079208
官方宣传上是该模型性能比肩满血版 DeepSeek-R1(671B)!
我们实现一个 使用Qwen/QwQ-32B 自动生成 PowerPoint 演示文稿的需求,基于 Qwen/QwQ-32B 的自动化 PPT 生成 Agent,带有一个简单的用户界面。以下是一个使用 Python、Gradio(用于界面)和 python-pptx(用于生成 PPT)的实现方案,先利用提示词工程实现一个简单的ppt生成
-
前提条件
- 你已经下载了 Qwen/QwQ-32B 权重到 ./qwq_32b 目录。
- 确保有足够的 GPU 内存(建议至少 24GB VRAM)来加载 32B 模型。
-
系统提示:
- 设计一个系统提示,指导模型如何生成内容。该提示应包含结构化的内容格式,例如:
- 第一张幻灯片为标题幻灯片(包含标题和副标题)。
- 后续幻灯片为内容幻灯片(包含标题和要点)。
- 设计一个系统提示,指导模型如何生成内容。该提示应包含结构化的内容格式,例如:
-
内容生成流程
-
消息构建:
- 将系统提示与用户提示结合,形成完整的输入消息列表。
-
文本生成:
- 使用分词器将消息转换为模型输入格式,然后调用模型生成文本。
- 设置生成参数(如最大新令牌数、温度等)来控制生成内容的质量和多样性。
PPT内容处理
-
解析生成的内容:
- 将模型生成的文本分割为不同的幻灯片,识别标题幻灯片和内容幻灯片。
-
创建PPT文件:
- 使用
python-pptx
库创建 PowerPoint 演示文稿,根据解析的内容添加幻灯片。
- 使用
- 保存PPT文件:
- 将生成的PPT文件保存到指定的位置,方便用户下载和使用。
def generate_ppt_content(topic, slide_count=3):
system_prompt = (
"You are Qwen, developed by Alibaba. You are tasked with creating content for a PowerPoint presentation. "
"Generate a structured response with a title slide and subsequent slides based on the user's topic. "
f"Provide content for {slide_count} slides in the following format:\n"
"Slide 1: Title Slide (Title, Subtitle)\n"
f"Slide 2 to {slide_count}: Content Slide (Slide Title, Bullet Points)\n"
"Keep each slide concise and professional."
)
user_prompt = f"Generate a PPT about '{topic}'."
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
model_inputs = tokenizer([text], return_tensors="pt").to("cuda") # 输入放到 GPU
generated_ids = model.generate(
model_inputs.input_ids,
max_new_tokens=512,
temperature=0.7,
repetition_penalty=1.2
)
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in
zip(model_inputs.input_ids, generated_ids)]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
return response
处理流程概述:
- 定义了一个结构化的提示,用于指示模型(Qwen)预期的输出格式。它具体说明了输出的格式要求,包括标题幻灯片和内容幻灯片。
- 这将系统提示与请求生成指定主题的演示文稿结合在一起。
- 将提示进行标记化,意味着将文本转换为模型可以理解的格式。这涉及将文本转换为数字表示(标记)。
- 将标记化的输入准备好,移动到GPU以加快处理速度。
- 模型根据输入提示生成响应。参数max_new_tokens、temperature和repetition_penalty决定生成的
- max_new_tokens:模型可以一次生成的最大标记数(这里设置为512)。
- temperature:控制输出的随机性。较低的值使输出更确定,而较高的值增加创造性。
- repetition_penalty:抑制模型重复短语或想法,促进内容的多样性。
将生成的标记ID解码回人类可读的文本格式,排除模型使用的任何特殊标记。
初步运行下报错了
torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate 270.00 MiB. GPU 0 has a total capacity of 23.53 GiB of which 158.56 MiB is free. Including non-PyTorch memory, this process has 23.36 GiB memory in use. Of the allocated memory 22.99 GiB is allocated by PyTorch, and 1.24 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables)
机器有两张 NVIDIA 4090 显卡,每张显存 24GB,但当前模型加载时超出了单张 GPU 的显存容量(23.53GiB 已几乎用尽)。由于 Qwen/QwQ-32B 是一个非常大的模型(未经量化可能需要 60GB+ 显存),单张 24GB 显卡无法完整加载,因此需要利用多卡并行加载模型。
以下是实现多卡运行的解决方案,结合你的硬件(2 张 4090,48GB 总显存):
首先确认你的环境支持多卡:
import torch
print("CUDA available:", torch.cuda.is_available())
print("Device count:", torch.cuda.device_count())
for i in range(torch.cuda.device_count()):
print(f"GPU {i}: {torch.cuda.get_device_name(i)}, {torch.cuda.get_device_properties(i).total_memory / 1024**3:.2f} GB")
使用 accelerate 的多卡支持
transformers 和 accelerate 提供了内置的多卡支持,通过 device_map="auto" 自动将模型分层分配到多个 GPU 上。
# 1. 加载模型和分词器
model_path = "./qwq_32b"
tokenizer = AutoTokenizer.from_pretrained(model_path)
# 使用 accelerate 的多卡加载
model = AutoModelForCausalLM.from_pretrained(
model_path,
torch_dtype=torch.float16, # 使用 FP16 减少显存占用
device_map="auto", # 自动分配到多张 GPU
low_cpu_mem_usage=True # 优化内存使用
)
print("Model loaded successfully across multiple GPUs!")
优化显存使用
如果仍然遇到显存不足,可以进一步优化:
1. 使用 4-bit 量化
安装 bitsandbytes:
pip install bitsandbytes
修改模型加载部分:
model = AutoModelForCausalLM.from_pretrained(
model_path,
torch_dtype=torch.float16,
device_map="auto",
load_in_4bit=True, # 4-bit 量化
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True # 双重量化
)
- 显存需求降至约 20-24GB,两张 4090 完全够用。
- 可能略微影响生成质量,但对 PPT 内容生成影响不大。
设置环境变量
错误信息提到 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,可以尝试设置以优化内存分配:
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
或者代码上添加
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
修改后代码
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import gradio as gr
from pptx import Presentation
import os
# 1. 加载模型和分词器
model_path = "./base_model/qwq_32b"
tokenizer = AutoTokenizer.from_pretrained(model_path)
# 使用 accelerate 的多卡加载
model = AutoModelForCausalLM.from_pretrained(
model_path,
torch_dtype=torch.float16,
device_map="auto",
load_in_4bit=True, # 启用 4-bit 量化
bnb_4bit_compute_dtype=torch.float16, # 计算用 FP16
bnb_4bit_use_double_quant=True # 双重量化,进一步节省显存
)
print("Model loaded successfully across multiple GPUs!")
# 2. 生成 PPT 内容的函数
def generate_ppt_content(topic, slide_count=3):
system_prompt = (
"You are Qwen, developed by Alibaba. You are tasked with creating content for a PowerPoint presentation. "
"Generate a structured response with a title slide and subsequent slides based on the user's topic. "
f"Provide content for {slide_count} slides in the following format:\n"
"Slide 1: Title Slide (Title, Subtitle)\n"
f"Slide 2 to {slide_count}: Content Slide (Slide Title, Bullet Points)\n"
"Keep each slide concise and professional."
)
user_prompt = f"Generate a PPT about '{topic}'."
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
model_inputs = tokenizer([text], return_tensors="pt").to("cuda") # 输入放到 GPU
generated_ids = model.generate(
model_inputs.input_ids,
max_new_tokens=512,
temperature=0.7,
repetition_penalty=1.2
)
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in
zip(model_inputs.input_ids, generated_ids)]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
return response
# 3. 创建 PPT 文件
def create_ppt(topic, content):
prs = Presentation()
slides = content.split("Slide")[1:]
for slide_text in slides:
slide_lines = slide_text.strip().split("\n")
if not slide_lines:
continue
if "1:" in slide_text:
slide_layout = prs.slide_layouts[0]
slide = prs.slides.add_slide(slide_layout)
title, subtitle = slide.shapes.title, slide.placeholders[1]
title.text = slide_lines[0].replace("1: Title Slide", "").strip()
subtitle.text = slide_lines[1].strip() if len(slide_lines) > 1 else " "
else:
slide_layout = prs.slide_layouts[1]
slide = prs.slides.add_slide(slide_layout)
title, body = slide.shapes.title, slide.placeholders[1]
title.text = slide_lines[0].strip()
tf = body.text_frame
tf.text = "\n".join(line.strip() for line in slide_lines[1:] if line.strip())
output_file = f"{topic.replace(' ', '_')}_presentation.pptx"
prs.save(output_file)
return output_file
# 4. 主函数
def ppt_agent(topic):
content = generate_ppt_content(topic)
output_file = create_ppt(topic, content)
return content, output_file
# 5. Gradio 界面
with gr.Blocks(title="PPT 生成 Agent") as demo:
gr.Markdown("# 自动化 PPT 生成器")
gr.Markdown("输入主题,生成 PPT 文件。基于 Qwen/QwQ-32B,多卡运行。")
topic_input = gr.Textbox(label="PPT 主题", placeholder="例如:人工智能简介")
output_text = gr.Textbox(label="生成的内容", lines=10, interactive=False)
output_file = gr.File(label="下载 PPT 文件")
submit_btn = gr.Button("生成 PPT")
submit_btn.click(fn=ppt_agent, inputs=topic_input, outputs=[output_text, output_file])
demo.launch(server_name="0.0.0.0", server_port=7860)
运行界面如下
点击“生成PPT”
但是ppt质量有点堪忧
生成的 PPT 内容确实存在以下问题:
- 内容质量低:生成的文本包含不自然的表达(如 "I don’t have the prime data regarding from common knowledge up until now"),语法错误较多,逻辑也不够清晰。
- 格式问题:内容没有很好地适配 PPT 的结构,段落过长,缺乏简洁的要点化表达。
- 语言混杂:中英文混杂(如标题是中文,内容是英文),缺乏统一性。
为了提升 PPT 的质量,引入多个智能体的协作模式,利用联网功能获取更高质量的内容,并优化 PPT 的结构和格式。改进方案:
多智能体协作生成高质量 PPT
智能体角色分工
- 内容生成智能体:基于 QwQ-32B 模型,负责生成 PPT 的初步内容(已实现)。
- 内容优化智能体:联网搜索最新信息,修正生成内容中的错误,优化语言表达。
- 格式设计智能体:优化 PPT 结构,确保内容简洁、要点清晰,并适配 PPT 格式。
- 视觉美化智能体:提供 PPT 排版建议,优化字体、颜色、布局等(目前无法直接生成图片,但可以提供设计建议)。
改进步骤
- 优化内容生成:调整提示词,提升 QwQ-32B 的输出质量。
- 联网搜索补充:搜索与主题相关的最新信息,修正模型生成的内容。
- 结构化输出:将内容转为简洁的要点形式,适配 PPT。
修改代码
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import gradio as gr
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
from pptx.dml.color import RGBColor
from pptx.oxml.xmlchemy import OxmlElement
import os
import requests
from bs4 import BeautifulSoup
import time
# 清理显存
torch.cuda.empty_cache()
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
# 1. 加载模型和分词器
model_path = "./base_model/qwq_32b"
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(
model_path,
torch_dtype=torch.float16,
device_map="auto",
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True
)
print("Model loaded successfully!")
# 2. 联网搜索(使用百度,优化解析逻辑)
def search_web(query, retries=3):
for attempt in range(retries):
try:
url = f"https://www.baidu.com/s?wd={requests.utils.quote(query)}"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
snippets = []
# 调整选择器,适配百度搜索结果
for result in soup.select(".result.c-container"):
title = result.select_one("h3 a")
summary = result.select_one(".c-abstract") or result.select_one(".c-span-last")
if title and summary:
snippets.append(f"{title.get_text()}: {summary.get_text()}")
elif title:
snippets.append(title.get_text())
if snippets:
print("Search results:", snippets) # 调试输出
return "\n".join(snippets[:3])
else:
return "未找到相关搜索结果"
except Exception as e:
print(f"Search attempt {attempt + 1} failed: {str(e)}")
if attempt == retries - 1:
return "搜索失败,请检查网络连接或稍后再试"
time.sleep(2) # 重试前等待 2 秒
# 3. 生成 PPT 内容
def generate_ppt_content(topic, slide_count=3):
system_prompt = (
"你是一个专业的 PPT 内容生成助手,语言为中文。你的任务是根据用户提供的主题,生成一份结构化的 PPT 内容。要求:"
f"1. 生成 {slide_count} 页幻灯片:\n"
" - Slide 1: 标题页(标题,副标题)\n"
f" - Slide 2 到 {slide_count}: 内容页(页标题,3-5 个简洁的要点)\n"
"2. 语言简洁、专业,适合 PPT 展示,避免长句。\n"
"3. 确保内容逻辑清晰,信息准确,基于小米汽车的最新信息(如 2025 年目标交付 30 万台,SU7 和 YU7 系列,智能化布局等)。\n"
"4. 不要包含不准确的信息(如错误的自动驾驶级别,当前小米汽车未达到 L4)。"
)
user_prompt = f"生成一份关于 '{topic}' 的 PPT,重点介绍产品亮点和市场战略。"
messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
model_inputs = tokenizer([text], return_tensors="pt").to("cuda")
generated_ids = model.generate(
model_inputs.input_ids,
max_new_tokens=256,
temperature=0.7,
repetition_penalty=1.2,
pad_token_id=tokenizer.eos_token_id
)
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in
zip(model_inputs.input_ids, generated_ids)]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
# 联网搜索补充信息
search_result = search_web(f"{topic} 2025")
return response + "\n\n补充信息(来自百度):\n" + search_result
# 4. 创建 PPT 文件(优化美观度)
def create_ppt(topic, content):
prs = Presentation()
print("Generated content:\n", content)
slides = content.split("Slide")[1:]
for slide_index, slide_text in enumerate(slides):
slide_lines = slide_text.strip().split("\n")
if not slide_lines:
continue
# 设置背景渐变(浅灰色到白色)
slide_layout = prs.slide_layouts[0 if "1:" in slide_text else 1]
slide = prs.slides.add_slide(slide_layout)
background = slide.background
fill = background.fill
fill.gradient()
fill.gradient_stops[0].color.rgb = RGBColor(240, 240, 240)
fill.gradient_stops[1].color.rgb = RGBColor(255, 255, 255)
# 添加占位符图片框(右侧)
left = Inches(6)
top = Inches(1.5)
width = Inches(3)
height = Inches(3)
slide.shapes.add_picture_placeholder(left=left, top=top, width=width, height=height)
if "1:" in slide_text:
# 标题页
title, subtitle = slide.shapes.title, slide.placeholders[1]
title.text = slide_lines[0].replace("1: Title Slide", "").strip()
subtitle.text = slide_lines[1].strip() if len(slide_lines) > 1 else " "
# 美化标题页(使用小米品牌色:橙色)
title.text_frame.paragraphs[0].font.size = Pt(44)
title.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 103, 0) # 小米橙色
title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
subtitle.text_frame.paragraphs[0].font.size = Pt(24)
subtitle.text_frame.paragraphs[0].font.color.rgb = RGBColor(89, 89, 89)
subtitle.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
else:
# 内容页
title, body = slide.shapes.title, slide.placeholders[1]
title.text = slide_lines[0].strip()
# 格式化内容为要点
tf = body.text_frame
tf.clear()
for line in slide_lines[1:]:
if line.strip():
p = tf.add_paragraph()
p.text = line.strip()
p.level = 0 if not line.startswith("-") else 1
p.font.size = Pt(20)
p.font.color.rgb = RGBColor(0, 0, 0)
p.alignment = PP_ALIGN.LEFT
# 美化内容页
title.text_frame.paragraphs[0].font.size = Pt(32)
title.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 103, 0) # 小米橙色
# 在最后一页添加补充信息
if slide_index == len(slides) - 1 and "补充信息(来自百度)" in content:
search_content = content.split("补充信息(来自百度):\n")[-1].strip()
if search_content and search_content != "未找到相关搜索结果":
left = Inches(0.5)
top = Inches(5.5)
width = Inches(9)
height = Inches(1.5)
textbox = slide.shapes.add_textbox(left, top, width, height)
tf = textbox.text_frame
tf.text = "补充信息(来自百度):\n" + search_content
for p in tf.paragraphs:
p.font.size = Pt(14)
p.font.color.rgb = RGBColor(100, 100, 100)
p.alignment = PP_ALIGN.LEFT
output_file = f"{topic.replace(' ', '_')}_presentation.pptx"
prs.save(output_file)
return output_file
# 5. 主函数
def ppt_agent(topic):
content = generate_ppt_content(topic)
output_file = create_ppt(topic, content)
return content, output_file
# 6. Gradio 界面
with gr.Blocks(title="PPT 生成 Agent") as demo:
gr.Markdown("# 自动化 PPT 生成器")
gr.Markdown("输入主题,生成高质量 PPT 文件。基于 Qwen/QwQ-32B 和百度联网优化。")
topic_input = gr.Textbox(label="PPT 主题", placeholder="例如:小米汽车最新发布")
output_text = gr.Textbox(label="生成的内容", lines=10, interactive=False)
output_file = gr.File(label="下载 PPT 文件")
submit_btn = gr.Button("生成 PPT")
submit_btn.click(fn=ppt_agent, inputs=topic_input, outputs=[output_text, output_file])
demo.launch(server_name="0.0.0.0", server_port=7860)
我们再尝使用添加些样式来丰富下ppt排班
修改代码
def create_ppt(topic, content):
prs = Presentation()
print("Generated content:\n", content)
slides = content.split("Slide")[1:]
for slide_index, slide_text in enumerate(slides):
slide_lines = slide_text.strip().split("\n")
if not slide_lines:
continue
# 设置背景渐变(浅灰色到白色)
slide_layout = prs.slide_layouts[0 if "1:" in slide_text else 1]
slide = prs.slides.add_slide(slide_layout)
background = slide.background
fill = background.fill
fill.gradient()
fill.gradient_stops[0].color.rgb = RGBColor(240, 240, 240)
fill.gradient_stops[1].color.rgb = RGBColor(255, 255, 255)
# 添加矩形框作为图片占位符
left = Inches(6)
top = Inches(1.5)
width = Inches(3)
height = Inches(3)
shape = slide.shapes.add_shape(1, left, top, width, height) # 1 表示矩形
shape.fill.solid()
shape.fill.fore_color.rgb = RGBColor(220, 220, 220)
shape.line.dash_style = MSO_LINE_DASH_STYLE.DASH # 使用正确的虚线样式
shape.line.color.rgb = RGBColor(150, 150, 150)
txBox = shape.text_frame
txBox.text = "图片占位符\n(请手动插入图片)"
p = txBox.paragraphs[0]
p.font.size = Pt(12)
p.font.color.rgb = RGBColor(100, 100, 100)
p.alignment = PP_ALIGN.CENTER
if "1:" in slide_text:
# 标题页
title, subtitle = slide.shapes.title, slide.placeholders[1]
title.text = slide_lines[0].replace("1: Title Slide", "").strip()
subtitle.text = slide_lines[1].strip() if len(slide_lines) > 1 else " "
# 美化标题页(使用小米品牌色:橙色)
title.text_frame.paragraphs[0].font.size = Pt(44)
title.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 103, 0)
title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
subtitle.text_frame.paragraphs[0].font.size = Pt(24)
subtitle.text_frame.paragraphs[0].font.color.rgb = RGBColor(89, 89, 89)
subtitle.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
else:
# 内容页
title, body = slide.shapes.title, slide.placeholders[1]
title.text = slide_lines[0].strip()
# 格式化内容为要点
tf = body.text_frame
tf.clear()
for line in slide_lines[1:]:
if line.strip():
p = tf.add_paragraph()
p.text = line.strip()
p.level = 0 if not line.startswith("-") else 1
p.font.size = Pt(20)
p.font.color.rgb = RGBColor(0, 0, 0)
p.alignment = PP_ALIGN.LEFT
# 美化内容页
title.text_frame.paragraphs[0].font.size = Pt(32)
title.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 103, 0)
# 在最后一页添加补充信息和分割线
if slide_index == len(slides) - 1 and "补充信息(来自百度)" in content:
# 添加分割线
left = Inches(0.5)
top = Inches(5.3)
width = Inches(9)
height = Inches(0.01)
line = slide.shapes.add_shape(1, left, top, width, height)
line.fill.solid()
line.fill.fore_color.rgb = RGBColor(200, 200, 200)
line.line.color.rgb = RGBColor(200, 200, 200)
# 添加补充信息
search_content = content.split("补充信息(来自百度):\n")[-1].strip()
if search_content and search_content != "未找到相关搜索结果":
left = Inches(0.5)
top = Inches(5.5)
width = Inches(9)
height = Inches(1.5)
textbox = slide.shapes.add_textbox(left, top, width, height)
tf = textbox.text_frame
tf.text = "补充信息(来自百度):\n" + search_content
for p in tf.paragraphs:
p.font.size = Pt(14)
p.font.color.rgb = RGBColor(100, 100, 100)
p.alignment = PP_ALIGN.LEFT
output_file = f"{topic.replace(' ', '_')}_presentation.pptx"
prs.save(output_file)
return output_file
再次运行,控制台和新ppt