在 BiSheng 里做 DOCX
0. 开工纪律
这一轮只读文档,不要在同一轮里并行调用别的工具。 读完本文件(必要时再读 references)之后, 下一轮才开始动手。把「读 SKILL.md」和「产出交付物」放进同一轮并行调用,等于技能没读。
本技能要求已勾选代码执行器(bisheng_code_interpreter)。 没有它就无法生成 .docx ——
直接告诉用户「请在工具里勾选代码执行器后重试」。
灵思自带
export_docx(markdown → Word)。两者分工是硬的,选错路线是本技能最大的失败风险 —— 动手前先看 §2 的边界表。 一句话:只要「把刚才的内容存成 Word」用export_docx; 要目录、页码、A4 版式、表格列宽、封面、或改用户上传的 .docx,才用本技能。
1. 环境事实(照做,不要试探)
| 项 | 事实 |
|---|---|
| 生成方式 | 只有 python-docx(后端 pyproject.toml 的正式依赖),import docx 直接可用 |
| 中文字体 | run.font.name 只管西文;中文必须写 w:eastAsia。用本技能包的 set_run_font() |
| 不存在的东西 | Node/npm/docx-js、markitdown、defusedxml、pdftoppm、zip/unzip 命令 |
| 可能没有 | pandoc(发布镜像有,手工部署的机器未必)、soffice(只影响可选的渲染预览) |
| 其它可用库 | pandas/numpy、Pillow、PyMuPDF(fitz)、matplotlib、openpyxl、python-pptx、lxml |
| 禁止 | pip install、npm install、任何联网假设(生产多为离线内网) |
| 工作目录 | 执行器 cwd = 工作区根,一律用相对路径 |
output/ |
唯一交付区 |
scratch/ |
中间产物区,不会交付,需自己 os.makedirs |
uploads/ |
用户上传的原件在这里 |
skills/bisheng-docx/ |
本技能包,脚本和参考资料在这里,只读 |
| 绝对禁止 | 写 /output/xxx.docx 这种带前导斜杠的路径 —— 文件被静默丢弃,用户拿不到 |
| 单次执行上限 | 本地执行器 600 秒,E2B 沙箱 300 秒。构建、体检、渲染分三次调用,不要挤在一次里 |
| E2B 沙箱 | 该模式下 skills/ 结构性不可见(文件快照早于技能物化)→ 调包内脚本必报 FileNotFoundError。出现这个报错就别再试包内脚本,把 cookbook §1 的 w:eastAsia 五行 XML 抄进构建脚本自己写 |
| 日志规则 | 执行器二选一:退出码 0 只回 stdout,非 0 只回 stderr → 一切诊断用 print(),子进程的 stdout/stderr 分两行都打出来 |
| 代码围栏 | 提交给执行器的代码里只要出现三反引号围栏,就只执行围栏里的内容 —— 别在代码参数里贴 markdown |
| 可见性 | 退出码 0 时执行器会把本轮新增/修改的文件同步进工作区,之后 ls/read_file 一般能看到。判成功以 退出码 0 + 日志为准,不要反复找文件、更不要重做一遍 |
| 交付物排序 | .md 与 .docx 同为最高档,同档比修改时间 → 后写的 .md 会直接压过 .docx 成为用户看到的头条。过程稿一律 scratch/ |
| 轮次 | 最后两轮代码执行器会被摘除 → 文档要尽早产出,不要拖到收尾 |
1.1 先探一次环境(第一次执行代码时顺手做,只花一轮)
import shutil
try:
import docx
print("python-docx OK")
except ImportError:
print("python-docx MISSING")
print("soffice:", shutil.which("soffice") or "无(只影响可选的渲染预览,不影响生成)")
print("pandoc :", shutil.which("pandoc") or "无(读 .docx 用 inspect_docx.py 即可)")
python-docx MISSING 正常部署不会出现。不要 pip install(共享的离线环境,装了会污染所有租户),
如实告诉用户环境缺依赖、需要运维补装。
2. 选路线(先划清与 export_docx 的边界)
export_docx 走的是 MarkDocx:读 output/ 下的 markdown,纯 python-docx 转成 Word。
下面是它的实测产物特征 —— 对照着选,别凭感觉:
| 维度 | export_docx |
本技能 |
|---|---|---|
| 输入 | 只吃 output/ 下的 markdown,单向 |
直接写 .docx,也能读改已有 .docx |
| 纸张 | US Letter 21.6×27.9cm(不是 A4),左右边距 3.2cm,改不了 | A4 纵/横随选,边距可调 |
| 标题 | 用内置 Heading 样式,中文字体已设(黑体/楷体) | 同样用内置 Heading,字体字号全可控 |
| 目录 / 页码 | 都没有,也加不了 | add_toc + add_page_number_footer |
| 表格 | 宽度 auto,无表头底纹、无列宽、不重复表头 | 列宽写到每个单元格、表头按档(企业档深底白字 / 公文档素表加粗)、跨页重复表头 |
| 图片 | 固定 5.7 英寸宽 | 按版心等比缩放 + 图注 |
| 封面、分节、页眉、分隔线 | 没有 | 都有 |
| 代价 | 一次调用,零风险 | 写脚本 + 体检,2–4 轮 |
| 情况 | 做法 |
|---|---|
| 用户只要「把刚才的内容存成 Word」,没提版式要求 | export_docx,不要用本技能 |
| 用户要目录 / 页码 / A4 / 表格列宽 / 封面 / 特定字体字号 | §3 从零构建 |
| 用户上传了 .docx 要改 | §5 改已有文件(export_docx 做不到) |
| 用户上传了模板要按样式填 | 用 Document("uploads/模板.docx") 打开模板本身,样式自动继承 |
| 还勾选了公文写作类技能 | §2.1:文种文风听它的,版式落地仍走本技能 |
| 拿不准 | 按用户原话里的名词判:出现「正式/对外/打印/汇报稿/公文/目录/页码」走本技能,否则走 export_docx |
2.1 定版式档(决定全篇字体字号,只需定一次)
本技能自带两档。apply_chinese_defaults() 选定后,add_body / add_heading_cn /
add_table / add_toc / 页码全部自动跟随,不必逐处传字体(逐处传的结果通常是
「大部分对、少数几处漏了」,这是本技能历史上最常见的翻车方式)。
| 档 | 什么时候用 | 取值 |
|---|---|---|
gongwen(默认) |
公文、通知、报告、请示、函、纪要、制度办法,以及任何"正式行文" | GB/T 9704-2012:标题二号小标宋、正文三号仿宋_GB2312、一级黑体、二级楷体、行距固定 28 磅 → 读 /skills/bisheng-docx/references/gongwen-gbt9704.md |
modern |
简历、宣传方案、对外提案、周报月报 —— 明确不是公文的 | 微软雅黑 11pt 那一套 → 读 /skills/bisheng-docx/references/design-zh.md |
apply_chinese_defaults(doc) # 公文档,默认,什么都不用做
apply_chinese_defaults(doc, profile="modern") # 简历 / 宣传稿 / 周报
同时勾选了其他公文技能时(用户导入的某单位公文规范、gongwen-draft 之类),分工是硬的:
文种、结构、文风、措辞听它的;版式落地永远是本技能。
它若给出了明确的字体字号(本单位模板要求华文中宋之类),把取值翻成 dict 传进来, 只写有差异的项,其余自动沿用默认档:
apply_chinese_defaults(doc, profile={"body": {"font": "华文中宋"}, "headings": {1: {"font": "方正小标宋简体"}}})它没给版式取值就用默认公文档,不要为此去翻它的一堆 references —— 那会烧掉几轮还找不到。
❌ 不要调用其他技能的 Word 导出脚本(
generate_docx.py、export.py之类)。 它们多是给 Windows 桌面环境写的,在这里会因为"系统未安装公文字体"直接退出 —— 而这个检查在本环境毫无意义:字体名只写进 XML,渲染发生在用户的 Word 里。
3. 从零构建
第 1 步 · 定结构。先把提纲写到 scratch/outline.md。
提纲、草稿、任何 .md 都不许进 output/ —— .md 和 .docx 在交付物排序里同档、按修改时间比,
后写的 .md 会顶掉 .docx 成为用户看到的头条文件。定清楚几级标题、哪里要表、哪里要图。
第 2 步 · 写构建脚本。用 write_file 把完整脚本写到 scratch/build_doc.py,
不要把整段代码塞进代码执行器的参数里 —— 参数过长会被截断,导致反复重试却总差一截。
写法读 /skills/bisheng-docx/references/python-docx-cookbook.md
(中文字体、标题、目录、页码、表格、图片、列表都有可直接抄的片段),
排版规范按 §2.1 定的档读:公文走 references/gongwen-gbt9704.md,
非公文走 references/design-zh.md。
骨架长这样:
import sys, os
sys.path.insert(0, "skills/bisheng-docx/scripts")
from docx_helpers import setup_page, apply_chinese_defaults, add_heading_cn, add_body, \
add_table, add_toc, add_page_number_footer, add_gongwen_title, \
add_signature_block
from docx import Document
os.makedirs("output", exist_ok=True)
doc = Document()
apply_chinese_defaults(doc) # ★ 必须调,且要在 setup_page 之前:定档(默认公文)
section = setup_page(doc) # A4 纵向,页边距按档取(公文 3.7/3.5/2.8/2.6)
# —— 公文档 ——
add_gongwen_title(doc, "关于××××的通知") # 标题:二号小标宋,不进目录
add_body(doc, "各有关部门:", indent=False) # 主送机关顶格
add_body(doc, "正文……") # 自动左空二字
add_heading_cn(doc, "一、xxx", 1) # 一级:三号黑体
add_heading_cn(doc, "(一)xxx", 2) # 二级:三号楷体
add_signature_block(doc, "××××公司", "2026年8月27日")
add_page_number_footer(section)
doc.save("output/xxx.docx")
print("saved")
第 3 步 · 执行:
import subprocess, sys
r = subprocess.run([sys.executable, "scratch/build_doc.py"], capture_output=True, text=True)
print(r.stdout or "(no stdout)")
print(r.stderr[-2000:] if r.stderr else "(no stderr)")
为什么不直接写
python scratch/build_doc.py:PATH 里的python未必是后端那个解释器, 用sys.executable才能保证跑在装了 python-docx 的环境里。下面所有脚本调用都用这个写法。
第 4 步 · 体检并返修(§6)。返修用 edit_file 定点改 scratch/build_doc.py 再重跑,
不要每次重写整份脚本。
4. ★ 中文字体(最容易出的错)
run.font.name = "微软雅黑" 对中文完全无效 —— 它只写 w:ascii/w:hAnsi(西文),
中文由 w:eastAsia 决定。不设的话中文退回主题字体,用户打开就是"字体乱了"。
from docx_helpers import set_run_font
set_run_font(run, "仿宋_GB2312", size_pt=16, bold=True) # ✅
run.font.name = "仿宋_GB2312" # ❌ 中文不生效
apply_chinese_defaults(doc) 会按 §2.1 定的档把 Normal 和 Heading 1–4 一次性设好
(顺带关掉 Word 内置 Heading 4 的斜体)。用 add_body / add_heading_cn / add_table
写的内容自动就是对的,不需要也不应该逐处传字体名。体检脚本专门抓这条。
⚠️ 「用户机器可能没装仿宋_GB2312 / 方正小标宋简体」不是弃用它们的理由: .docx 只把字体名写进 XML,渲染发生在用户的 Word 里,服务端装没装完全不影响产物。 公文字体是国标强制项 —— 换成微软雅黑等于交了一份不合规的公文。
5. 改用户上传的文档
import subprocess, sys
r = subprocess.run([sys.executable, "skills/bisheng-docx/scripts/inspect_docx.py",
"uploads/原文.docx", "--content-only"], capture_output=True, text=True)
print(r.stdout or "(no stdout)")
print(r.stderr[-2000:] if r.stderr else "(no stderr)")
它按段落序号打印样式和文本、逐表打印内容,据此规划改哪里。然后:
- 只改需要改的 run,不要整段重建 —— 重建会丢掉原有字体、编号、批注关联。
- 原文的样式约定压倒本技能的规范,跟着它走。
- 改完另存到
output/,不要覆盖uploads/里的原件。 - Word 会把一句话拆进多个
w:r,看得见的短语在 XML 里未必是连续字符串 —— 跨 run 的替换要先合并 run,或整段重写后手动补格式。
6. 交付前体检(必做)
import subprocess, sys
r = subprocess.run([sys.executable, "skills/bisheng-docx/scripts/inspect_docx.py", "output/xxx.docx"],
capture_output=True, text=True)
print(r.stdout or "(no stdout)")
print(r.stderr[-2000:] if r.stderr else "(no stderr)")
输出分两段:
=== 内容 ===:页面尺寸、段落数、逐段样式与文本、逐表内容。用它核对错字、顺序、缺漏。=== 体检 ===:开头会先说明它按哪个版式档校验(公文 / 非公文,从文档自身推断)。 ERROR 必须修完;WARN 逐条复核(确认无误可交付);INFO 是建议。 覆盖公文文档混入通用字体、中文字体缺w:eastAsia(含样式继承链)、字号过小、标题层级跳级、表格超出版心、 表格首行不像表头、长表不重复表头、图片超宽、目录域没开 updateFields、目录域下没有 Heading、 残留占位符、段落里塞\n、连续空段落、长文档缺页码域。
结尾固定是 合计: x ERROR / y WARN / z INFO 加一行结论。ERROR 清零就会打出「结论: 通过」,
看到它才算过关;没到就改 scratch/build_doc.py 重新生成再跑一次。不要为了消灭 WARN 无限返工。
可选 · 看渲染图:
⚠️ 仅在你确知当前模型支持读图时才做。 这些 PNG 会被编成 base64 图片块发给厂商接口, 而默认的 Qwen/dashscope 通道已知不接收 base64 图片 —— 读图可能直接失败,甚至中断整个请求。 拿不准就跳过这一步,以体检结果为准。
import subprocess, sys
r = subprocess.run([sys.executable, "skills/bisheng-docx/scripts/render_docx.py", "output/xxx.docx"],
capture_output=True, text=True)
print(r.stdout or "(no stdout)")
print(r.stderr[-2000:] if r.stderr else "(no stderr)")
它把每页渲染成 scratch/preview/<名字>/page-01.png,再用 read_file 逐张查看。
渲染用的中文字体只有文泉驿正黑,和用户 Word 里的实际字体宽度不同 ——
行长松紧只作参考,不要为了预览效果反复微调字号。
环境里没有 LibreOffice(.docx 走的是 Writer 组件)时脚本会直说,跳过这一步、以体检结果为准即可。
7. 交付纪律
output/里只放最终的.docx。提纲、构建脚本、预览图、任何.md一律放scratch/。 理由是硬的:交付物排序里.md与.docx同为最高档、同档比修改时间, 而过程稿总是后写的 → 一份output/outline.md会永久压在.docx前面当头条, 用户点开看到的是草稿。scratch/不会交付,但跨轮追问会丢(output/、uploads/才会带到下一轮)。- 文件名用有意义的中文名,如
output/2024年度经营分析报告.docx。 - 收尾时如实说明做了什么、多少页/多少节、有没有目录和页码。 不要声称生成了实际不存在的文件。
8. 绝不要做的事
- ❌ 写
require('docx')或任何 Node 脚本 —— 装不上,npm install也会失败。 - ❌ 跑
unzip/zip拆装 .docx —— 环境里没有这两个命令。要改 XML 用 Python 的zipfile。 - ❌
pip install任何东西。 - ❌ 给代码执行器的路径带前导斜杠(
/output/...、/skills/...)。两套命名空间别混:read_file读技能文档带斜杠(/skills/bisheng-docx/references/design-zh.md), 代码执行器里的路径一律不带(skills/bisheng-docx/scripts/inspect_docx.py、output/x.docx)。 - ❌ 只写
run.font.name就以为中文字体设好了。 - ❌ 因为「用户机器上可能没装仿宋_GB2312 / 方正小标宋简体」就换成微软雅黑、宋体。 字体名只写进 XML,渲染在用户的 Word 里 —— 服务端装没装不影响产物,公文字体是国标强制项。
- ❌ 用手写的
•/1.当列表,用\n当换行。 - ❌ 用单行表格画分隔线(用
add_hr)。 - ❌ 把文档降级成 Markdown 或 PDF 交付。用户要的是 .docx。
- ❌ 退出码 0、日志已写明 saved,却因为一时没看到文件就重做一遍。