数据截至 (上游 commit fb951af7744d)
04 · 工程精华、边界与代码地图
这一章是“带走的精华”:STORM 里那些不显然但很实用的小设计;它刻意不做什么、会在哪崩;以及完整的代码跳转地图。
1. 巧妙之处(可借鉴的技术)
1.1 引用编号的“局部→全局”统一与越界裁剪
妙在哪: 每个章节是并行写的,LLM 只看到本节资料的局部编号 [1][2];但全文需要一套统一编号,而且 LLM 经常会引用不存在的编号。STORM 用一个占位符两步替换法既统一编号、又不会“替串”。
update_citation_index(utils.py:540-550)先把所有旧编号换成 __PLACEHOLDER_n__,再把占位符换成新编号——避免“把 [1] 换成 [10] 时又被后续规则误伤”:
# 示意,源码精简自 utils.py:540-550
for original in citation_map: # 第一遍:全换成占位符
s = s.replace(f"[{original}]", f"__PLACEHOLDER_{original}__")
for original, unify in citation_map.items(): # 第二遍:占位符→新编号
s = s.replace(f"__PLACEHOLDER_{original}__", f"[{unify}]")
配合 update_section(storm_dataclass.py:249-299):它先正则抓出本节用到的引用号,把超过资料条数的越界引用直接删掉(max_ref_num > len(info_list) 时清除),再只保留真正被引用的资料并重映射编号。最后 reorder_reference_index(storm_dataclass.py:374-412)按引用在全文出现顺序重排成 1,2,3…,并删掉没被引用的参考。
1.2 砍掉被 token 上限截断的“半截句”
妙在哪: LLM 输出常因 max_tokens 在句子中间断掉,留下半句脏话。remove_uncompleted_sentences_with_citations(utils.py:367-425)用正则找最后一个“句末标点(可带引用)”,只保留到那里。顺手还把 [1, 2, 3] 拆成 [1][2][3] 并去重排序,统一引用风格。
1.3 取材用 嵌入相似度,不是全量塞
妙在哪: 资料可能很多,全塞进 prompt 又贵又超窗。StormInformationTable.retrieve_information(storm_dataclass.py:119-145)对每个章节查询,用 SentenceTransformer 编码后算 cosine 相似度取 top-k,再按 URL 聚合 snippet返回。Co-STORM 的信息归位也用同一招做候选排序(information_insertion_module.py:149-163)。
1.4 一切皆 dspy.Signature:prompt 即“带类型的函数签名”
妙在哪: STORM 的每个 LLM 调用都写成一个 dspy.Signature 子类(如 AskQuestionWithPersona、WriteSection、InsertInformation),docstring 是指令、InputField/OutputField 是输入输出。好处是 prompt 集中、可读、可被 dspy 优化器调。with dspy.settings.context(lm=...) 在不同步骤切换不同模型;多处 show_guidelines=False 是为了“对不同 LLM 家族更鲁棒”(见 article_polish.py:88-89 的注释)。