angChain 实战教程·番外篇一用 LangChain 构建智能 PPT 生成器前置要求需要 Python 3.10、一个 MiniMax API Key前置知识建议先读完前 20 篇基础教程对 LCEL 有基本了解这篇教程解决什么问题学完前 20 个 Demo你已经掌握了 LangChain 的各种组件用法。但这些用法是散落的——这篇教程用一条主线任务把所有东西串起来做一个智能 PPT 生成器输入一个主题输出一个真实的 .pptx 文件这个任务会用到D01 LCEL 管道语法D04 Output Parser以及 python-pptx 库学完这篇你会有一个真正能用的工具同时深刻理解Chain这个核心理念。最终效果预览图片最终运行效果截图Step 0 · 环境准备0.1 创建项目目录打开终端按顺序执行mkdir langchain-ppt cd langchain-ppt uv init0.2 安装依赖本教程需要以下 Python 包langchain-core1.0.0 langchain-openai1.0.0 python-dotenv1.0.1 python-pptx0.6.23安装方式推荐 requirements.txtuv sync # 或 uv pip install -r requirements.txt什么是 uvuv 是新一代 Python 包管理工具比 pip 更快、更可靠。如果还没安装执行pip install uv0.3 创建 .env 配置文件在项目根目录新建.env文件touch .env编辑.env文件内容# MiniMax API Key必填 MINIMAX_API_KEYsk-your-key-here获取 API Key访问 MiniMax 开放平台 注册后获取0.4 验证环境新建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( modelMiniMax-M2.7, base_urlhttps://api.minimaxi.com/v1, api_keyos.getenv(MINIMAX_API_KEY), temperature0.7 ) response llm.invoke(你好) print(response.content)运行uv run python 01_verify_env.py看到你好的回复说明环境 OK。Step 1 · 让模型吐出结构化 PPT 大纲1.1 什么是 Output Parser在前 D04 里学过LLM 输出的本质是字符串。想让程序自动处理 LLM 的输出必须先把字符串解析成结构化数据。OutputParser就是做这件事的组件图片Pydantic Parser 工作原理LangChain 内置了多种 Parser本教程用PydanticOutputParser——因为它可以定义字段描述Field description让 LLM 知道每个字段应该填什么同时自动校验输出格式。1.2 定义 PPT 大纲的数据模型用 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_length2, max_length5 ) notes: str Field(description演讲备注) class PPTSchema(BaseModel): 完整PPT大纲 title: str Field(descriptionPPT主题标题不超过30字) subtitle: str Field(descriptionPPT副标题概括主题) author: str Field(description演讲者姓名) slides: List[Slide] Field( description幻灯片列表, min_length5, max_length15 )Field description 为什么重要这里的 description 就是告诉 LLM我希望你输出什么格式。LLM 会参考它来生成内容。1.3 组合 Parser 到 Chain完整代码与 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_length2, max_length5) notes: str Field(description演讲备注) class PPTSchema(BaseModel): title: str Field(descriptionPPT主题标题不超过30字) subtitle: str Field(descriptionPPT副标题概括主题) author: str Field(description演讲者姓名) slides: List[Slide] Field(description幻灯片列表, min_length5, max_length15) # Parser Prompt 组合 parser PydanticOutputParser(pydantic_objectPPTSchema) prompt ChatPromptTemplate.from_messages([ (system, 你是一个专业的PPT内容策划专家。), (human, 请为「{topic}」这个主题生成一份完整的PPT大纲。\n\n{format_instructions}) ]) prompt prompt.partial( format_instructionsparser.get_format_instructions() ) # 串联 Chain chain prompt | ChatOpenAI( modelMiniMax-M2.7, base_urlhttps://api.minimaxi.com/v1, api_keyos.getenv(MINIMAX_API_KEY), temperature0.7 ) | parser # 执行 result chain.invoke({topic: 量子计算技术趋势}) print(result.model_dump_json(indent2))运行结果JSON 摘要{ title: 量子计算技术趋势, subtitle: 从原理到应用的全景解析, author: LangChain助手, slides: [ {title: 什么是量子计算, subtitle: 理解量子力学基础, points: [量子叠加态, 量子纠缠原理, 量子比特], notes: 开场要通俗易懂}, ... ] }图片Pydantic Parser 完整流程Step 2 · 把大纲变成真实的 PPTX 文件2.1 python-pptx 基础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 代码与效果对照2.2 把 Parser 结果对接到 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_length2, max_length5) notes: str Field(description演讲备注) class PPTSchema(BaseModel): title: str Field(descriptionPPT主题标题不超过30字) subtitle: str Field(descriptionPPT副标题) author: str Field(description演讲者姓名) slides: List[Slide] Field(description幻灯片列表, min_length5, max_length15) # Chain与Step1相同 parser PydanticOutputParser(pydantic_objectPPTSchema) prompt ChatPromptTemplate.from_messages([ (system, 你是一个专业的PPT内容策划专家。), (human, 请为「{topic}」生成PPT大纲。\n\n{format_instructions}) ]).partial(format_instructionsparser.get_format_instructions()) chain prompt | ChatOpenAI( modelMiniMax-M2.7, base_urlhttps://api.minimaxi.com/v1, api_keyos.getenv(MINIMAX_API_KEY), temperature0.7 ) | parser # PPTX 生成函数 def build_pptx(schema: PPTSchema, output_path: str) - None: 把PPTSchema写入真实PPTX文件 os.makedirs(os.path.dirname(output_path), exist_okTrue) 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 文件已生成。Step 3 · 用 LCEL 把所有组件串联成完整系统3.1 完整 Chain 全貌回顾一下整个流程图片LCEL Chain 串联图我们用 LCEL 的管道语法把四步串成一条线。完整代码与 demos/04_ai_generate_ppt.py 一致简化版不含多风格 Prompt# generate_ppt_full.py # 完整可运行版本输入主题 → 输出 PPTX 文件 # 依赖langchain-openai1.0.0 python-dotenv1.0.1 python-pptx0.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, errorsreplace) 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:i1] return text load_dotenv() # 数据模型 class Slide(BaseModel): title: str Field(description幻灯片标题不超过20字) subtitle: str Field(description副标题不超过30字) points: List[str] Field(description要点列表, min_length2, max_length10) notes: str Field(description演讲备注) class PPTSchema(BaseModel): title: str Field(descriptionPPT主题标题不超过30字) subtitle: str Field(descriptionPPT副标题) author: str Field(description演讲者姓名) slides: List[Slide] Field(description幻灯片列表, min_length5, max_length30) # 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( modelMiniMax-M2.7, base_urlhttps://api.minimaxi.com/v1, api_keyos.getenv(MINIMAX_API_KEY), temperature0.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_okTrue) 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()3.2 最终运行效果依赖清单包名版本用途langchain-core1.0.0LangChain 核心组件langchain-openai1.0.0LLM 调用兼容 MiniMaxpython-dotenv1.0.1环境变量加载python-pptx0.6.23PPTX 文件生成安装命令uv sync # 或 uv pip install -r requirements.txt常见问题Q报错MINIMAX_API_KEY is not set或认证错误A环境变量未正确加载。检查.env文件是否在项目根目录API Key 是否正确复制运行前确认load_dotenv()已执行QJSON 解析失败Invalid json output 或 OUTPUT_PARSING_FAILUREALLM 输出格式不稳定经常包含额外的解释性文字。解决方案使用extract_json_from_llm_output()自动提取 JSON项目已实现适当放宽 schema 限制slides 从 20 放宽到 30QNameError: name get_output_path is not definedA脚本中缺少函数定义。确保添加def get_output_path(filename: str) - str: return os.path.join(output, filename)QFileNotFoundError 或 PermissionError 保存 PPT 失败Aoutput/目录不存在或没有写入权限。解决os.makedirs(os.path.dirname(path), exist_okTrue)Q乱码问题或特殊字符显示异常A文本中包含 emoji 或特殊字符。解决项目已实现clean_text_for_ppt()函数使用简洁格式如-而非•Q1 validation error for PPTSchema slides 数量超限A模型生成的幻灯片数量超过 schema 限制。已将max_length从 20 放宽到 30。Qzsh: command not found: pyAmacOS/Linux 没有py命令。使用python3 01_verify_env.py # 或 uv run python 01_verify_env.pyQModuleNotFoundError: No module named langchain_openaiA依赖未安装。解决uv sync # 或 uv pip install -r requirements.txt uv run python 01_verify_env.pyQerror: No pyproject.toml foundA在没有pyproject.toml的目录执行了uv。先进入项目目录再执行。Qopenai.AuthenticationError: 401 Invalid AuthenticationAMINIMAX_API_KEY无效或为空。检查.env文件。Qopenai.BadRequestError: unknown modelA模型名不存在或不在账号可用范围。当前项目使用MiniMax-M2.7。Qzsh: 1.0.0 not found版本约束被识别为重定向Ashell 里直接写被解释为重定向。解决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切换 Prompt 风格运行PPT_PROMPT_STYLEdefault uv run python 04_ai_generate_ppt.py # 默认风格