






















前置要求:需要 Python 3.10+、一个 MiniMax API Key
前置知识:建议先读完前 20 篇基础教程,对 LCEL 有基本了解
学完前 20 个 Demo,你已经掌握了 LangChain 的各种组件用法。但这些用法是散落的——这篇教程用一条主线任务把所有东西串起来:
做一个智能 PPT 生成器:输入一个主题,输出一个真实的 .pptx 文件
这个任务会用到:
学完这篇,你会有一个真正能用的工具,同时深刻理解"Chain"这个核心理念。
(图片:最终运行效果截图)

打开终端,按顺序执行:
mkdir langchain-ppt
cd langchain-ppt
uv init
本教程需要以下 Python 包:
langchain-core>=1.0.0
langchain-openai>=1.0.0
python-dotenv>=1.0.1
python-pptx>=0.6.23
安装方式(推荐 requirements.txt):
uv sync
# 或
uv pip install -r requirements.txt
什么是 uv?
uv 是新一代 Python 包管理工具,比 pip 更快、更可靠。如果还没安装,执行:pip install uv
在项目根目录新建 .env 文件:
touch .env
编辑 .env 文件内容:
# MiniMax API Key(必填)
MINIMAX_API_KEY=sk-your-key-here
获取 API Key:访问 MiniMax 开放平台 注册后获取
新建 01_verify_env.py,内容如下(与 demos 目录一致):
# 01_verify_env.py
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
import os
load_dotenv()
print(os.getenv("MINIMAX_API_KEY"))
llm = ChatOpenAI(
model="MiniMax-M2.7",
base_url="https://api.minimaxi.com/v1",
api_key=os.getenv("MINIMAX_API_KEY"),
temperature=0.7
)
response = llm.invoke("你好")
print(response.content)
运行:
uv run python 01_verify_env.py
看到 你好 的回复,说明环境 OK。

在前 D04 里学过:LLM 输出的本质是字符串。想让程序自动处理 LLM 的输出,必须先把字符串解析成结构化数据。
OutputParser 就是做这件事的组件:
(图片:Pydantic Parser 工作原理)
LangChain 内置了多种 Parser,本教程用 PydanticOutputParser——因为它可以定义字段描述(Field description),让 LLM 知道每个字段应该填什么,同时自动校验输出格式。
用 Pydantic 定义我们想要的 PPT 结构(与 demos/02_ppt_schema.py 一致):
# 02_ppt_schema.py
from pydantic import BaseModel, Field
from typing import List
class Slide(BaseModel):
"""单页幻灯片"""
title: str = Field(description="幻灯片标题,不超过20字")
subtitle: str = Field(description="副标题,不超过30字")
points: List[str] = Field(
description="要点列表,每个要点不超过50字",
min_length=2,
max_length=5
)
notes: str = Field(description="演讲备注")
class PPTSchema(BaseModel):
"""完整PPT大纲"""
title: str = Field(description="PPT主题标题,不超过30字")
subtitle: str = Field(description="PPT副标题,概括主题")
author: str = Field(description="演讲者姓名")
slides: List[Slide] = Field(
description="幻灯片列表",
min_length=5,
max_length=15
)
Field description 为什么重要?
这里的 description 就是告诉 LLM"我希望你输出什么格式"。LLM 会参考它来生成内容。
完整代码(与 demos/02_ppt_schema.py 一致):
# 02_ppt_schema.py
# generate_outline.py
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List
load_dotenv()
# ========== 数据模型 ==========
class Slide(BaseModel):
title: str = Field(description="幻灯片标题,不超过20字")
subtitle: str = Field(description="副标题,不超过30字")
points: List[str] = Field(description="要点列表,每个要点不超过50字", min_length=2, max_length=5)
notes: str = Field(description="演讲备注")
class PPTSchema(BaseModel):
title: str = Field(description="PPT主题标题,不超过30字")
subtitle: str = Field(description="PPT副标题,概括主题")
author: str = Field(description="演讲者姓名")
slides: List[Slide] = Field(description="幻灯片列表", min_length=5, max_length=15)
# ========== Parser + Prompt 组合 ==========
parser = PydanticOutputParser(pydantic_object=PPTSchema)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的PPT内容策划专家。"),
("human", "请为「{topic}」这个主题生成一份完整的PPT大纲。\n\n{format_instructions}")
])
prompt = prompt.partial(
format_instructions=parser.get_format_instructions()
)
# ========== 串联 Chain ==========
chain = prompt | ChatOpenAI(
model="MiniMax-M2.7",
base_url="https://api.minimaxi.com/v1",
api_key=os.getenv("MINIMAX_API_KEY"),
temperature=0.7
) | parser
# ========== 执行 ==========
result = chain.invoke({"topic": "量子计算技术趋势"})
print(result.model_dump_json(indent=2))
运行结果(JSON 摘要):
{
"title": "量子计算技术趋势",
"subtitle": "从原理到应用的全景解析",
"author": "LangChain助手",
"slides": [
{"title": "什么是量子计算", "subtitle": "理解量子力学基础", "points": ["量子叠加态", "量子纠缠原理", "量子比特"], "notes": "开场要通俗易懂"},
...
]
}
(图片:Pydantic Parser 完整流程)

