系列基础主题已经覆盖了消息、参数、结构化输出、错误处理、提示词、评估和工程边界。本文做一个新的综合小项目:输入一段产品文档和一份术语表,让模型找出可能不一致的写法,再由 Python 校验结果并输出审核报告。它不会直接修改原文,重点是把模型的“候选意见”变成可检查、可人工复核的结果。

先定义问题和边界

术语一致性检查适合交给模型做初筛,但不适合让模型直接覆盖源文件。比如术语表规定“工作区”是推荐写法,文档却出现“工作空间”;程序可以报告这处疑似不一致,最终是否修改仍由作者决定。

工具的输入是两个文本文件:glossary.json 保存推荐词和允许的别名,document.md 保存待检查内容。输出是一个 JSON 报告,每条问题包含原词、推荐词、所在片段和理由。模型只负责发现候选项,Python 负责读取文件、检查字段、去重和保存报告。这种边界能避免一次不可靠的生成直接造成批量改稿。

准备术语表和环境

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

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

创建 glossary.json:

1
2
3
4
5
[
{"preferred": "工作区", "aliases": ["工作空间"]},
{"preferred": "访问令牌", "aliases": ["访问 token", "access token"]},
{"preferred": "命令行", "aliases": ["CLI"]}
]

只从环境变量读取密钥和模型名:

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

不要把真实密钥写入脚本、配置文件或 Git。模型名称、服务地址和返回格式支持情况可能因服务而异;运行前应查看所用服务的官方文档。本例只依赖 Python SDK 的 responses.create、响应对象的 output_text 和普通 JSON 解析,仍需使用实际账户做一次集成验证。

编写最小检查器

新建 check_terms.py:

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
76
77
78
79
80
81
82
83
84
import json
import os
import sys
from pathlib import Path
from typing import Any

from openai import OpenAI


def load_glossary(path: Path) -> list[dict[str, Any]]:
data = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(data, list) or not data:
raise ValueError("术语表必须是非空数组")
for item in data:
if (not isinstance(item, dict)
or not isinstance(item.get("preferred"), str)
or not isinstance(item.get("aliases"), list)
or not all(isinstance(x, str) for x in item["aliases"])):
raise ValueError("每条术语必须包含字符串 preferred 和 aliases")
return data


def check(document: str, glossary: list[dict[str, Any]]) -> list[dict[str, str]]:
if not document.strip():
raise ValueError("文档不能为空")
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
model = os.environ["MODEL_NAME"]
prompt = (
"根据术语表检查下面的文档,只返回 JSON 数组,不要 Markdown 代码围栏。"
"每项必须包含 alias、preferred、evidence、reason 四个字符串字段。"
"只报告文档中确实出现的别名;没有问题时返回空数组。\n\n"
f"术语表:{json.dumps(glossary, ensure_ascii=False)}\n"
f"文档:{document}"
)
response = client.responses.create(
model=model,
instructions="你是严谨的中文技术文档审校助手。不要猜测文档中不存在的词。",
input=prompt,
)
raw = response.output_text.strip()
findings = json.loads(raw)
if not isinstance(findings, list):
raise ValueError("模型返回的顶层结构不是数组")

allowed = {
alias: item["preferred"]
for item in glossary
for alias in item["aliases"]
}
checked: list[dict[str, str]] = []
seen: set[tuple[str, str]] = set()
for item in findings:
if not isinstance(item, dict):
continue
fields = ("alias", "preferred", "evidence", "reason")
if not all(isinstance(item.get(field), str) for field in fields):
continue
alias = item["alias"]
if alias not in allowed or item["preferred"] != allowed[alias]:
continue
key = (alias, item["evidence"])
if key not in seen:
checked.append({field: item[field] for field in fields})
seen.add(key)
return checked


def main() -> None:
if len(sys.argv) != 4:
raise SystemExit("用法:python check_terms.py glossary.json document.md report.json")
glossary_path, document_path, report_path = map(Path, sys.argv[1:])
findings = check(
document_path.read_text(encoding="utf-8"),
load_glossary(glossary_path),
)
report_path.write_text(
json.dumps({"findings": findings}, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
print(f"已保存 {len(findings)} 条候选问题:{report_path}")


if __name__ == "__main__":
main()

运行命令:

1
python check_terms.py glossary.json document.md report.json

代码中最重要的不是请求本身,而是请求后的边界检查。json.loads 只能证明文本是合法 JSON;allowed 又把别名和术语表重新对照,拒绝模型自造的推荐词;seen 避免相同证据重复出现。最终写入的报告是新文件,不会覆盖 document.md。

如何验证而不是伪造结果

没有 API 密钥或不希望产生网络请求时,可以先单独测试确定性部分。把 check 拆成接收“模型返回文本”的函数,传入固定字符串,验证非法顶层结构、未知别名和重复证据都会被过滤。真实 API 测试只需覆盖少量样例,并检查报告能被 JSON 重新读取、每项字段齐全、推荐词来自术语表。

实际运行的输出内容会受模型、文档和服务状态影响,不能预先写死。可以准备一份包含“工作空间”和“CLI”的小文档,运行后人工确认报告是否指出它们;这验证的是流程,不是保证模型永远不会漏报。若需要更强的稳定性,应使用服务支持的结构化输出约束,并继续保留程序校验。

常见问题

为什么不让模型直接改文档? 术语替换可能改变代码片段、专有名词或引用。先产出候选报告,再人工确认,副作用更小,也保留了审阅依据。

为什么模型返回了错误字段还要继续? 示例选择跳过不符合协议的条目,避免一条坏记录导致整份报告不可用。生产系统还应记录被丢弃条目的数量,并在数量异常时让任务失败,而不是静默吞掉质量问题。

别名本身可能是合法用法怎么办? 在术语表中增加上下文、文档类型或例外规则,并让程序把“疑似问题”交给人工复核。术语一致性不是简单的全局字符串替换。

文档很长会怎样? 当前示例把全文放进一次请求,受上下文窗口限制。长文档应按段落分块,给每块保留章节标题和定位信息,再合并去重;这属于下一步工程化,而不是把超长文本硬塞进一次调用。

小结

这个项目把模型调用、提示词约束、JSON 解析、结果校验和人工审批串成了一条小而完整的链路。模型适合提出术语问题,Python 适合验证协议、限制结果范围并保存证据;两者各自承担擅长的部分。对于会影响源文档的 AI 功能,先输出可审阅报告、再执行修改,通常比让模型直接写回文件更容易测试和回滚。