OpenAI 引文格式

2026-11-04 17:09 更新

可靠的引用能建立信任,帮助读者验证回答。本指南说明如何准备可引用材料,并用OpenAI模型熟悉的模式指示引用格式。

<a id="overview"></a>

概览

引用系统包含多个环节:确定哪些材料可引用,清楚表示材料,告诉模型怎样引用,并在展示前验证结果。模型直接接触的五个核心要素是:可引用单元、材料表示、引用格式、提示指令、引用解析。

<a id="choose-citable-units"></a>

选择可引用单元

写提示词前,先确定模型允许引用什么。

单元 适用情况 局限 示例
文档 只需指出答案来自哪份文档 不够精确 以整份员工手册支持说法
块/分块 兼顾简单与精度 不能精确到行 引用包含条款的合同段落或检索块
行范围 需要显示确切支持文本 对模型更困难 引用L42-L47供用户核查原段落

单元应保持一致:同一来源跨次运行使用同一ID;易于检查:人能读懂并理解周边上下文;大小适当:足够表达意义,又足够精确。多数系统可默认采用块级引用,它通常比行级引用更易生成,也比文档级引用更有用。

<a id="represent-citable-material"></a>

表示可引用材料

材料没有清楚呈现,模型就无法正确引用。无论来自工具还是直接注入,都应有稳定来源ID(如file1、block1)、清楚排版的文本,以及可选URL、时间戳、标题等元数据。

<a id="example-citable-material"></a>

可引用材料示例

可引用材料示例(来源示例1,text)

引用标记:{CITATION_START}cite{CITATION_DELIMITER}file0{CITATION_STOP}
标题:员工手册
URL:https://company.example/handbook
更新:2026-03-01

[L1] 员工每周最多可远程工作三天。
[L2] 增加远程工作天数需经理批准。
[L3] 获批的特殊便利安排可能适用例外。

来源ID与定位符不同:来源ID是稳定标识,例如block1;定位符指向界面上精确高亮的位置,例如lines L8-L13或第21段。通常让模型输出来源ID,由系统解析或渲染定位符。过早混合两者容易增加格式错误。

<a id="define-citation-format"></a>

定义引用格式

明确规定生成格式,要求显式、一致且易于可靠复现。以下标记接近模型训练时使用的格式,因此推荐采用;如需更换标记值,也应尽量维持整体格式。

部分 作用 推荐值
CITATION_START 开始引用标记 \ue200
引用类别 区分引用类型;所有支持来源用cite cite
CITATION_DELIMITER 分隔字段 \ue202
来源ID turn#是工具调用轮号,item#是文件、块或URL turn0file1、turn0block1、turn0url1
定位符(可选) 限定确切范围 L8-L13
CITATION_STOP 结束标记 \ue201

turnN按每次工具调用递增,不按单个结果递增。同次调用的来源以file0、file1等后缀区分。一次回答前恰好只调用一次工具时,所有引用才均为turn0...;多次调用则可能有turn0fileX、turn1fileX等。

<a id="template"></a>

模板

模板(来源示例2,text)

{CITATION_START}<citation_family>{CITATION_DELIMITER}<source_id>{CITATION_DELIMITER}<locator>{CITATION_STOP}

<a id="example"></a>

示例

示例(来源示例3,text)

{CITATION_START}cite{CITATION_DELIMITER}turn0file1{CITATION_DELIMITER}L8-L13{CITATION_STOP}

不使用定位符时,省略该字段:

示例(来源示例4,text)

{CITATION_START}cite{CITATION_DELIMITER}turn0file1{CITATION_STOP}

<a id="write-effective-citation-instructions"></a>

编写有效引用指令

熟悉的模式有助于准确。自定义或陌生格式会增加认知负担,特别是在低推理力度、难以修复格式错误时,或复杂任务已把大部分推理预算用于任务本身时。

下面格式接近模型熟悉的模式,可以直接使用或适配。如果自行设计提示词,应定义确切语法、引用位置、何时引用或不引用、多来源如何处理、禁止哪些格式,以及缺少支持时如何处理。

<a id="recommended-prompt-instructions"></a>

推荐提示指令

推荐提示指令(来源示例5,md)

