综合小项目:用 Python 构建 AI Markdown 链接审查器
前面的系列已经覆盖了模型调用、上下文组织、结构化输出、错误处理、提示词、评估和安全边界。现在做一个小型综合项目:输入一篇 Markdown,先让模型找出疑似链接问题,再由 Python 检查链接是否真的出现在原文中、字段是否完整,并输出一份可复核的报告。这个项目的重点不是让模型“自动改好文章”,而是把模型放在建议环节,把确定性的校验和最终决策留在程序中。
先定义项目边界
我们只处理两类链接:Markdown 链接 [文字](地址) 和裸 URL。模型需要为每个疑似问题返回 kind、target、reason 和 confidence 四个字段。kind 只能是 broken_syntax、suspicious_url 或 unclear;target 必须是原文中出现的链接目标;confidence 只能是 low、medium、high。
这个工具不直接访问每个网站,也不把模型的判断当成事实。URL 是否能访问,还需要单独的网络检查;某个地址看起来可疑,也不代表它一定失效。明确边界后,程序才能给出诚实的结果:它报告“模型建议”和“程序验证”,而不是宣称已经证明链接有效。
准备环境和输入
使用独立虚拟环境安装 OpenAI 官方 Python SDK:
1 | python -m venv .venv |
新建 article.md,放入待审查的 Markdown。密钥只从环境变量读取,不要写进文章、脚本或提交记录。模型名称也作为配置读取,避免把某个具体模型永久写死在代码里。
编写最小审查器
创建 link_auditor.py:
1 | import json |
运行:
1 | python link_auditor.py article.md |
这里的 extract_targets 是本地的确定性步骤,先从文章中提取允许出现的目标。ask_model 使用 Responses API,把审查规则放在 instructions 中,并通过 response.output_text 读取 SDK 提供的文本结果。模型返回的 JSON 仍然只是字符串,所以紧接着用 json.loads 解析;解析失败时程序会报错,而不是悄悄把自然语言当成审查结果。
validate 是第二道边界。它检查枚举值、必填字段和目标是否来自原文。即使模型凭空生成一个 URL,或者把 confidence 写成其他词,也会被丢弃。注意,这种校验不能证明模型判断正确,只能阻止一部分格式和范围错误。
为什么不直接让模型修改文章
自动改写会同时改变链接文字、段落结构和原文意图,失败后也不容易知道改动来自模型还是程序。审查器只输出报告,用户可以根据 target 定位原文,再人工决定是否修改。若未来需要自动修复,也应该先把修复建议保存为补丁,展示差异并经过确认,而不是覆盖原文件。
还可以把模型输出和输入摘要记录下来,形成可重放的样本。测试时使用固定 Markdown,验证以下条件:模型返回的未知 URL 会被过滤;缺少 reason 的项目不会进入报告;合法项目会保留。网络请求本身不应成为单元测试的前提,可以把 validate 单独测试。
常见问题
为什么不直接访问 URL? 因为访问会带来超时、重定向、权限、robots 规则和网络副作用。可以另写一个有超时、限并发和明确白名单的检查器,把 HTTP 结果作为独立证据,不要把“模型觉得可疑”和“服务器返回错误”混为一谈。
模型返回的不是 JSON 怎么办? 当前示例让 json.loads 失败并暴露问题,这是比静默接受更安全的行为。生产版本可使用 SDK 支持的结构化输出配置,但仍应查阅当前服务商文档确认参数和响应形状,并保留本地字段校验。
为什么 target not in targets 不能防止所有幻觉? 它只能过滤不在输入中的目标。模型仍可能对真实 URL 给出错误理由,因此报告必须标注为建议,并保留原文和规则版本,方便人工复核。
长文章会不会超出上下文窗口? 会。应先按标题或字符预算分块,并在报告中保存块编号;不能简单截断,否则可能把链接定义和引用分开。分块策略属于输入设计,应该用固定长文本测试边界。
小结
这个综合项目把一次模型调用放进了一个小而清晰的流水线:Python 提取输入,模型提出候选问题,JSON 解析和字段校验过滤结果,程序输出报告而不覆盖原文。模型适合处理“哪些地方值得注意”这类语义任务,程序适合处理来源、类型和必填字段等确定性约束。沿着这个边界继续扩展时,可以加入结构化响应、有限重试、URL 网络探测和人工审批,但每一步都应保留证据,避免把不确定的建议包装成确定结论。