python-pptx 是操作 PowerPoint 文件的 Python 库。用法与 demos/03_generate_ppt.py 一致:
# 03_generate_ppt.py
from pptx import Presentation
from pptx.util import Pt, Inches
from pptx.dml.color import RGBColor
# 新建空白演示文稿
prs = Presentation()
# 添加一页(使用"标题+内容"布局,layouts[1])
slide = prs.slides.add_slide(prs.slide_layouts[1])
# 设置标题
title = slide.shapes.title
title.text = "这是标题"
# 设置正文
body = slide.placeholders[1]
body.text = "这是第一行\n这是第二行"
# 保存文件
prs.save("output.pptx")
常见 Slide Layouts 索引:
layouts[0]:空白layouts[1]:标题 + 内容(最常用)layouts[6]:仅标题
(图片:python-pptx 代码与效果对照)

把 Step 1 的 Parser 输出接到 PPTX 生成函数:
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List
from pptx import Presentation
load_dotenv()
# ========== 数据模型(与Step1相同)==========
class Slide(BaseModel):
title: str = Field(description="幻灯片标题,不超过20字")
subtitle: str = Field(description="副标题,不超过30字")
points: List[str] = Field(description="要点列表", min_length=2, max_length=5)
notes: str = Field(description="演讲备注")
class PPTSchema(BaseModel):
title: str = Field(description="PPT主题标题,不超过30字")
subtitle: str = Field(description="PPT副标题")
author: str = Field(description="演讲者姓名")
slides: List[Slide] = Field(description="幻灯片列表", min_length=5, max_length=15)
# ========== Chain(与Step1相同)==========
parser = PydanticOutputParser(pydantic_object=PPTSchema)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的PPT内容策划专家。"),
("human", "请为「{topic}」生成PPT大纲。\n\n{format_instructions}")
]).partial(format_instructions=parser.get_format_instructions())
chain = prompt | ChatOpenAI(
model="MiniMax-M2.7",
base_url="https://api.minimaxi.com/v1",
api_key=os.getenv("MINIMAX_API_KEY"),
temperature=0.7
) | parser
# ========== PPTX 生成函数 ==========
def build_pptx(schema: PPTSchema, output_path: str) -> None:
"""把PPTSchema写入真实PPTX文件"""
os.makedirs(os.path.dirname(output_path), exist_ok=True)
prs = Presentation()
for slide_data in schema.slides:
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = slide_data.title
body = slide.placeholders[1] if len(slide.placeholders) > 1 else None
if body:
content_lines = [slide_data.subtitle, ""]
content_lines += [f"- {pt}" for pt in slide_data.points]
content_lines += ["", f"备注: {slide_data.notes}"]
body.text = "\n".join(content_lines)
try:
prs.save(output_path)
print(f"✅ PPT已保存至:{output_path}")
except PermissionError:
print(f"❌ 权限错误:请关闭已打开的PPT文件后重试")
# ========== 执行完整流程 ==========
if __name__ == "__main__":
topic = input("请输入PPT主题:").strip()
if not topic:
topic = "LangChain 架构解析"
print("正在生成PPT大纲...")
result = chain.invoke({"topic": topic})
print("✅ 大纲生成完成,正在写入文件...")
output_file = os.path.join("output", f"{topic}.pptx")
build_pptx(result, output_file)
运行:
uv run python 02_ppt_schema.py
请输入PPT主题:量子计算技术趋势
正在生成PPT大纲...
✅ 大纲生成完成,正在写入文件...
✅ PPT已保存至:output/量子计算技术趋势.pptx
打开 output/量子计算技术趋势.pptx,可以看到真实的 PPT 文件已生成。
回顾一下整个流程:
(图片:LCEL Chain 串联图)

