简介:面向产品经理、教师、运营人员及需要快速产出专业演示文稿的开发者,这份可运行源码完整示范了“需求描述→大模型生成Markdown大纲→Kimi/通义千问/Coze一键转换并美化PPT”的自动化路径。压缩包共3个文件,包含HTML页面、inscode项目配置和gitignore规则,整体仅6KB,轻量精简,便于快速下载、查看页面逻辑和本地调试。目前已有241人学习下载。借助其中的核心代码与项目结构,读者可以理解大模型提示词如何构造、Markdown的结构化表达如何与PPT转换工具衔接,同时可直接替换主题内容生成自己的演示文稿;源码中简洁的页面实现也为二次开发提供了便利。这套流程尤其适合需要批量产出课程讲义、产品方案、工作报告或汇报课件的用户,能有效降低PPT制作的技术门槛与时间成本,让零基础或入门学习者也能迅速上手。
1. 大模型生成PPT指南:一份能直接跑起来的源码包
做方案、赶汇报、把会议纪要变成演示文稿,这类需求几乎每周都会来一次。比起手动做版式,让大模型生成PPT已经成了不少人的日常操作,但要真正把“大模型返回的内容”变成“一个正常的 .pptx 文件”,中间还隔着一层工程实现——模型吐出来的是文字,不是文件。这份可运行源码包做的就是这件事:你给一个大纲或一段主题文本,它调用大模型接口,解析返回结果,在本地直接生成一份版式干净的PPT文件,全程离线排版、可批量重复运行。适合产品经理、课程制作人和想二次开发的工程师,新手跟着步骤能跑通,熟手可以直接替换模型接入点。
2. 两条技术路线对比:为什么源码包选了 python-pptx 而不是 HTML 渲染
2.1 python-pptx:直接在 PPTX 层做排版,输出最可控
PPTX 格式本质上是一个 zip 压缩包,内部全是 XML 描述的幻灯片结构、文本、样式和媒体对象。python-pptx 这个库的核心工作,就是把这一层 XML 操作封装成 Python 对象,让开发者不需要手写 XML 就能创建幻灯片、添加文本框、设定字体字号和坐标位置。
我选择这条路线的理由很直接:排版参数全部写死在代码里。比如标题字号 28 磅、正文 14 磅、左边距 0.8 英寸、顶部留白 0.5 英寸,这些数值在代码中一目了然,生成的每一页结构完全一致。对于需要批量产出、风格统一、不追求复杂动画的PPT场景,这是最稳定的方案。另一个隐性优点是离线能力——调用大模型只需要一次网络请求,拿到文本之后所有排版动作都在本地完成,不依赖任何在线渲染服务。
这套源码包在这个环节依赖三个核心库:python-pptx 负责读写 PPTX 文件,lxml 负责底层的 XML 处理(通常作为 python-pptx 的依赖自动安装),Pillow 用于处理封面图和配图。三个库加在一起安装体积不大,Python 3.9 以上版本都能正常跑。
2.2 HTML + 浏览器转 PDF 的路线:视觉上限高,但环境坑多
另一条常见路线是让大模型生成 HTML/CSS 幻灯片,再用浏览器把 HTML 渲染成 PDF,最后把 PDF 页面插入 PPT。这条路线在视觉表现力上确实更强,CSS 可以做渐变、动画、复杂布局,很多在线 AI 幻灯片工具用的是这个思路。
但这条路线在工程落地时有几个绕不开的麻烦。第一,大模型生成的 HTML/CSS 质量不稳定,偶尔会输出不闭合的标签或非标准的 CSS 属性,同一个模板这次渲染正常、下次就可能错位。第二,浏览器渲染到 PDF 依赖本机安装了 Chromium 或 Chrome,还需要处理页面尺寸、分页截断、字体嵌入一堆问题。第三,PDF 转 PPT 后所有内容变成图片,文本不可编辑,后期想改一个字都比重新生成还麻烦。
对于想拿源码包离线批量跑的人,这套链路的环境差异太大了。一份源码如果需要在不同电脑上复现,应该尽量降低对系统环境的依赖,而不是把结果押注在“刚好装了某个版本的 Chrome”上。
2.3 选型结论:离线、批量、可复现优先,所以我选 python-pptx
| 对比维度 | python-pptx | HTML 渲染转 PDF/PPT |
|---|---|---|
| 输出格式 | 直接生成 .pptx,文本可编辑 | PDF 转图,内容不可编辑 |
| 定制粒度 | 坐标、字号、字体精确控制 | 依赖 CSS 模板,粒度粗 |
| 运行依赖 | 只用 Python 库,跨平台一致 | 需要 Chromium 等浏览器环境 |
| 批量速度 | 本地排版,毫秒级生成 | 每个文件走浏览器渲染,秒级 |
| 学习成本 | 熟悉面向对象调用即可 | 需要同时懂 CSS 和渲染配置 |
我是从“内容与表现分离”的角度做的决定:大模型负责输出结构化的内容,python-pptx 负责把内容映射到固定的版式上。模型输出不稳定,但本地排版是稳定的,两者一组合,整体质量就可控了。如果你已经部署了本地大模型或者有免费的 API 额度,源码包的调用层可以直接切换过去,这个到第 3 章会说具体接入方式。
3. 最小可运行流程:从安装依赖到生成第一份 3 页 PPT
3.1 目录结构与依赖清单
拿到源码包之后,先按下面的结构理解文件组织。源码包的核心逻辑是有意拆开的:llm_client.py负责网络请求,parser.py负责解析大模型返回的文本,builder.py负责生成 PPTX,config.py保存模型参数和排版参数。这样拆分的好处是每一部分都可以单独替换,换模型不用碰排版代码,调样式也不用重新测试网络层。
gpt-ppt/ ├── config.py # 模型参数、API配置、排版常量 ├── llm_client.py # 调用大模型API,支持OpenAI兼容接口 ├── parser.py # 把markdown解析成页面对象 ├── builder.py # python-pptx 排版引擎,生成PPTX ├── run.py # 入口:读入大纲 → 生成PPT ├── requirements.txt # 依赖清单 └── examples/ └── outline.txt # 示例大纲,直接可以喂给run.py# requirements.txt python-pptx==0.6.23 lxml>=4.9.0 Pillow>=9.5.0 requests>=2.31.0逻辑说明:python-pptx版本锁在 0.6.23 是因为这个版本在 Office 和 WPS 双端的兼容性表现最好,更新的 1.x 版本引入了一些对 XML 空间的调整,个别老模板会出兼容问题。lxml不锁死版本,只要大于 4.9.0 就能配合 python-pptx 工作。
3.2 先写一个最简单的生成脚本
先不纠结复杂的排版逻辑,直接跑通最小链路。源码包的run.py核心流程就五步:读入大纲文件,把大纲拼接成提示词,请求大模型拿到结构化 markdown,解析成页面数组,最后交给 builder 逐页生成 PPT。
# run.py 核心逻辑(源码包按此结构实现) import os from llm_client import LLMClient from parser import parse_markdown_to_pages from builder import build_ppt # 1. 从环境变量读取API配置,避免把密钥写进代码 api_key = os.environ.get("LLM_API_KEY", "sk-xxxx") base_url = os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1") # 2. 初始化客户端,temperature 调成 0.3 保证输出稳定 client = LLMClient(api_key=api_key, base_url=base_url, temperature=0.3) # 3. 读取大纲,拼接提示词 with open("examples/outline.txt", "r", encoding="utf-8") as f: outline = f.read() prompt = build_prompt(outline) # 4. 请求大模型,返回markdown格式的幻灯片内容 markdown_text = client.get_response(prompt) # 5. 解析并生成PPT pages = parse_markdown_to_pages(markdown_text) build_ppt(pages, output_file="demo.pptx") print("已生成 demo.pptx,共 {} 页".format(len(pages)))逻辑说明:temperature=0.3是关键参数,数值越低模型输出越保守、越不容易自由发挥格式。生成 PPT 这种场景不需要创造力,控制住格式比控制内容重要。max_tokens在源码包里不是写死的,而是根据大纲长度动态计算,估算逻辑是“每个页面约 120 字,再加上 30% 的冗余”,这部分在config.py里有完整实现。
参数说明:LLM_API_KEY和LLM_BASE_URL是环境变量,不是代码里写死的字符串。这么做是让源码包可以直接对接市面上绝大多数 OpenAI 兼容接口——包括各种开源模型的代理服务、国内模型的 API、本地部署的模型框架,只需要改BASE_URL就能切换。
3.3 运行与验证:打开文件数页数
依赖装好之后,在源码包根目录执行下面的命令跑通流程:
# 安装依赖,建议在虚拟环境里执行 pip install -r requirements.txt # 设置API密钥 export LLM_API_KEY="你的密钥" # 运行生成流程 python run.py跑完会在当前目录生成demo.pptx。建议养成一个习惯:不用直接双击打开文件,先写一个小检查脚本确认页数和文本内容都是完整的。因为双击打开有视觉惯性,很容易忽略“漏了一页”这种问题。
# check_ppt.py 验证脚本 from pptx import Presentation prs = Presentation("demo.pptx") print("总页数:", len(prs.slides)) for idx, slide in enumerate(prs.slides): texts = [] for shape in slide.shapes: if shape.has_text_frame: texts.append(shape.text_frame.text.replace("\n", " ")) print("第{}页:{}".format(idx + 1, " | ".join(texts)[:80]))参数说明:这个脚本用Presentation()直接打开生成的.pptx文件,逐个检查每页的文本内容是否和预期一致。如果发现某页文本是空的,说明解析环节出了问题,就该去查第 4 章说的容错逻辑了。
3.4 本地大模型或免费 API 的接入方式
源码包默认走 OpenAI 兼容接口,但很多人实际用的是本地部署的模型框架或免费额度的 API。接入方式很简单,环境变量已经预留了位置:
# 以常见的本地模型框架为例,只需要改 BASE_URL export LLM_BASE_URL="http://localhost:11434/v1" export LLM_API_KEY="ollama" # 或者对接兼容OpenAI协议的免费API export LLM_BASE_URL="https://api.example-free-llm.com/v1" export LLM_API_KEY="你的免费密钥" python run.py逻辑说明:这类本地模型框架通常都会提供一个 OpenAI 兼容的 REST 接口,路径一样是/v1/chat/completions。源码包里的llm_client.py直接使用requests库调用这个标准接口,所以换模型不需要改代码逻辑。需要注意的一点是:本地小模型的指令遵循能力普遍不如商用大模型,提示词里的格式要求可能要写得再强硬一些——这点在下一章的提示词设计里会展开。
4. 结构化输出与容错解析:从大模型返回的 markdown 到页面对象
4.1 提示词设计:让模型返回固定结构
大模型生成 PPT 最容易出的问题不是内容不好,而是格式不听话。有的模型喜欢给每一页加标题层级,有的喜欢用列表嵌套列表,有的偶尔在结尾加一段“总结”。源码包解决这个问题的思路是:在提示词里给出极严格的输出格式约束,用pages、title、bullets三个字段定义每个页面。
# prompt_template.py 中的核心提示词模板 SYSTEM_PROMPT = """你是一个PPT大纲生成器。我会给你一份文档提纲,请你生成PPT内容。 输出要求: 1. 严格输出 JSON 对象,禁止输出任何解释文字 2. 顶层包含 pages 数组,每个元素是一个页面对象 3. 页面对象格式:{"title": "页面标题,不超过20字", "bullets": ["要点1", "要点2", "要点3"]} 4. bullets 列表长度 2-5 条,每条不超过 40 字 5. 总共生成 {num_pages} 页 6. 内容必须直接反映提纲,不要添加提纲中没有的推论 提纲如下: {outline} """逻辑说明:第 3 条和第 5 条是核心。固定字段名是为了后续解析简单,限制字数是防止模型自由发挥写出超长句子导致 PPT 页面放不下。{num_pages}是动态填充的,源码包根据提纲的行数估算页数,估算公式是提纲行数加 2——这个数字是经验值:太少页面塞不下内容,太多页面每页很空。
参数说明:bullets长度限制在 2-5 条是有讲究的。PPT 不是文档,一页超过 5 个要点,字号就必须缩到 12 磅以下,投影出来根本看不清。如果模型非要生成 7、8 条,源码包的解析器会做截断而不是报错,只取前 5 条,宁缺毋滥。
4.2 解析函数:剥围栏、拆页面、兜底截断
大模型返回的数据经常有意外。最常见的是在 JSON 外面套了 markdown 代码围栏,即返回内容里带json 和标记。源码包的parser.py里第一个动作就是剥掉围栏;第二步才是解析 JSON;如果 JSON 都解析失败,再做一次简单的文本兜底。
# parser.py 中的解析与容错逻辑 import json import re def parse_markdown_to_pages(raw_text): # 1. 去掉 markdown 代码围栏,兼容 ```json 前缀 cleaned = raw_text.strip() cleaned = re.sub(r"^```(?:json)?\s*", "", cleaned) cleaned = re.sub(r"\s*```$", "", cleaned) # 2. 尝试解析 JSON try: data = json.loads(cleaned) return data["pages"] except (json.JSONDecodeError, KeyError): # 3. JSON 解析失败时,降级为按标题行切分 pages = [] current_title = None current_bullets = [] for line in cleaned.splitlines(): if line.startswith("#"): if current_title: pages.append({"title": current_title, "bullets": current_bullets}) current_title = line.lstrip("#").strip() current_bullets = [] elif line.strip(): current_bullets.append(line.strip()) if current_title: pages.append({"title": current_title, "bullets": current_bullets}) return pages逻辑说明:这套双层解析设计是关键——优先走 JSON 严格解析,失败后不直接报错,而是降级到按 markdown 标题结构切分。这样即使换了一个指令遵循能力较弱的模型,最多损失一些格式精度,不会让整个生成流程中断。
参数说明:re.sub的两个正则表达式分别处理开头和结尾的围栏。^```(?:json)?匹配以三个反引号开头且后面可带 json 字样的字符串;结尾的\s*```$匹配最后一行。注意正则里用了\s*,这样即使模型在围栏后面留了空行也能正确剥掉。
4.3 文本清洗:把控制字符和空白杂讯清掉
大模型返回的文本偶尔会夹带一些意想不到的字符,比如零宽空格(\u200b)、不换行空格(\xa0)、表情符号,还有全角/半角混用的空格。这些直接写进 PPT 里,轻则排版对不齐,重则在某些字体渲染下显示成乱码方块。源码包在解析之后加了一道清洗工序。
# parser.py 中的文本清洗函数 def sanitize_text(text): if not text: return "" # 去除零宽字符和控制字符 text = re.sub(r"[\u200b\u200c\u200d\u2060]", "", text) text = re.sub(r"[\x00-\x1f\x7f]", "", text) # 把不间断空格替换为普通空格 text = text.replace("\xa0", " ") # 压缩连续空格 text = re.sub(r" {2,}", " ", text) return text.strip()逻辑说明:清洗函数放在解析之后、传给排版引擎之前。\u200b到\u2060这一串是 Unicode 的零宽字符区间,肉眼看不见,但会影响文本拼接和字号计算;\x00-\x1f是 ASCII 控制字符区间,部分接口返回内容里会在换行处残留这些字符。源码包的builder.py在写入每个文本框前都会调用这个清洗函数。
4.4 一次请求还是逐页请求:两种策略的适用边界
源码包默认是单次请求生成全部页面,这对 10 页以内的 PPT 完全够用。但如果你要生成的是 30 页以上的长文档,单次请求很容易触发max_tokens上限截断,导致后半部分内容丢失。这种情况下源码包支持一种“逐页请求”模式:把大纲按章节拆开,每次只让模型生成 3-5 页,然后拼接起来。
我在实际使用中的经验是:短汇报型 PPT 用单次请求,速度快、成本低;课程讲义、操作手册这种长文档用逐页请求,稳定性和内容质量都更好。切换方式在源码包run.py里通过一个--batch 5参数控制,意思是每批生成 5 页,批与批之间自动拼合。
5. 大模型生成 PPT 的避坑记录:五条实测翻车与修复方案
5.1 中文全部变成方块
现象:生成的 PPT 里中文全部显示为小方块,英文正常。
原因:python-pptx 默认的字体样式继承自空白模板,而空白模板里中文字体映射通常是空的。代码里只设置了font.name = "Arial",中文字符无法用该字体渲染,就回退成了默认字体——在部分系统上这个默认字体不支持中文。
解决:设置英文字体的同时,必须通过 XML 操作设置中文字体。源码包在builder.py里封装了set_font函数,每次设置字体时同时写入latin和ea(East Asian)两个字体属性:
# builder.py 中的字体设置 from pptx.oxml.ns import qn def set_font(run, name="微软雅黑", size=14, bold=False, color="000000"): run.font.name = name # 设置西文字体 run.font.size = Pt(size) run.font.bold = bold rPr = run._r.get_or_add_rPr() ea = rPr.find(qn('a:ea')) if ea is None: ea = rPr.makeelement(qn('a:ea'), {}) rPr.append(ea) ea.set('typeface', name) # 设置中文字体逻辑说明:<a:latin>负责西文字符,<a:ea>负责东亚字符。只设置前者,中文字符就会被系统当成“未知字体”处理。源码包默认字体是“微软雅黑”,这是 Windows 和 Office 环境覆盖率最高的中文字体;如果你的目标环境是 macOS,可以改为“PingFang SC”。
5.2 返回内容里夹带 ```json 围栏
现象:解析时报json.JSONDecodeError: Expecting value,生成流程中断。
原因:一些模型在系统提示词里看到了“JSON”字样,会在输出里自动加上 markdown 代码围栏,这在人工对话里是正常的,但在程序调用里就成了多余字符。
解决:parser.py的第一条正则已经做了围栏剥离。但源码包还加了一道保险——在llm_client.py的返回值处理里,如果检测到围栏字符串,会先做一次替换再交给解析器:
# llm_client.py 中的预处理 def _strip_fence_if_present(text): if text.startswith("```"): # 去掉第一行 ```json 和最后一行 ``` lines = text.splitlines() return "\n".join(lines[1:-1]) return text参数说明:这段代码只在文本以```开头时触发,不匹配正常 JSON 内容,所以不会误伤。逻辑上比正则更严格,适合处理围栏和 JSON 混排的情况。
5.3 响应被截断,PPT 少了后三页
现象:生成的 PPT 一共只有 7 页,但提示词要求生成 10 页;内容到“总结”部分明显中断。
原因:max_tokens设置得太小,模型输出到一半就撞上了上限,后半部分内容根本没有生成。大模型对“生成完再停止”这件事没有精确控制,输出长度受 token 限制非常严格。
解决:源码包在config.py里根据页数动态计算max_tokens。经验公式:每页平均 120 字,约等于 180 个 token,再加上 500 token 的富余量,最终值是页数乘以 180 再加 500。如果要生成 30 页,建议直接把--batch 5参数加进去逐页请求,不要指望单次请求一次性输出全部内容。
5.4 并发调用免费 API 被限流
现象:批量生成 20 个 PPT,跑到第 7 个突然报错429 Too Many Requests。
原因:绝大多数免费额度的 API 都有每分钟请求数限制,源码包默认不做并发控制,连续调用必然触发限流。
解决:源码包在llm_client.py里内置了简单的重试机制——遇到 429 或 5xx 状态码时等待 3 秒重试,最多重试 3 次。如果你用的是免费额度,建议把这个等待时间改长到 5 秒,免费接口的限流窗口通常比较短,等 5 秒再试的通过率明显更高。
5.5 同一份代码在 Office 和 WPS 里显示不一致
现象:Office 里排版正常,WPS 里文本框位置偏移了 0.3 厘米;或者反过来,WPS 正常,Office 字体被替换。
原因:两个软件对 PPTX 里没有显式声明的样式属性有不同的默认值。python-pptx 生成的文本框中,如果没有设置行距和边距,Office 默认自动行距,WPS 可能按固定值处理;字体的autofit属性未设置时,两边对溢出文本的压缩策略也不一样。
解决:源码包的builder.py里所有文本框都显式设置了三个参数——margin_left、margin_right、margin_top全部写死,行距设置为固定值 1.2。设置之后不要用自动换行时的“压缩字体”选项,这个选项在两个软件的实现差异最大。提醒一句:任何人给你说“跨端渲染是玄学”基本上是因为参数没写全,把边距、行距、字体这三个维度全部显式指定后,两边差异会缩小到肉眼不可见。
6. 进阶玩法:把布局改成配置驱动,一套提示词适配多种 PPT 风格
走到这一步,源码包的基础流程已经能稳定出片了。接下来的进阶方向不是让大模型更“聪明”,而是把版式决策权从代码里拿出来,做成配置驱动。源码包在config.py里预留了一个LAYOUTS配置字典,用不同的布局名称区分版本风格。
# config.py 中的布局配置 LAYOUTS = { "clean": { "title_font": "微软雅黑", "title_size": 28, "body_font": "微软雅黑", "body_size": 14, "left_margin": 0.8, "top_margin": 0.5, "bullet_spacing": 1.2, }, "compact": { "title_font": "微软雅黑", "title_size": 24, "body_font": "微软雅黑", "body_size": 12, "left_margin": 0.6, "top_margin": 0.3, "bullet_spacing": 1.0, }, }逻辑说明:clean是默认版式,适合汇报场景,字号大、留白多、每页信息量小;compact适合课程讲义和技术文档,字号小、单位面积内能容纳更多内容。生成时通过命令行参数--layout compact切换,builder.py在排版时只读取这个字典,不关心具体数值——这保证了版式的调整不需要改动任何页面生成逻辑。
我做完这个改造后最大的感受是:大模型负责内容组织,本地配置负责视觉呈现,两者完全解耦之后,换风格的成本就从“修改排版代码”降到了“改一个 JSON 字典”。以前总觉得让模型一句话同时把控内容和版式是本事,后来发现模型在版式把控上基本靠不住,同样的提示词换一个模型版本,排出来的间距可能差两倍。从那以后我每次生成 PPT 都强制走一遍“读大纲 → 定 layout → 调 API → 跑自检脚本”的流程,不再在提示词里让模型自由发挥样式。自检脚本就按第 3 章的例子改,只验证页数和每页文本是否非空,跑通了再打开看视觉细节。这个习惯帮我避开了至少 3 次“生成完了才发现缺页”的尴尬,希望帮到你。
本文还有配套的精品资源,点击获取