这个工具的起点很真实:写本科毕业论文时,我花了大量时间手动调字体、字号、标题层级。当时 Manus 这类 AI Agent 正火,但实测它们满足不了高校论文格式的硬性要求。所以我没指望通用 Agent,自己做了个轻量工具,只解决”能用代码确定性实现”的那部分——格式这种东西要的是精确,不是模型即兴发挥。
我重写这篇时回去把代码通读了一遍,发现当年 README 写的有些功能其实代码里并没真正做到。下面只讲代码真实做了什么,也把那几处”宣传大于实现”如实记下来——这本身是个值得复盘的教训。
真正硬核的一招:直接写 Word XML 的 w:eastAsia
普通 python-docx 设字体时,中文常常不生效——因为 Word 的字体分槽:西文走 w:ascii/w:hAnsi,中文字符走 w:eastAsia 槽位,而高层 API 只设了前者。我的渲染器直接操作底层 XML:
run.font.name = style.familyrun._element.rPr.rFonts.set(qn('w:eastAsia'), style.family)qn('w:eastAsia') 把命名空间前缀展开成全限定名,直接给中文槽位赋值,这样中文字体才真正生效、且在任何设备打开都标准统一。这是这个项目唯一真正硬核、且确实正确的技巧。
但要诚实说明:当年 README 宣传”中文宋体、英文 Times 双通道独立 + 衬线无衬线智能回退”,回看代码,w:ascii 和 w:eastAsia 其实被设成了同一个字体名,样式数据结构里也只有一个 family 字段——物理上承载不了中英文两种字体。所以它做到的是”正确设置中文字体”,不是”中英文用不同字体”。那个智能回退在代码里并不存在。这是我当年把”想做的”写成了”做到的”。
一个有意思(也有争议)的架构决策:让 LLM 查表
口语化字号字体(“楷体""小四”)要映射成系统码(KaiTi、12.0pt)。我的做法不是在 Python 里查表,而是把整张字体映射表 json.dumps 后塞进 prompt,让 LLM 在 prompt 里查。
好处是灵活——加一个字体改 JSON 配置就行、不用动代码;代价是把一个本该确定性的映射交给了概率模型,靠 temperature=0.1 赌稳定。这个选择见仁见智,但它是这个项目的核心架构,我把它如实摆出来。配套有兜底:配置文件找不到时用一份硬编码的保底映射(注释原话是”为了防止变回宋体”)。
真正的金矿:6 个 fix 脚本背后的调试史
仓库里有 6 个 fix_*.py 一次性补丁脚本,是我当年”改完线上文件”的痕迹。把它们串起来,正好是 LLM 工程的三类经典坑:
- LangChain 花括号转义:prompt 里的字面 JSON 花括号
{}被 LangChain 当成变量插值符,必须双写{{}}。 - 过度修复误伤代码:上一步把 Python 代码里本不该转义的
{"type":"json_object"}也误转成了{{}},又得写个脚本还原——典型的”修 prompt 误伤代码”。 - LLM 输出的 JSON 不合法:模型文本里的裸双引号撑爆
json.loads(往 prompt 注入转义指令)、输出小写 align 撑爆 Enum 校验(merge 时强制.upper())、偶尔包 ```json 围栏(代码里剥离)。 - Windows GBK 编码:在 Windows 上默认 GBK 存盘,传到 Linux 按 UTF-8 读就乱码,得遍历文件
gbk 读 → utf-8 写回。
这些坑没法编,是真接了 LLM、跨了 Windows/Linux 才会撞上的。整个工程贯穿一个”LLM 不可靠”的防御式假设:配置找不到→硬编码兜底、LLM 挂了→原文当单个段落、JSON 不合法→剥围栏/补大小写。这是我觉得这个小项目里最实在的部分。
技术栈与诚实的边界
FastAPI 后端、Streamlit 前端、LangChain 管 prompt、智谱 GLM-4 出 JSON、python-docx 生成文档。要补一句诚实的:仓库里的 RAG(Chroma 向量库)在提交状态下是空的,所以语义检索那条链路当时没真正跑通,实际走兜底默认样式。
后来补的(AI 协作完成,诚实标注)
回头给它做了一轮加固,几处值得记(这些是后来补的、AI 辅助完成,不是当年手搓):
- 预设规则现在真存在了:补了一份本科毕设格式预设(真实学校规范——标题黑体三号、正文宋体小四、1.5 倍行距),
?school_id=…可直接套用。上面说的”预设规则文件不存在”已不成立。 - 表格渲染:用
python-docx的add_table把识别出的二维表格真正插进 Word——支持二维数组和{rows:[…]}两种结构、自动补齐短行/空单元格、表头加粗、网格线、表标题置于表上方。一个有意的防御细节:非法表格数据降级成红色占位符、不中断整篇生成,和这个项目”假设 LLM 输出不可靠”的基调一致。 - 一个编不出来的真坑,正好解释”预设当年为什么没生效”:样式 schema 用的字段是
family / size / bold,而配置文件和 prompt 示例里写的是font_name / font_size / is_bold。Pydantic v2 默认extra='ignore',于是这些对不上名的字段在构造配置时被静默丢弃——预设里的字体/字号/加粗全部失效,实际只有对齐生效。这就是预设长期”形同虚设”的根因,解法是把字段名统一到 schema(schema 为唯一真相源)。静默丢弃这种 bug 不报错,最难查。
小结
这是个典型的”自己痛点驱动”的本科小工具。它有一个真正正确的硬核技巧(w:eastAsia ),一个有争议但自洽的架构(prompt 查表),和一段编不出来的真实调试史(6 个 fix 脚本)。我也借这次重写把当年”宣传大于实现”的几处如实更正了——回到代码看真相,比照着 README 复述,要靠谱得多。