我们用 LCEL 的管道语法,把四步串成一条线。完整代码与 demos/04_ai_generate_ppt.py 一致(简化版,不含多风格 Prompt):
# generate_ppt_full.py
# 完整可运行版本:输入主题 → 输出 PPTX 文件
# 依赖:"langchain-openai>=1.0.0" python-dotenv>=1.0.1 python-pptx>=0.6.23
import os
import json
import re
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field
from typing import List
from pptx import Presentation
def get_output_path(filename: str) -> str:
return os.path.join("output", filename)
def clean_text_for_ppt(text: str) -> str:
"""清理文本,移除可能导致显示问题的字符"""
if not text:
return ""
# 移除控制字符(保留换行)
cleaned = ''.join(c for c in text if c == '\n' or c == '\t' or (ord(c) >= 32 and ord(c) != 127))
return cleaned
def extract_json_from_llm_output(text: str) -> str:
"""从LLM输出中提取JSON,即使有额外文字也能工作"""
# 确保文本是正确的编码
if isinstance(text, bytes):
text = text.decode('utf-8', errors='replace')
stack = []
start_idx = -1
for i, c in enumerate(text):
if c == '{':
if not stack:
start_idx = i
stack.append(c)
elif c == '}':
stack.pop()
if not stack and start_idx != -1:
return text[start_idx:i+1]
return text
load_dotenv()
# === 数据模型 ===
class Slide(BaseModel):
title: str = Field(description="幻灯片标题,不超过20字")
subtitle: str = Field(description="副标题,不超过30字")
points: List[str] = Field(description="要点列表", min_length=2, max_length=10)
notes: str = Field(description="演讲备注")
class PPTSchema(BaseModel):
title: str = Field(description="PPT主题标题,不超过30字")
subtitle: str = Field(description="PPT副标题")
author: str = Field(description="演讲者姓名")
slides: List[Slide] = Field(description="幻灯片列表", min_length=5, max_length=30)
# === Chain ===
# 创建提示词模板,使用 ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的PPT内容策划专家。"),
("human", "请为「{topic}」这个主题生成一份完整的PPT大纲。\n\n请按照以下JSON格式输出:\n{{\n \"title\": \"PPT主题标题\",\n \"subtitle\": \"PPT副标题\",\n \"author\": \"演讲者姓名\",\n \"slides\": [\n {{\n \"title\": \"幻灯片标题\",\n \"subtitle\": \"副标题\",\n \"points\": [\"要点1\", \"要点2\"],\n \"notes\": \"演讲备注\"\n }}\n ]\n}}")
])
# 可扩展 prompt 方案(保留上面的原始 prompt,不删除)
prompt_strict_json = ChatPromptTemplate.from_messages([
("system", "你是一个专业的PPT内容策划专家。你必须只输出一个合法JSON对象,不得输出任何额外文本。"),
("human", "请为「{topic}」生成PPT大纲。\n\n要求:\n1. 仅输出 JSON。\n2. 严禁输出 markdown、注释、解释、前后缀文字。\n3. JSON 结构必须为:\n{{\n \"title\": \"PPT主题标题\",\n \"subtitle\": \"PPT副标题\",\n \"author\": \"演讲者姓名\",\n \"slides\": [\n {{\n \"title\": \"幻灯片标题\",\n \"subtitle\": \"副标题\",\n \"points\": [\"要点1\", \"要点2\"],\n \"notes\": \"演讲备注\"\n }}\n ]\n}}\n4. slides 数量 5-30。\n5. points 数量 2-10。")
])
prompt_executive = ChatPromptTemplate.from_messages([
("system", "你是企业级汇报专家,擅长高管汇报PPT结构设计。输出必须是纯JSON。"),
("human", "请为「{topic}」生成一份偏商业汇报风格的PPT大纲,强调业务价值、ROI、落地路径与风险控制。\n\n输出为 JSON:\n{{\n \"title\": \"PPT主题标题\",\n \"subtitle\": \"PPT副标题\",\n \"author\": \"演讲者姓名\",\n \"slides\": [\n {{\n \"title\": \"幻灯片标题\",\n \"subtitle\": \"副标题\",\n \"points\": [\"要点1\", \"要点2\"],\n \"notes\": \"演讲备注\"\n }}\n ]\n}}")
])
prompt_workshop = ChatPromptTemplate.from_messages([
("system", "你是技术培训讲师,擅长把复杂主题拆成可教学的结构化内容。输出必须是纯JSON。"),
("human", "请为「{topic}」生成一份偏教学实战风格的PPT大纲,强调概念-示例-实操-总结的节奏。\n\n输出为 JSON:\n{{\n \"title\": \"PPT主题标题\",\n \"subtitle\": \"PPT副标题\",\n \"author\": \"演讲者姓名\",\n \"slides\": [\n {{\n \"title\": \"幻灯片标题\",\n \"subtitle\": \"副标题\",\n \"points\": [\"要点1\", \"要点2\"],\n \"notes\": \"演讲备注\"\n }}\n ]\n}}")
])
PROMPT_PRESETS = {
"default": prompt, # 现有 prompt(默认)
"strict_json": prompt_strict_json,
"executive": prompt_executive, # 商业汇报风格
"workshop": prompt_workshop # 培训讲解风格
}
PROMPT_STYLE = os.getenv("PPT_PROMPT_STYLE", "default")
selected_prompt = PROMPT_PRESETS.get(PROMPT_STYLE, prompt)
llm = ChatOpenAI(
model="MiniMax-M2.7",
base_url="https://api.minimaxi.com/v1",
api_key=os.getenv("MINIMAX_API_KEY"),
temperature=0.7
)
# === PPTX 生成 ===
def build_pptx_fn(input_dict: dict) -> dict:
schema: PPTSchema = input_dict["schema"]
topic = input_dict["topic"]
prs = Presentation()
for slide_data in schema.slides:
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = clean_text_for_ppt(slide_data.title)
body = slide.placeholders[1] if len(slide.placeholders) > 1 else None
if body:
# 移除可能导致乱码的特殊字符,使用简洁的格式
lines = [clean_text_for_ppt(slide_data.subtitle), ""] + [f"- {clean_text_for_ppt(p)}" for p in slide_data.points]
lines += ["", "备注: " + clean_text_for_ppt(slide_data.notes)]
body.text = "\n".join(lines)
# 更安全的文件名处理
safe_topic = re.sub(r'[^\w\u4e00-\u9fff\s-]', '', topic)
safe_topic = safe_topic.strip().replace(' ', '_')
path = get_output_path(f"{safe_topic}.pptx")
os.makedirs(os.path.dirname(path), exist_ok=True)
try:
prs.save(path)
return {"path": path, "count": len(schema.slides)}
except PermissionError:
return {"path": None, "count": 0, "error": "文件被占用"}
except Exception as e:
return {"path": None, "count": 0, "error": str(e)}
if __name__ == "__main__":
topic = input("请输入PPT主题:").strip() or "LangChain 架构解析"
print(f"🎯 主题:{topic}")
try:
# 直接调用 LLM,不使用解析器
llm_chain = selected_prompt | llm
llm_output = llm_chain.invoke({"topic": topic})
llm_text = llm_output.content if hasattr(llm_output, "content") else str(llm_output)
# 提取 JSON 并解析
json_text = extract_json_from_llm_output(llm_text)
json_data = json.loads(json_text)
schema = PPTSchema(**json_data)
print(f"✅ Schema 解析成功,共 {len(schema.slides)} 页")
result = build_pptx_fn({"topic": topic, "schema": schema})
if result.get("path"):
print(f"✅ 完成!共 {result['count']} 页\n📁 {result['path']}")
else:
print(f"❌ 保存失败:{result.get('error', '未知错误')}")
except Exception as e:
print(f"❌ 运行出错:{e}")
import traceback
traceback.print_exc()

