前面的系列已经覆盖了模型调用、上下文、提示词、结构化输出、错误处理、评估和安全边界。本篇在学习路线完成后做一个新的综合小项目:给模型一份“已确认事实”和一段待发布周报,让它标出哪些句子能被事实支持、哪些句子需要人工核对。程序只生成检查报告,不自动发布、不修改原文,重点是练习把模型放在“候选分析器”位置,而不是让它替人做最终决定。

先定义任务和边界

核对工具接收两类输入:事实清单和周报草稿。事实清单中的每条记录都有编号,例如 F1,内容可以是已经从项目系统确认过的日期、数量或状态;周报是普通文本。模型需要返回每个段落的结论:supported 表示能在事实中找到直接依据,needs_review 表示信息不足或表述超出了事实范围。

这个项目不会判断事实本身是否真实,也不会访问数据库、发送通知或直接改写周报。模型返回的编号、结论和理由都只是建议,程序先检查格式,再由人决定是否修改原文。尤其要注意:语言模型可能把常识或上下文猜测当成事实,所以“没有找到依据”比“模型觉得合理”更适合作为人工复核信号。

准备环境和输入文件

在独立虚拟环境中安装官方 Python SDK:

1
2
3
python -m venv .venv
source .venv/bin/activate
python -m pip install openai

密钥和模型名只通过环境变量提供,不要写进代码或提交到 Git:

1
2
export OPENAI_API_KEY="替换为你的真实密钥"
export MODEL_NAME="替换为你可用的模型名称"

新建 fact_check.json:

1
2
3
4
5
6
7
{
"facts": [
{"id": "F1", "text": "版本 2.4 已于 10 月 8 日发布。"},
{"id": "F2", "text": "本周收到 18 条用户反馈,其中 12 条已经关闭。"}
],
"draft": "版本 2.4 已于 10 月 8 日发布。本周收到 18 条反馈,全部已经关闭。"
}

输入文件允许被人编辑,因此代码仍要检查顶层字段、事实编号和文本类型,不能把 JSON 解析成功误认为内容可信。

编写最小核对程序

创建 fact_checker.py。下面的程序使用官方 Python SDK 的 Responses API;response.output_text 是 SDK 提供的文本结果便捷属性。为了让示例容易运行,输出协议先采用“要求 JSON + 本地解析校验”的方式。真实项目可以进一步使用服务端支持的结构化输出配置,但仍不能省略本地校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import json
import os
import sys
from pathlib import Path

from openai import OpenAI


ALLOWED_VERDICTS = {"supported", "needs_review"}


def load_case(path: Path) -> dict:
case = json.loads(path.read_text(encoding="utf-8"))
facts = case.get("facts")
draft = case.get("draft")
if not isinstance(facts, list) or not facts:
raise ValueError("facts 必须是非空列表")
if not isinstance(draft, str) or not draft.strip():
raise ValueError("draft 必须是非空字符串")
for fact in facts:
if not isinstance(fact, dict) or not isinstance(fact.get("id"), str):
raise ValueError("每条事实都必须有字符串 id")
if not isinstance(fact.get("text"), str):
raise ValueError("每条事实都必须有字符串 text")
return case


def check_result(result: object, fact_ids: set[str]) -> list[dict]:
if not isinstance(result, list) or not result:
raise ValueError("模型结果必须是非空数组")
checked = []
for item in result:
if not isinstance(item, dict):
raise ValueError("每项核对结果必须是对象")
if item.get("verdict") not in ALLOWED_VERDICTS:
raise ValueError("verdict 不在允许范围内")
refs = item.get("evidence_ids")
if not isinstance(refs, list) or not all(x in fact_ids for x in refs):
raise ValueError("evidence_ids 含有未知事实编号")
reason = item.get("reason")
if not isinstance(reason, str) or not reason.strip():
raise ValueError("reason 必须是非空字符串")
checked.append(item)
return checked


def main() -> int:
if len(sys.argv) != 2:
print("用法:python fact_checker.py fact_check.json", file=sys.stderr)
return 2
case = load_case(Path(sys.argv[1]))
client = OpenAI()
facts = json.dumps(case["facts"], ensure_ascii=False)
instructions = (
"你是事实核对助手。只能依据 FACTS 判断 DRAFT,禁止补充外部知识。"
"逐个核对草稿中的独立陈述,返回 JSON 数组,不要 Markdown。"
"每项必须包含 verdict、evidence_ids、reason;verdict 只能是 "
"supported 或 needs_review。证据不足时必须 needs_review。"
)
prompt = f"FACTS:\n{facts}\n\nDRAFT:\n{case['draft']}"
response = client.responses.create(
model=os.environ["MODEL_NAME"],
instructions=instructions,
input=prompt,
)
checked = check_result(
json.loads(response.output_text),
{fact["id"] for fact in case["facts"]},
)
print(json.dumps(checked, ensure_ascii=False, indent=2))
return 0


if __name__ == "__main__":
raise SystemExit(main())

load_case 负责输入边界,check_result 负责输出边界。后者不仅检查 JSON 是否可解析,还检查结论枚举、证据编号和理由是否存在。这样即使模型返回了格式正确但引用了不存在的 F99,程序也会失败,而不会生成看似可信的报告。运行命令是:

1
python fact_checker.py fact_check.json

这个示例不会伪造输出:实际结果取决于密钥、模型版本和当前请求。正常情况下,第二句因为“全部关闭”与 F2 的“12 条已经关闭”不一致,应进入 needs_review;但仍应以程序实际打印的报告为准。

常见问题

为什么不让模型直接重写周报? 重写会把核对、编辑和发布混在一起,出错时难以追责。先输出带证据编号的报告,人确认后再编辑,边界更清楚。

为什么要把事实编号传给模型? 自由复制长句不利于程序校验。稳定编号能让报告与原始记录建立可追踪关系,后续也能保存为审计日志。

模型说 supported 就一定正确吗? 不一定。模型只能比较文本,不能证明事实来源可靠;高风险数据仍应由业务系统或人工复核。生产版本还应限制输入长度、记录请求耗时和错误,并对重复请求设置超时与重试策略。

一段话里有多个结论怎么办? 可以要求模型逐条拆分,但不要只看返回条数。实际项目应给草稿段落或句子编号,让每条判断都能回到原文位置,并增加一组人工标注样例做回归测试。

小结

这个小项目的核心不是让模型“审定”周报,而是建立一条可检查的流水线:事实和草稿作为上下文输入,模型提出带证据的核对建议,Python 校验协议,人工完成最终决策。把模型输出视为不可信输入,并保留事实编号、原文和检查结果,才能让 AI 功能从一次性演示逐步走向可复核、可维护的应用。