科研工作流的最后一公里是"成稿"——把一堆分析结果、笔记、引用,拼成一份格式规范的 Word 或 PDF。这一步最磨人的不是写内容,而是排版、对齐引用、统一格式。Canguo Science 把它封装成了"学术 Skills":模型负责产出内容,Skill 负责把内容变成规范文档。
这篇讲讲这条链路在工程上怎么落地,用能跑的 Python:让模型返回结构化输出 → 用 python-docx 生成规范 Word → 处理字体 / 表格 / 引用 → 导出 PDF。模型这一层在 Canguo Science 里统一走 CanguoAI 的 OpenAI 兼容入口,所以下面的代码换任意模型都一样,不影响成稿逻辑。代码为便于阅读做了简化。
一、先让模型给你"结构化"的输出
直接把模型吐的一大段 Markdown 硬塞进文档,格式很难控制。更稳的做法是让模型返回结构化 JSON,成稿逻辑只跟固定的数据结构打交道:
import os, json
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LLM_API_KEY"],
base_url=os.environ["LLM_BASE_URL"], # OpenAI 兼容入口的 /v1,换模型只改 model
)
SCHEMA_HINT = """
只输出 JSON,结构为:
{"title": "标题",
"sections": [{"heading": "小节标题", "body": "正文"}],
"references": ["文献1", "文献2"]}
"""
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "你是科研写作助手。" + SCHEMA_HINT},
{"role": "user", "content": "写一份关于扩散模型的简短综述"},
],
response_format={"type": "json_object"}, # 保证输出是合法 JSON
)
data = json.loads(resp.choices[0].message.content)一个细节:用
response_format={"type": "json_object"}时,prompt 里必须出现 "JSON" 字样,否则接口会报错——这是 OpenAI 兼容协议的约定。二、用 python-docx 把结构化数据变成 Word
拿到规整的
data之后,生成文档就是"照着结构填":
from docx import Document
def build_docx(data: dict, out="report.docx"):
doc = Document()
doc.add_heading(data["title"], level=0)
for sec in data["sections"]:
doc.add_heading(sec["heading"], level=1)
doc.add_paragraph(sec["body"])
if data.get("references"):
doc.add_heading("参考文献", level=1)
for i, ref in enumerate(data["references"], 1):
doc.add_paragraph(f"[{i}] {ref}")
doc.save(out)
build_docx(data)结构化的好处在这一步体现得淋漓尽致:模型输出一变,文档跟着变,中间没有脆弱的字符串解析。
三、中文字体:一个绕不过去的坑
python-docx 设中文字体有个经典陷阱——只设
font.name对中文不生效,中文会回退成默认字体。正确做法是额外通过w:eastAsia设一遍:
from docx.shared import Pt
from docx.oxml.ns import qn
def set_base_font(doc, latin="Times New Roman", cjk="宋体", size=12):
style = doc.styles["Normal"]
style.font.name = latin
style.font.size = Pt(size)
# 关键:中文字体必须单独通过 w:eastAsia 设置,否则不生效
style.element.rPr.rFonts.set(qn("w:eastAsia"), cjk)这个坑几乎每个用 python-docx 排中文文档的人都会踩一次,记住
qn("w:eastAsia")就行。四、表格与图表:把实验结果放进去
科研文档少不了表格和图。表格用
add_table,图用add_picture:
from docx.shared import Inches
from docx.enum.text import WD_ALIGN_PARAGRAPH
def add_table(doc, headers, rows):
table = doc.add_table(rows=1, cols=len(headers))
table.style = "Table Grid" # 带边框的内置样式,最稳
for j, h in enumerate(headers):
table.rows[0].cells[j].text = str(h)
for row in rows:
cells = table.add_row().cells
for j, v in enumerate(row):
cells[j].text = str(v)
def add_figure(doc, img_path, caption=None):
doc.add_picture(img_path, width=Inches(5.5))
if caption:
p = doc.add_paragraph(caption)
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_table(doc=Document(), headers=["方法", "准确率"],
rows=[["A", "0.91"], ["B", "0.88"]])
Table Grid是内置样式、任何环境都有,比那些花哨样式名稳;图的宽度用Inches/Cm控制,避免原图过大撑破版面。五、引用与编号:让正文和参考文献对得上
综述里最容易出错的是引用编号——正文里的
[3]得和文末第 3 条对得上。与其手动数,不如维护一个引用登记表,自动分配编号:
class Citations:
def __init__(self):
self._refs = []
self._index = {}
def cite(self, ref: str) -> str:
"""引用一次,返回对应编号(同一文献编号固定)。"""
if ref not in self._index:
self._refs.append(ref)
self._index[ref] = len(self._refs)
return f"[{self._index[ref]}]"
def bibliography(self) -> list[str]:
return [f"[{i}] {r}" for i, r in enumerate(self._refs, 1)]
cite = Citations()
para = f"扩散模型最早由相关工作提出 {cite.cite('Ho et al., 2020')}," \
f"后续被大量改进 {cite.cite('Song et al., 2021')}。"
print(para) # ...提出 [1],后续被大量改进 [2]。
print(cite.bibliography()) # ['[1] Ho et al., 2020', '[2] Song et al., 2021']正文按出现顺序自动编号,文末一次性生成参考文献列表,两边永远对得上——这类机械又易错的活,正是"学术 Skill"最该替你干的。
六、导出 PDF:别指望纯 Python 完美转换
很多人以为有个库能把 docx 一键完美转 PDF,实际没有那么理想。几条现实路径:
docx2pdf:效果好,但依赖本机装了 Word(Windows / macOS);- LibreOffice headless:服务器首选,命令行批量转,无需 Word;
- reportlab:从结构化数据直接另画 PDF,排版完全可控,但样式得自己写。
服务器环境推荐 LibreOffice,一行命令搞定:
import subprocess
def docx_to_pdf(path, outdir="."):
subprocess.run(
["soffice", "--headless", "--convert-to", "pdf", "--outdir", outdir, path],
check=True,
)选哪条看场景:要还原 Word 版式用前两个,要完全掌控排版、且不怕多写代码就用 reportlab。
七、成稿也要可溯源
最后补一句和前面工作流的衔接:Canguo Science 里每段内容都能挂上生成它的
run_id。成稿时把这个信息留在段落或脚注里,读者(或审稿人)就能顺着它回溯到原始运行——引用不是"编"出来的,而是从真实运行档案里回溯出来的。这是把"可溯源"一路贯穿到最终文档的关键一步。小结
把模型输出变成规范文档,工程上就三件事:
- 先要结构化输出(JSON),别让脆弱的字符串解析毁掉排版;
- python-docx 的中文字体要设
w:eastAsia,表格用Table Grid最稳;- 引用自动编号,正文和文末靠登记表对齐;
- PDF 转换按场景选,服务器用 LibreOffice headless。
这些拼起来,就是"从模型输出到一份能交付的 Word / PDF"这条链路。它跟你用哪个模型无关——在 Canguo Science 里模型走 CanguoAI 的统一入口接进来,成稿逻辑一行都不用改。
Canguo Science 的学术 Skills 是怎么把模型输出变成规范 Word / PDF 的?(含代码)
张小明
前端开发工程师
Windows系统终极清理方案:EdgeRemover专业卸载工具完全指南
Windows系统终极清理方案:EdgeRemover专业卸载工具完全指南 【免费下载链接】EdgeRemover A PowerShell script that correctly uninstalls or reinstalls Microsoft Edge on Windows 10 & 11. 项目地址: https://gitcode.com/gh_mirrors/ed/EdgeRemover …
5分钟上手draw.io桌面版:完全免费的跨平台图表工具实战指南
5分钟上手draw.io桌面版:完全免费的跨平台图表工具实战指南 【免费下载链接】drawio-desktop Official electron build of draw.io 项目地址: https://gitcode.com/GitHub_Trending/dr/drawio-desktop 还在为寻找一款既专业又免费的图表软件而烦恼吗…
CSS: underline
.mark { text-decoration-line: underline; //下划线text-decoration-style: wavy; //波浪线 text-decoration-color: red; //红色 }text-decoration-line: overrline; //上划线
【arXiv 2026】世界行动模型即零样本策略|从视频扩散世界模型视角
摘要 本文解读 arXiv 2026 论文《World Action Models are Zero-shot Policies》。该论文提出 DreamZero——一个 14B 参数的世界行动模型(WAM),通过融合预训练视频扩散骨干 Wan2.1、flow matching 联合去噪与自回归分块生成,让机…
2026最新5款团队编程协作工具深度实测方案
作为一个带3人小团队的Tech Lead,过去这大半年我一直在寻找适合小团队的高效AI编程协作方案。我们团队同时维护着几个面向企业客户的SaaS项目,之前一直在用传统的Git Flow配合老款AI编程插件,但总感觉协作效率上不去——每个人的AI提示词风格…
Linux convertquota 命令详解:磁盘配额文件格式转换工具
Linux 学习资料 https://pan.baidu.com/s/1A6qqBk2ViE_Le2cQjSowkQ?pwd7uz8 1. 命令简介 convertquota 是一个用于转换磁盘配额(Disk Quota)数据文件格式的 Linux 系统管理命令。它主要用于将旧版(通常是 v1 版本)的配额数据…