| 包名 | 版本 | 用途 |
|---|---|---|
安装命令:
uv sync
# 或
uv pip install -r requirements.txt
Q:报错 MINIMAX_API_KEY is not set 或认证错误
A:环境变量未正确加载。检查:
.env 文件是否在项目根目录load_dotenv() 已执行Q:JSON 解析失败(Invalid json output 或 OUTPUT_PARSING_FAILURE)
A:LLM 输出格式不稳定,经常包含额外的解释性文字。解决方案:
extract_json_from_llm_output() 自动提取 JSON(项目已实现)Q:NameError: name 'get_output_path' is not defined
A:脚本中缺少函数定义。确保添加:
def get_output_path(filename: str) -> str:
return os.path.join("output", filename)
Q:FileNotFoundError 或 PermissionError 保存 PPT 失败
A:output/ 目录不存在或没有写入权限。解决:
os.makedirs(os.path.dirname(path), exist_ok=True)
Q:乱码问题或特殊字符显示异常
A:文本中包含 emoji 或特殊字符。解决:
clean_text_for_ppt() 函数- 而非 •)Q:1 validation error for PPTSchema slides 数量超限
A:模型生成的幻灯片数量超过 schema 限制。已将 max_length 从 20 放宽到 30。
Q:zsh: command not found: py
A:macOS/Linux 没有 py 命令。使用:
python3 01_verify_env.py
# 或
uv run python 01_verify_env.py
Q:ModuleNotFoundError: No module named 'langchain_openai'
A:依赖未安装。解决:
uv sync
# 或
uv pip install -r requirements.txt
uv run python 01_verify_env.py
Q:error: No pyproject.toml found
A:在没有 pyproject.toml 的目录执行了 uv。先进入项目目录再执行。
Q:openai.AuthenticationError: 401 Invalid Authentication
A:MINIMAX_API_KEY 无效或为空。检查 .env 文件。
Q:openai.BadRequestError: unknown model
A:模型名不存在或不在账号可用范围。当前项目使用 MiniMax-M2.7。
Q:zsh: 1.0.0 not found(版本约束被识别为重定向)
A:shell 里直接写 >= 被解释为重定向。解决:
uv sync
# 或
uv pip install -r requirements.txt
uv init # 初始化当前目录为 uv 项目
uv sync # 根据 pyproject.toml 同步依赖
uv sync
# 或
uv pip install -r requirements.txt # 新增依赖
uv pip install -r requirements.txt # 按 requirements.txt 安装
uv run python 01_verify_env.py # 验证环境
uv run python 04_ai_generate_ppt.py # 生成 PPT
PPT_PROMPT_STYLE=default uv run python 04_ai_generate_ppt.py # 默认风格
PPT_PROMPT_STYLE=strict_json uv run python 04_ai_generate_ppt.py # 严格JSON
PPT_PROMPT_STYLE=executive uv run python 04_ai_generate_ppt.py # 商业汇报
PPT_PROMPT_STYLE=workshop uv run python 04_ai_generate_ppt.py # 培训讲解
open output/Langchain生态_default.pptx
本篇实现了单链 PPT 生成器——输入主题,输出文件。
但这只是开始。进阶篇我们会做这些事情:
敬请期待。
REF: 本篇对应前 20 个 Demo 中的 D01 LCEL管道语法 + D04 OutputParser
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。