用 LLM 提取参考文献时,先用正则分段再逐条解析比一次全量提取效果更好,因边界判断是排版问题而非语义问题,分段后可利用编号数量做断言校验。
The instinct is to hand the whole reference list to a model and ask for an array of citation objects. On a list of eighty entries that produces seventy-three, with two merged and five hallucinated into tidiness. The fix is to make segmentation a separate, deterministic step.
直觉会让人把整个参考文献列表丢给模型,要求返回一个 citation 对象数组。在一份八十条目的列表上,这种做法会产生七十三条,其中两条被合并、五条被"整理"得干干净净——实际上是幻觉。解决办法是把分节作为一个独立的、确定性的步骤来做。
Two stages, and why the first one is harder
两阶段,以及为什么第一阶段更难
Parsing one reference string into author, year, title and venue is a task current models do well. Deciding where one reference ends and the next begins is a task they do badly, because the boundary is typographic rather than semantic: a hanging indent, a numeric label, a line break that is either a wrap or a separator depending on the column width.
将一条参考文献解析为作者、年份、标题和出版物,这是一项当前模型做得很好的任务。而判断一条参考文献在哪里结束、下一条在哪里开始,则是他们做得很差的任务——因为边界是排版上的而非语义上的:悬挂缩进、数字标签、换行——在列宽不同时,可能是软换行也可能是分隔符。
Splitting the work also gives you a count to assert against. If the list is numbered 1 to 84 and you segmented 81 entries, you know the parse is wrong before you have looked at a single field. A single-call extraction gives you no such handle — a merged pair looks identical to a list that was three shorter.
将工作分开还能让你获得一个可用于校验的数量。如果列表编号为 1 到 84,而你分出了 81 条,你就知道解析是错误的——在查看任何字段之前就知道。单次调用提取没有这样的抓手——两条被合并的文献看起来和一条本来就短了三条的列表完全一样。
Step 1: segment the list
步骤一:分节列表
Three reference-list styles cover almost everything, and each has a different boundary signal:
三种参考文献列表样式几乎覆盖了所有情况,每种都有不同的边界信号:
Numbered (Vancouver, IEEE). Each entry begins with 1. or [1]. Boundary detection is a regex, the sequence is monotonic, and you get the assertion for free.
数字编号式(Vancouver、IEEE)。每条条目以 1. 或 [1] 开头。边界检测靠正则表达式,序列是单调的,而且你免费得到了数量校验。
Author-date (APA, Harvard, Chicago author-date). No labels. Entries are separated by a hanging indent — the first line starts at the margin and continuations are indented — which is invisible in a flat text stream and obvious in the layout.
作者-日期式(APA、Harvard、Chicago author-date)。没有标签。条目由悬挂缩进分隔——第一行从页边开始,后续行缩进——这在纯文本流中不可见,但在排版布局中很明显。
Note-bibliography (Chicago notes). Also unlabelled, also hanging-indented, and additionally uses a three-em dash for a repeated first author, which is the case discussed below.
注释-参考文献式(Chicago notes)。同样无标签、同样悬挂缩进,另外对重复的第一作者使用三连破折号——这就是下面要讨论的情况。
For the unlabelled styles, segment on the indent rather than on the text. If you have coordinates from the PDF, an entry starts at every line whose left edge is at the block minimum and continues through every line indented further. If you do not have coordinates, a reasonable proxy is a line that begins with a capital letter followed by a comma-and-initial pattern, but it is a proxy and it will miss entries beginning with an institutional author or a title.
对于无标签样式,依据缩进而不是文本来分节。如果你有 PDF 的坐标信息,每条条目从左边缘位于块最小值的行开始,延续到所有进一步缩进的行。如果没有坐标,一个合理的代理特征是:以大写字母开头、随后是逗号和首字母模式的行——但这只是代理,会漏掉以机构作者或标题开头的条目。
function segmentNumbered(text) {
// Split before a line-initial "1." / "[1]" / "1)".
const parts = text.split(/\n(?=\s*(?:\[\d+\]|\d+[.)])\s)/);
return parts.map((p) => p.trim()).filter(Boolean);
}
function segmentByIndent(lines) {
// lines: [{ text, x }] from the PDF text layer, one entry per visual line.
const margin = Math.min(...lines.map((l) => l.x));
const out = [];
for (const line of lines) {
if (Math.abs(line.x - margin) < 1.5) out.push(line.text);
else out[out.length - 1] += " " + line.text;
}
return out;
}
The tolerance of 1.5 points matters. Text-layer x-coordinates are not exact — kerning and the width of an opening quotation mark shift the reported origin — and a strict equality test puts every reference beginning with a quotation mark into the previous entry.
1.5 磅的容差很重要。文本层的 x 坐标并不精确——字距调整和前引号的宽度会影响报告的起始位置——而严格的等值测试会把所有以前引号开头的参考文献归入前一条条目。
The conventions that destroy authorship
破坏作者信息的排版惯例
Two style rules silently delete the field you most want, and neither is recoverable from the entry in isolation.
两条排版规则悄悄删除了你最想要的字段,而且仅凭条目本身都无法恢复。
The repeated-author dash. In Chicago-style bibliographies, consecutive works by the same author replace the name with a three-em dash: ———. 2019. A parser that reads each entry independently records the author as a dash, or as empty, for every entry after the first. The entry is only parseable in the context of its predecessor, which means segmentation order is load-bearing and you cannot parallelise the parse across a shuffled list. Carry the previous entry's author forward, and note that the dash can appear in several widths — em dash repeated three times, a single three-em dash character, or a run of hyphens in a plain-text rendering.
重复作者破折号。在 Chicago 格式的参考文献中,同一作者连续发表的作品会将姓名替换为三连破折号:———。2019 年。如果解析器独立读取每条条目,会把第一条之后的所有条目的作者记录为破折号或空值。该条目只有在与其前驱的上下文中才能被解析,这意味着分节顺序是"承力"的——你无法对打乱顺序的列表进行并行解析。需要将前一条目的作者向前携带,并且注意破折号可能有多种宽度——重复三次的 em 破折号、单个三连 em 破折号字符,或纯文本渲染中的一串连字符。
Et al. truncation. Most styles abbreviate long author lists after a threshold. The information is gone from the page; no amount of prompting recovers it. The right response is to record what is there and mark the list as truncated, so that a downstream match on "same author list" does not fail against the full record from a lookup:
"等"截断。大多数格式在作者列表超过阈值后进行缩写。信息已从页面上消失——无论多少提示都恢复不了。正确的做法是记录实际内容并标记列表为截断,这样下游在匹配"相同作者列表"时不会因为查到的完整记录而失败:
{ "authors": [{ "family": "Rivera", "given": "A." }], "authors_truncated": true }
Two further traps are worth pre-empting. Page ranges use an en dash and often an elided upper bound — 1123–31 means 1123 to 1131, not 1123 to 31 — so expansion is a rule, not a parse. And a trailing period is part of the sentence, not part of the DOI, which is the single most common way a greedy identifier regex captures a character that makes the DOI unresolvable.
还有两个陷阱值得提前防备。页码范围使用 en 破折号,且上界常做省略——1123–31 表示 1123 到 1131,而不是 1123 到 31——所以扩展是规则而非解析。另外,末尾的句点属于句子的一部分,不属于 DOI——这是贪婪的标识符正则表达式捕获字符、导致 DOI 无法解析的最常见方式。
Step 2: parse each entry
步骤二:解析每条条目
With entries isolated, per-entry parsing is a constrained structured output task. Use a strict schema so the model cannot invent a field, and give it an explicit entry_type enum — a book chapter, a conference paper and a preprint have genuinely different fields, and forcing all three into a journal-article shape loses the container title. Designing that enum so an unfamiliar entry type has somewhere to go is the subject of schema design for unseen variants.
条目被隔离后,每条目解析是一个受限的结构化输出任务。使用严格的 schema 以防止模型编造字段,并为其提供明确的 entry_type 枚举——书籍章节、会议论文和预印本有真正不同的字段,强迫三者都套用期刊文章的格式会丢失容器标题。设计这个枚举时,要让不熟悉的条目类型有去处——这是为未见变体设计 schema 的课题。
const CITATION_SCHEMA = {
type: "object",
additionalProperties: false,
required: ["entry_type", "authors", "title", "raw"],
properties: {
entry_type: {
enum: ["journal_article", "book", "book_chapter", "conference_paper",
"preprint", "thesis", "report", "webpage", "other"],
},
authors: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["family"],
properties: { family: { type: "string" }, given: { type: "string" } },
},
},
authors_truncated: { type: "boolean" },
title: { type: "string" },
container_title: { type: "string" }, // journal, book or proceedings
year: { type: "integer" },
volume: { type: "string" },
issue: { type: "string" },
pages: { type: "string" },
doi: { type: "string" },
raw: { type: "string" }, // the entry exactly as segmented
},
};
Keeping raw is not optional. It is what makes every later disagreement resolvable without going back to the PDF, and it is what you diff against when you change the prompt. Whether your provider enforces this schema or merely encourages it differs by provider and by mode — see structured output support and JSON mode versus structured outputs, because with additionalProperties: false the difference between the two is the difference between a guarantee and a suggestion.
保留 raw 字段不是可选项。它使得后续所有分歧都可以不回头查 PDF 就能解决,也是你在修改 prompt 时做 diff 的依据。你的 provider 是强制执行这个 schema 还是仅鼓励使用,因 provider 和模式而异——请参阅 structured output support 和 JSON mode versus structured outputs,因为配合 additionalProperties: false,两者的区别就是保证与建议的区别。
Extract the reference-list region with its line coordinates. The section heading is usually "References", "Bibliography" or "Works Cited"; take everything from it to the next heading or the end of the document.
提取参考文献区域的行坐标。章节标题通常是"References"、"Bibliography"或"Works Cited";从该标题到下一个标题或文档末尾的所有内容都要。
Detect the style. If more than 80% of lines at the block margin start with a numeric label, treat the list as numbered; otherwise segment by indent.
检测样式。如果块边距处超过 80% 的行以数字标签开头,则将列表视为数字编号式;否则按缩进分节。
Segment. Assert the count against the highest numeric label if there is one, and stop the run if they disagree by more than one.
分节。如果有数字标签,将分出的条目数与最高数字标签比对;如果差值超过一,则停止运行。
Walk the segmented entries in order and expand any repeated-author dash from the previous entry before parsing.
按顺序遍历分节后的条目,在解析前展开任何来自前一条目的重复作者破折号。
Parse each entry against the schema, in parallel, one call per entry. Set raw from the segmenter rather than letting the model echo it back.
根据 schema 解析每条条目,并行进行,每条目一次调用。用分节器设置的 raw 值,而不是让模型回显。
Post-validate: check the DOI syntactically, recompute any ISBN check digit, and reject a year outside a plausible range with a date field validation rule. Then resolve the DOIs you found and prefer the registry's metadata over your parse wherever the two disagree.
后验证:检查 DOI 句法、重新计算 ISBN 校验位,并用日期字段验证规则拒绝超出合理范围的年份。然后解析你找到的 DOI,并在 DOI 与解析结果不一致时,优先采用注册表的元数据而非你的解析。
Step 6 is where the accuracy actually comes from. Your parse of an entry is a guess at what the citing author typed; the registry record is what the cited work is. Treat the parse as a lookup key and the lookup as the answer, and the whole pipeline becomes far more robust than any amount of prompt tuning would make it.
第六步才是准确度真正来源。你对条目的解析是对引用作者输入内容的猜测;注册表记录才是被引用的作品。把解析当作查询键,把查询当作答案,整个流程就比任何数量的 prompt 调优都健壮得多。
A reference-list backlog is one call per entry, which is tens of thousands of small requests with a hard cost ceiling you want to know before you start rather than after. Running them through a gateway gives you per-request cost attribution and a spend cap on the job, and lets a rate-limit rejection from one provider fall through to another without the batch driver knowing which provider it is talking to.
参考文献列表积压是每条一次调用,即数万个小请求,有硬性成本上限——你希望在开始前就知道上限,而不是之后。通过网关运行它们可以获得每请求成本归属和作业支出上限,并允许在一个 provider 受到速率限制拒绝时自动切换到另一个,而批处理驱动程序无需知道自己在与哪个 provider 通信。
Extracting DOIs and ISBNs From a Reference List
从参考文献列表中提取 DOI 和 ISBN
Extracting Author Affiliations From a Scientific Paper
从科学论文中提取作者单位信息
Structured Output Support: Test It Yourself
结构化输出支持:亲自测试
For further actions, you may consider blocking this person and/or reporting abuse
如需进一步操作,你可以考虑屏蔽此人或举报滥用