## 引用
结果由"tool_1"返回。其每条消息称为“来源”,引用ID为首次出现的【turn\d+\w+\d+】,如【turn2file1】,其中turn2file1就是来源引用ID。
引用指向tool_1来源,可引用一个或多个。
单来源必须写为{CITATION_START}cite{CITATION_DELIMITER}turn\d+\w+\d+{CITATION_STOP},如{CITATION_START}cite{CITATION_DELIMITER}turn2file5{CITATION_STOP}。
多来源必须写为{CITATION_START}cite{CITATION_DELIMITER}turn\d+\w+\d+{CITATION_DELIMITER}turn\d+\w+\d+{CITATION_DELIMITER}...{CITATION_STOP},如{CITATION_START}cite{CITATION_DELIMITER}turn2file5{CITATION_DELIMITER}turn2file1{CITATION_DELIMITER}...{CITATION_STOP}。
不要把引用放进Markdown加粗、斜体或代码围栏内,否则无法正确展示;放在这些格式外。围栏外的引用也不得与结束围栏处于同一行。
绝不直接在正文写turn\d+\w+\d+,必须包在{CITATION_START}...{CITATION_STOP}中。
- 默认段末引用,段落长时可行内引用,用户指定位置时按其要求。
- 引用放标点之后。
- 不把全部引用集中在回复末尾。
- 不让某行或某段只有引用。

如还需输出L1-L22这样的行定位符,进一步规定:

推荐提示指令(来源示例6,text)

仅当项目有对应引用标记时,你必须用`\ue200cite\ue202turn0file0\ue202L8-L13\ue201`格式引用本工具中使用的任何结果。

  • 没有对应引用标记的项目不应引用,它们并非可引用材料。
  • 引用必须包含行范围。

<a id="optional-instructions-for-higher-quality-grounding"></a>

可选:提高依据质量

需要更高质量的依据时,可结合用例加入以下规则。

可选:提高依据质量(来源示例7,xml)

<extra_considerations_for_citations>
- 相关性:只纳入支持被引文字的搜索结果与引用;无关来源会永久损害信任。
- 多样性:答案必须基于不同域名来源,并相应引用。
- 可信度:依赖高质量域名,低声誉域名的信息除非是唯一来源,否则忽略。
- 准确表达:引用必须准确反映来源,不能选择性解释。

记住,域名或来源质量依上下文而定。
- 有多种观点时,引用覆盖各方意见,保证平衡全面。
- 可靠来源有分歧时,每个主要观点至少引用一个高质量来源。
- 超过一半的引用来自该主题广泛认可的权威机构或媒体。
- 有争议主题至少为每个主要观点引用一个可靠来源。
- 不要因为相关来源质量较低,就忽略其内容。
</extra_considerations_for_citations>

<a id="parse-citations"></a>

解析引用

模型生成后,从回答文字提取标记,以解析来源ID、渲染链接,或展示前移除原始标记。下面的辅助实现可直接复制进应用,解析单来源、多来源及可选行范围,并保持原文本的字符偏移。示例只支持行定位符;采用其他定位格式时应修改。

<a id="post-processor-examples"></a>

后处理示例

后处理示例(来源示例8,javascript)

// [中文] 用私用区分隔符识别引文标记,再拆分引文家族、来源ID及可选定位信息。
// [中文] 提取范围用于替换或渲染引用;不要把展示用占位符当作实际分隔符。保持当前语言的字符串偏移单位,不混用字符数与字节数。
const CITATION_START = "\uE200";
const CITATION_DELIMITER = "\uE202";
const CITATION_STOP = "\uE201";

const SOURCE_ID_RE = /^[A-Za-z0-9_-]+$/;
const LINE_LOCATOR_RE = /^L\d+(?:-L\d+)?$/;

