
1. 从 Copilot 的一次翻车说起PyPDF2 那个 DeprecationError如果你看过《AI 加持下的 Python 编程实战》第九章应该记得那个批量合并 PDF 封面的场景reports文件夹里有 100 份报告covers里有对应的 100 张封面目标是让 AI 生成一段 Python 脚本把封面插到每份报告前面输出到final。原书写的是用 Copilot 配合 PyPDF2 来做但真实执行时遇到一个很典型的坑你让 AI 按旧教程写PyPDF2.PdfFileReader而 PyPDF2 3.0.0 已经把PdfFileReader删掉了运行直接抛PyPDF2.errors.DeprecationError。这种接口破坏性变更在小众第三方库里太常见了解决路径无非三条退回历史版本、按报错提示换新接口、换一个库重写。这次我换了一种跑法不用 Copilot 一步步点开 IDE 补全而是用 Codex 做执行 Agent把模型通道切到 TaoToken让它在同一个长会话里完成「生成脚本 → 发现报错 → 改用 pdfrw」三件事。TaoToken 在这里只承担一件事给 Codex 提供一个稳定的模型 API 通道你只要去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 拿一把 Key填进 Codex 的配置文件后面的调试过程就交给 Agent 自己迭代。这篇文章就把完整路径记录下来包括配置文件怎么写、报错怎么对照、最终demo.py长什么样以及中途换库时 Codex 是怎么比 Copilot 更省事的。2. 先把 Codex 指向 TaoToken配置文件才是关键2.1 准备材料官网拿 Key模型 ID 以广场为准开始之前先打开 TaoToken 注册账号在控制台创建 API Key记下形如sk-...的密钥。这个 Key 就是 Codex 访问模型通道的凭证创建好后不需要在官网多做停留直接进入本地配置。注意官网只负责注册、建 Key、看模型广场和用量真正填进 Codex 的 Base URL 是https://taotoken.net/api末尾不要加/v1这是很多人第一次配错的地方。关于模型 ID不要凭记忆写。TaoToken 的模型广场会列出当前可用的模型 ID比如 Claude 系列的claude-...或 GPT 系列的gpt-...以 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 模型广场当时的列表为准。你可以在模型对话页面先用同一把 Key 发一条消息验证连通性确认没填错再继续。这一步能帮你区分「配置错了」和「模型本身不可用」省得后面调试代码时还要分心排查通道问题。2.2 修改 Codex 配置Base URL 填对模型 ID 写准Codex 的配置文件在~/.codex/config.toml它支持通过model_provider指定自定义的 Base URL。下面这份配置是我实测可用的注意base_url只写到/api没有加/v1# ~/.codex/config.toml model 你的模型ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY环境变量TAOTOKEN_API_KEY的值就是你在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建的那把 Key。你也可以直接在 shell 里设置export TAOTOKEN_API_KEYYOUR_API_KEY设置完先跑一条最简单的命令验证通道codex exec say hi如果返回正常说明 Codex 已经能通过 TaoToken 拿到模型响应。如果报 401检查 Key 是否复制完整如果报连接错误检查base_url是否误加了/v1——这是最常见的两个问题和模型没关系。3. 让 Codex 在长会话里解决 PyPDF2 的 DeprecationError3.1 第一步把需求说清楚让 Codex 生成脚本原文中作者用 Copilot 时先明确需求再让 AI 生成完整工具函数。用 Codex 时这个流程可以更顺因为你不需要反复切窗口直接在同一个会话里描述需求即可。我的提示词是这样的写一个 Python 脚本 demo.py功能是批量合并 PDF - reports 目录下有 1.pdf 到 100.pdf每个文件可能有多页 - covers 目录下有 cover1.pdf 到 cover100.pdf每个文件只有一页 - 把每个 cover 放到对应 report 的开头 - 合并结果输出到 final 目录文件名是 final1.pdf、final2.pdf ... - 用命令行参数接收 reports、covers、final 三个目录 - 代码要处理文件不存在的情况不要因为缺一个文件就崩溃Codex 很快就给出了基于 PyPDF2 的版本核心逻辑和原文几乎一样用PdfReader读取封面和报告用PdfWriter逐页写入最后保存。注意这次 Codex 生成的是PdfReader而不是PdfFileReader说明模型的训练数据已经包含了 PyPDF2 3.0.0 之后的变更。但为了完整复现原文的踩坑场景我故意把代码改成旧接口然后运行想看 Codex 怎么应对。3.2 第二步人为制造报错看 Codex 如何定位把demo.py里的PdfReader改成PdfFileReader运行python demo.py reports covers final报错如期而至Traceback (most recent call last): File F:\mydesktop\ai-demo\demo.py, line 24, in add_covers report_reader PyPDF2.PdfFileReader(report_file) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File F:\mydesktop\ai-demo\venv\Lib\site-packages\PyPDF2\_reader.py, line 1974, in __init__ deprecation_with_replacement(PdfFileReader, PdfReader, 3.0.0) File F:\mydesktop\ai-demo\venv\Lib\site-packages\PyPDF2\_utils.py, line 369, in deprecation_with_replacement deprecation(DEPR_MSG_HAPPENED.format(old_name, removed_in, new_name)) File F:\mydesktop\ai-demo\venv\Lib\site-packages\PyPDF2\_utils.py, line 351, in deprecation raise DeprecationError(msg) PyPDF2.errors.DeprecationError: PdfFileReader is deprecated and was removed in PyPDF2 3.0.0. Use PdfReader instead.这段报错把解决方案写得很清楚用PdfReader替代PdfFileReader。在原文中作者面对同样的报错自己做了两个选择退回 PyPDF2 3.0.0 之前的版本或者换用 pdfrw。而在 Codex 的长会话里我直接把报错贴回对话让它决定怎么办。3.3 第三步Codex 的排障路径——不纠结旧版本直接换 pdfrw我把完整 traceback 发给 Codex附上一句这是运行报错PyPDF2 3.0.0 里 PdfFileReader 已经移除了。请选择一种最省事的方案修复脚本。Codex 没有建议我退回旧版本而是直接推荐改用pdfrw理由是退回 PyPDF2 旧版本只是临时绕过以后还会遇到其他兼容问题换库是从根源上解决。这个判断和原文作者最终的选择一致但省去了手动搜索替代库、查文档、对比优缺点的过程。它给出的修复方案分两步。先安装依赖pip install pdfrw再把demo.py改成 pdfrw 版本。完整代码如下import os from pdfrw import PdfReader, PdfWriter def add_covers(report_dir, cover_dir, final_dir): report_dir is a directory of reports named 1.pdf, 2.pdf, and so on. These files may be one page or more than one page. cover_dir is a directory of covers, with one cover per report. The filenames in this directory are cover1.pdf, cover2.pdf, and so on. Each of these files is one page. Add the cover to the beginning of each report, and store all resulting pdfs in final_dir. if not os.path.exists(final_dir): os.makedirs(final_dir) for i in range(1, 100): # Assuming a maximum of 99 reports report_path os.path.join(report_dir, f{i}.pdf) cover_path os.path.join(cover_dir, fcover{i}.pdf) if not os.path.exists(report_path) or not os.path.exists(cover_path): continue report_pdf PdfReader(report_path) cover_pdf PdfReader(cover_path) writer PdfWriter() writer.addpage(cover_pdf.pages[0]) for page in report_pdf.pages: writer.addpage(page) final_pdf_path os.path.join(final_dir, ffinal{i}.pdf) writer.write(final_pdf_path) print(fCreated {final_pdf_path} with cover and report {i}.pdf) if __name__ __main__: report_directory reports cover_directory covers final_directory final_reports add_covers(report_directory, cover_directory, final_directory) print(All reports processed with covers added.)注意两个细节writer.addpage是 pdfrw 的写法不是 PyPDF2 的add_pagePdfReader的导入路径也变了。如果让 Copilot 来改它可能只会把PdfFileReader换成PdfReader因为那是报错信息里直接提示的方案但换库这种更大胆的判断需要模型真正理解报错背后的生态现状Codex 在长会话里结合上下文能做到这点。4. 本地验证与三个易错点对照4.1 运行 demo.py 验证合并结果保存代码后在项目目录下执行python demo.py如果一切正常你会看到类似这样的输出Created final_reports/final1.pdf with cover and report 1.pdf Created final_reports/final2.pdf with cover and report 2.pdf ...打开final_reports目录用任意 PDF 阅读器抽查几个文件每一份的首页应该是封面后面跟着报告正文。如果你用的是 Codex 生成的修复版这个过程是自动完成的——它在会话里已经替你想好了怎么验证你只需要把执行结果贴回去它会根据输出判断是否成功。4.2 配置相关Base URL 的 /v1 陷阱我在这次实操里踩了一个很典型的坑第一次配置 Codex 时顺手把 Base URL 写成了https://taotoken.net/api/v1结果 Codex 一直报 404。后来把 URL 改成https://taotoken.net/api就正常了。TaoToken 的兼容通道地址不需要手动加/v1这一点和很多直接抄官方文档的教程不一样。如果你遇到连接类错误先检查config.toml里的base_url是否多写了后缀。4.3 代码相关pdfrw 和 PyPDF2 的 API 差异如果你的场景和我一样是从 PyPDF2 迁移到 pdfrw有四个差异最容易踩PdfReader的导入路径from pdfrw import PdfReader不是from PyPDF2 import PdfReader写页面的方法pdfrw 用addpagePyPDF2 用add_pagewriter.write()的参数是输出路径不是文件对象不需要with open()包裹文件不存在时PdfReader直接抛异常代码里要先判断os.path.exists如果你拿到的报错是AttributeError: module pdfrw has no attribute PdfReader说明安装的 pdfrw 版本不对pip install --upgrade pdfrw后再试。4.4 模型通道相关401 和模型不可用是两回事如果 Codex 报 401问题几乎一定出在 Key 上——要么没设置环境变量TAOTOKEN_API_KEY要么 Key 复制多了空格。如果 Key 确认无误但请求超时去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的控制台看看是不是模型服务临时波动或者换一个模型 ID 试试。用 Codex 排障时区分「通道问题」和「代码问题」很重要我一般先在对话里问一句「当前模型是什么Base URL 是否连通」确认通道没问题再继续查代码。5. 跑通之后去控制台对一下这次调用配置保存后先在 TaoToken 模型对话 里用同一把 Key 发一条测试消息确认模型 ID 和 Base URL 没填错。若要长期写代码可以打开 Coding Plan 看套餐是否够用Key 在 控制台 API Keys 创建。Claude Code 环境变量对照见 接入文档。验证调用是否记账也很简单跑完demo.py后打开 TaoToken 控制台的用量页面应该能看到刚才这次会话对应的 token 消耗。如果显示为 0说明调用走了环境变量之外的默认通道检查 Codex 的config.toml是否真的加载了taotokenprovider。这一步做完你手上就有了一套完整可复用的「Codex TaoToken 跑 Python 批处理」的链路下次碰到类似的 PDF 合并、文件重命名、表格批处理直接让 Codex 在长会话里搞定就行。个人建议如果你是第一次配 Codex 接第三方 API 通道别急着跑复杂的批处理任务先拿一个最简单的需求比如codex exec print hello验证通道再逐步加复杂度。这样即使出问题你也能很快判断是配置还是代码的锅。