// [中文] 提取下列形式的引文;下面保留分隔符及定位器示例。
// Extract citations such as:
//
//   {CITATION_START}cite{CITATION_DELIMITER}turn0file0{CITATION_STOP}
//   {CITATION_START}cite{CITATION_DELIMITER}turn0file0{CITATION_DELIMITER}L8-L13{CITATION_STOP}
//   {CITATION_START}cite{CITATION_DELIMITER}turn0search0{CITATION_DELIMITER}turn1news2{CITATION_STOP}
function extractCitations(text, { families = ["cite"] } = {}) {
  if (families.length === 0) {
    return [];
  }

  const familyPattern = families
    .map((family) => family.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
    .join("|");

  const tokenRe = new RegExp(
    `${CITATION_START}(?<family>${familyPattern})${CITATION_DELIMITER}(?<body>[\\s\\S]*?)${CITATION_STOP}`,
    "g"
  );

  const citations = [];

  for (const match of text.matchAll(tokenRe)) {
    const body = match.groups?.body ?? "";
    const parts = body
      .split(CITATION_DELIMITER)
      .map((part) => part.trim())
      .filter(Boolean);

    if (parts.length === 0) {
      continue;
    }

    let locator = null;
    const lastPart = parts[parts.length - 1];
    if (LINE_LOCATOR_RE.test(lastPart)) {
      locator = parts.pop() ?? null;
    }

    if (parts.length === 0 || parts.some((part) => !SOURCE_ID_RE.test(part))) {
      continue;
    }

    citations.push({
      raw: match[0],
      family: match.groups?.family ?? "",
      source_ids: parts,
      locator,
      start: match.index ?? 0,
      end: (match.index ?? 0) + match[0].length,
    });
  }

  return citations;
}

function stripCitations(text, citations) {
  let cleanText = text;
  const sortedCitations = Array.from(citations).sort(
    (left, right) => right.start - left.start
  );

  for (const citation of sortedCitations) {
    cleanText =
      cleanText.slice(0, citation.start) + cleanText.slice(citation.end);
  }

  return cleanText;
}

后处理示例(来源示例9,python)

## [中文] 用私用区分隔符识别引文标记,再拆分引文家族、来源ID及可选定位信息。
## [中文] 提取范围用于替换或渲染引用;不要把展示用占位符当作实际分隔符。保持当前语言的字符串偏移单位,不混用字符数与字节数。
import re
from collections.abc import Iterable
from typing import TypedDict

CITATION_START = "\ue200"
CITATION_DELIMITER = "\ue202"
CITATION_STOP = "\ue201"

SOURCE_ID_RE = re.compile(r"^[A-Za-z0-9_-]+$")
LINE_LOCATOR_RE = re.compile(r"^L\d+(?:-L\d+)?$")


class Citation(TypedDict):
    raw: str
    family: str
    source_ids: list[str]
    locator: str | None
    start: int
    end: int


def extract_citations(
    text: str,
    *,
    families: tuple[str, ...] = ("cite",),
) -> list[Citation]:
    """
    Extract citations such as:

      {CITATION_START}cite{CITATION_DELIMITER}turn0file0{CITATION_STOP}
      {CITATION_START}cite{CITATION_DELIMITER}turn0file0{CITATION_DELIMITER}L8-L13{CITATION_STOP}
      {CITATION_START}cite{CITATION_DELIMITER}turn0search0{CITATION_DELIMITER}turn1news2{CITATION_STOP}
    """
    if not families:
        return []

    family_pattern = "|".join(re.escape(family) for family in families)
    token_re = re.compile(
        rf"{re.escape(CITATION_START)}"
        rf"(?P<family>{family_pattern})"
        rf"{re.escape(CITATION_DELIMITER)}"
        rf"(?P<body>.*?)"
        rf"{re.escape(CITATION_STOP)}",
        re.DOTALL,
    )

    citations: list[Citation] = []

    for match in token_re.finditer(text):
        parts = [part.strip() for part in match.group("body").split(CITATION_DELIMITER)]
        parts = [part for part in parts if part]

        if not parts:
            continue

        locator = None
        if LINE_LOCATOR_RE.fullmatch(parts[-1]):
            locator = parts.pop()

        if not parts or any(not SOURCE_ID_RE.fullmatch(part) for part in parts):
            continue

        citations.append(
            {
                "raw": match.group(0),
                "family": match.group("family"),
                "source_ids": parts,
                "locator": locator,
                "start": match.start(),
                "end": match.end(),
            }
        )

    return citations


def strip_citations(text: str, citations: Iterable[Citation]) -> str:
    """
    Remove raw citation markers from text using offsets returned by
    extract_citations().
    """
    clean_text = text

    for citation in sorted(citations, key=lambda item: item["start"], reverse=True):
        clean_text = clean_text[: citation["start"]] + clean_text[citation["end"] :]

    return clean_text

后处理示例(来源示例10,go)

// [中文] 用私用区分隔符识别引文标记,再拆分引文家族、来源ID及可选定位信息。
// [中文] 提取范围用于替换或渲染引用;不要把展示用占位符当作实际分隔符。Go的Start/End按UTF-8字节偏移切片。
// [中文] Start和End是UTF-8字节偏移,可直接用于Go字符串切片。
// Start and End are UTF-8 byte offsets, suitable for Go string slicing.
type Citation struct {
    Raw, Family string
    SourceIDs   []string
    Locator     string
    Start, End  int
}

var sourceID = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
var lineLocator = regexp.MustCompile(`^L\d+(?:-L\d+)?$`)

func extractCitations(text string, families []string) []Citation {
    citations := []Citation{}
    if len(families) == 0 {
        return citations
    }
    escaped := make([]string, len(families))
    for i, family := range families {
        escaped[i] = regexp.QuoteMeta(family)
    }
    token := regexp.MustCompile("(?s)\uE200(" + strings.Join(escaped, "|") + ")\uE202(.*?)\uE201")
    for _, match := range token.FindAllStringSubmatchIndex(text, -1) {
        parts := []string{}
        for _, part := range strings.Split(text[match[4]:match[5]], "\uE202") {
            if part = strings.TrimSpace(part); part != "" {
                parts = append(parts, part)
            }
        }
        locator := ""
        if len(parts) > 0 && lineLocator.MatchString(parts[len(parts)-1]) {
            locator = parts[len(parts)-1]
            parts = parts[:len(parts)-1]
        }
        valid := len(parts) > 0
        for _, part := range parts {
            if !sourceID.MatchString(part) {
                valid = false
                break
            }
        }
        if valid {
            citations = append(citations, Citation{
                text[match[0]:match[1]],
                text[match[2]:match[3]],
                parts,
                locator,
                match[0],
                match[1],
            })
        }
    }
    return citations
}
func stripCitations(text string, citations []Citation) string {
    sorted := append([]Citation(nil), citations...)
    sort.Slice(sorted, func(i, j int) bool {
        return sorted[i].Start > sorted[j].Start
    })
    for _, citation := range sorted {
        text = text[:citation.Start] + text[citation.End:]
    }
    return text
}

后处理示例(来源示例11,java)

// [中文] 用私用区分隔符识别引文标记,再拆分引文家族、来源ID及可选定位信息。
// [中文] 提取范围用于替换或渲染引用;不要把展示用占位符当作实际分隔符。Java的偏移以UTF-16代码单元计,与substring一致。
import java.util.*;
import java.util.regex.Pattern;

// [中文] Java字符串偏移按UTF-16代码单元计算,与substring一致。
// Java string offsets are UTF-16 code units, matching substring().
record Citation(
    String raw, String family, List<String> sourceIds, String locator, int start, int end) {}

static final Pattern SOURCE_ID = Pattern.compile("^[A-Za-z0-9_-]+$");
static final Pattern LINE_LOCATOR = Pattern.compile("^L\\d+(?:-L\\d+)?$");

static List<Citation> extractCitations(String text, List<String> families) {
  var citations = new ArrayList<Citation>();
  if (families.isEmpty()) return citations;
  var familyPattern =
      families.stream().map(Pattern::quote).collect(java.util.stream.Collectors.joining("|"));
  var matcher =
      Pattern.compile("\uE200(" + familyPattern + ")\uE202(.*?)\uE201", Pattern.DOTALL)
          .matcher(text);
  while (matcher.find()) {
    var parts =
        new ArrayList<>(
            Arrays.stream(matcher.group(2).split("\uE202"))
                .map(String::strip)
                .filter(s -> !s.isEmpty())
                .toList());
    String locator = null;
    if (!parts.isEmpty() && LINE_LOCATOR.matcher(parts.get(parts.size() - 1)).matches())
      locator = parts.remove(parts.size() - 1);
    if (parts.isEmpty() || parts.stream().anyMatch(s -> !SOURCE_ID.matcher(s).matches()))
      continue;
    citations.add(
        new Citation(
            matcher.group(),
            matcher.group(1),
            List.copyOf(parts),
            locator,
            matcher.start(),
            matcher.end()));
  }
  return citations;
}

static String stripCitations(String text, List<Citation> citations) {
  var sorted = new ArrayList<>(citations);
  sorted.sort(Comparator.comparingInt(Citation::start).reversed());
  for (var citation : sorted)
    text = text.substring(0, citation.start()) + text.substring(citation.end());
  return text;
}

后处理示例(来源示例12,ruby)

## [中文] 用私用区分隔符识别引文标记,再拆分引文家族、来源ID及可选定位信息。
## [中文] 提取范围用于替换或渲染引用;不要把展示用占位符当作实际分隔符。保持当前语言的字符串偏移单位,不混用字符数与字节数。
CITATION_START = "\u{E200}"
CITATION_DELIMITER = "\u{E202}"
CITATION_STOP = "\u{E201}"

SOURCE_ID_RE = /\A[A-Za-z0-9_-]+\z/
LINE_LOCATOR_RE = /\AL\d+(?:-L\d+)?\z/

def extract_citations(text, families: ["cite"])
  return [] if families.empty?

  family_pattern = families.map { |family| Regexp.escape(family) }.join("|")
  token_re = Regexp.new(
    "#{Regexp.escape(CITATION_START)}" \
      "(?<family>#{family_pattern})" \
      "#{Regexp.escape(CITATION_DELIMITER)}" \
      "(?<body>.*?)" \
      "#{Regexp.escape(CITATION_STOP)}",
    Regexp::MULTILINE
  )

  text.enum_for(:scan, token_re).map do
    match = Regexp.last_match
    next unless match

    body = match[:body]
    next unless body

    parts = body.split(CITATION_DELIMITER).map(&:strip).reject(&:empty?)
    locator = parts.pop if parts.last&.match?(LINE_LOCATOR_RE)
    next if parts.empty? || parts.any? { |part| !part.match?(SOURCE_ID_RE) }

    {
      raw: match[0],
      family: match[:family],
      source_ids: parts,
      locator: locator,
      start: match.begin(0),
      end: match.end(0)
    }
  end.compact
end

def strip_citations(text, citations)
  citations.sort_by { |citation| -citation.fetch(:start) }.each_with_object(text.dup) do |citation, clean|
    clean[citation.fetch(:start)...citation.fetch(:end)] = ""
  end
end

来源ID形状不同,则调整SOURCE_ID_RE。

<a id="examples"></a>

示例

下面展示工具返回可引用材料与ID,以及直接将可引用块注入提示词这两种模式。

<a id="format-citations-for-retrieved-tool-context"></a>

为工具检索上下文设置引用

模型通过工具取得上下文并在回答中引用时,使用此模式。

<a id="define-citable-units"></a>

定义可引用单元

按用例所需精度选择。具体工具随应用而异,重要的是结果像下例一样清楚、结构稳定。

<a id="line-level-example"></a>

行级示例

行级示例(来源示例13,text)

引用标记:{CITATION_START}cite{CITATION_DELIMITER}turn0file0{CITATION_STOP}
[L1] 服务协议规定,除非客户专属补充条款另行取代,无理由终止需提前三十(30)天书面通知。
[L2] 实际上,截止前未收到不续约书面通知时,合同按一年一期自动续展。
[L3] 附录B进一步规定,价格例外必须由财务部门及账户负责人双方书面批准。

引用标记:{CITATION_START}cite{CITATION_DELIMITER}turn0file1{CITATION_STOP}
...

turn0file0是稳定来源ID,行号是定位符。

<a id="block-level-example"></a>

块级示例

块级示例(来源示例14,text)

引用标记:{CITATION_START}cite{CITATION_DELIMITER}turn0file0{CITATION_STOP}
[Block1]
服务协议规定,除非客户专属补充条款另行取代,无理由终止需提前三十(30)天书面通知。
实际上,截止前未收到不续约书面通知时,合同按一年一期自动续展。
附录B进一步规定,价格例外必须由财务部门及账户负责人双方书面批准。

引用标记:{CITATION_START}cite{CITATION_DELIMITER}turn0file1{CITATION_STOP}
[Block2]
...

块级引用仍建议每个检索块使用独立稳定来源ID,维持同样的两字段引用形状,例如{CITATION_START}cite{CITATION_DELIMITER}turn0file0{CITATION_STOP},不要另造完全不同的引用类别。

<a id="write-prompt-instructions"></a>

编写提示指令

编写提示指令(来源示例15,md)

## 引用
结果由tool_1返回,每条消息为一个来源,其引用ID是首次出现的`turn\\d+file\\d+`,如turn0file0或turn2file1。本例turn0file0是来源引用ID。
引用指向tool_1来源,可引用一个或多个。
单来源格式:
{CITATION_START}cite{CITATION_DELIMITER}turn\d+file\d+{CITATION_STOP}
支持行级引用时,确切行范围格式:
{CITATION_START}cite{CITATION_DELIMITER}turn\d+file\d+{CITATION_DELIMITER}L\d+-L\d+{CITATION_STOP}
多来源应分别输出多个标记,每个支持来源一个。
不可在正文直接写turn0file0等引用ID,必须包在{CITATION_START}...{CITATION_STOP}中。
- 引用置于所支持句子末尾;长句含多项受支持分句时可行内放置。
- 放在标点之后。
- 只引直接支持文字的已检索来源。
- 绝不编造工具未返回的来源ID、行范围或块定位符。
- 多个来源实质支持同一命题时,全部引用。
- 来源冲突时,引用冲突双方并准确描述分歧。

输出示例:

编写提示指令(来源示例16,text)

值班交接流程记录在每周支持同步会议笔记中。\ue200cite\ue202turn0file0\ue202L8-L13\ue201

<a id="format-citations-for-injected-context"></a>

为注入上下文设置引用

提前检索或准备上下文,再直接注入提示词时使用此模式。

<a id="define-citable-units-1"></a>

定义可引用单元

常见做法是用具有稳定引用ID的明确标签包裹来源片段。

定义可引用单元(来源示例17,xml)

<BLOCK id="block1">
服务协议规定,除非客户专属补充条款另行取代,无理由终止需提前三十(30)天书面通知。
实际上,截止前未收到不续约书面通知时,合同按一年一期自动续展。
附录B进一步规定,价格例外必须由财务部门及账户负责人双方书面批准。

</BLOCK>

<BLOCK id="block2">
课程大纲(Syllabus)
</BLOCK>
...

这样能明确可引用单元,便于模型引用。此处及下一段提示中的BLOCK标签按官方HTML代码补回;Markdown导出缺少这些标签,校勘证据保存在来源快照中。

<a id="write-prompt-instructions-1"></a>

编写提示指令

编写提示指令(来源示例18,md)

## 引用
支持上下文以可引用单元直接放在提示中。每个单元由首次出现标签的id属性值标识,例如`<BLOCK id="block5"> ... </BLOCK>`。本例block5就是来源引用ID。
本模式不调用工具,因此没有需递增的工具轮号,标记无需turn#前缀。可保持turn0block5风格以统一系统,也可用这里的block5。关键是标记必须精确、一致地匹配注入上下文ID。
引用指向这些给定单元,可引用一个或多个。
单来源格式:
{CITATION_START}cite{CITATION_DELIMITER}<block_id>{CITATION_STOP}
例如:
{CITATION_START}cite{CITATION_DELIMITER}block5{CITATION_STOP}
多来源分别输出多个标记,每个支持块一个。
不在正文直接写块ID,必须包在{CITATION_START}...{CITATION_STOP}中。
- 引用放被支持句末;长句有多项受支持分句时可行内引用。
- 放标点之后。
- 只引用给定上下文出现的块。
- 不编造块ID。
- 不引用外部知识或外部权威。
- 多块实质支持命题时全部引用。
- 给定块冲突时引用冲突块,准确描述冲突。

输出示例:

编写提示指令(来源示例19,text)

法院裁定,地区法院对申请人缺乏对人管辖权。\ue200cite\ue202block5\ue201

OpenAI托管的网络搜索等工具自动提供行内引用。如使用托管工具,可参阅工具概览、网络搜索和文件搜索。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号