假设你在做一个活动通知页面。模型负责从通知中提取报名截止日期,程序把日期、出处和原文填进页面。
模型生成正确答案以后,还有一段工作需要程序完成:从响应中取出回答,检查需要的字段,决定这份结果能否使用。如果生成中断、缺少字段,或者资料不足,页面也需要知道该显示什么。
这篇文章把这段接收过程接起来:先约定数据,再读取真实响应,最后让每份结果进入"已接受、拒收、待核对"中的一个状态。 我们用四次真实模型调用观察正常结果,再用明确标为人工构造的输入检查失败分支。
1. 先决定页面需要什么,再约定模型返回什么
教学资料是两份来自同一主办方、针对同一次读书会的通知。年份均为 2026 年:
| 程序分配的编号 | 通知与正文 |
|---|---|
| D1 | 《读书会报名通知》,9 月 10 日发布:本次读书会报名将于2026年9月20日截止。 |
| D2 | 《读书会报名调整通知》,9 月 16 日发布:本次读书会报名截止日期提前到2026年9月18日,原通知中的截止日期作废。 |
页面有"截止日期"和"依据"两个位置,因此让模型返回以下三个字段。字段就是数据中一个有名称的位置:
| 字段 | 接收约定 | 用途 |
|---|---|---|
deadline_date |
日期字符串,格式为 YYYY-MM-DD |
填写页面日期,供程序按日期比较 |
source_id |
从本次资料编号中选择 | 找到程序已经保存的通知 |
quote |
该通知中支持判断的连续原文 | 让核对者检查日期与依据 |
三个字段必须齐全,不接受额外字段。无法确定唯一有效日期时,三个值都填 null,表示没有可用的提取结果。它不能被解释成"活动没有截止日期"。这个简化约定适合当前的单日期页面;若需要分别展示冲突通知或部分已知信息,就要增加相应的数据表达。
我们使用 JSON 保存这些字段。JSON 用名称和值组织数据,程序可以按名称取值,例如读取 deadline_date,不必先从一段回答中识别哪个日期应该填进页面。资料编号由程序分配,模型只从给定编号中选择;通知名称等已有信息可以按编号取回。
这里先定义本例的使用条件:只有字段检查通过,并确认日期有有效通知支持,才把它填入页面的正式结果。 尚未核对的候选可以展示在核对区,但不标为已确认。本例选择人工对照两份通知;实际产品是否需要逐条人工核对,要根据用途和风险决定。
2. 看真实输出怎样对应这份接收约定
2026 年 9 月 19 日,我们通过已配置的 Responses API 兼容接口,分别要求自然语言回答和三字段 JSON 回答,各做两次独立调用。请求模型名和四次响应的 model 均为 gpt-6;这里按接口返回的标识记录,响应未提供可独立核实的底层模型快照。
四次都设置 reasoning.effort="high"、max_output_tokens=4096、stream=false、store=false,未传 temperature,也未启用 JSON 模式或 JSON Schema 生成约束。各次重新提供相同通知,不带历史回答、标准答案或校验器代码,没有重试或筛选结果。
两组共有的任务指令是:
根据提供的通知,提取这次读书会唯一有效的报名截止日期,并提供依据。采用明确修改报名日期的通知,不仅凭发布时间选择。只使用本次资料;无法确定唯一有效日期时,不猜测。不要声称执行了报名或联系操作。
自然语言组追加:
请用自然语言简短回答,说明采用哪份通知,并引用支持判断的原文。无法确定时,说明具体缺口。
字段组追加:
只输出 JSON 对象,字段为 deadline_date、source_id、quote;日期使用 YYYY-MM-DD,出处使用给定编号,引文复制支持判断的连续原文。无法确定唯一有效日期时,三个字段都填 null。不要添加解释文字、Markdown 代码围栏或其他字段。
稳定任务与输出要求放在 instructions,两份通知和问题放在 input。问题相同:"这次读书会当前有效的报名截止日期是什么?"
自然语言第 1 次的完整回答是:
当前有效的报名截止日期是 2026年9月18日。
采用 D2《读书会报名调整通知》,依据是其明确写道:"本次读书会报名截止日期提前到2026年9月18日,原通知中的截止日期作废。"因此,D1 中的9月20日已失效;判断依据是 D2 明确修改并废止原截止日期,而非仅因其发布较晚。
两次字段回答的文字完全相同,原样为:
json
{"deadline_date":"2026-09-18","source_id":"D2","quote":"本次读书会报名截止日期提前到2026年9月18日,原通知中的截止日期作废。"}
四次都正常完成,并正确采用 D2 的 9 月 18 日。把保存的完整响应交给下文的接收程序,再对照通知作出核对决定,结果如下:
| 实际调用顺序 | 模型回答的日期与依据 | 三字段接收程序怎样处理 |
|---|---|---|
| 自然语言第 1 次 | 9 月 18 日,D2,引文准确 | 无法解析为 JSON,拒收为字段结果 |
| 字段第 1 次 | 2026-09-18,D2,引文准确 |
自动检查通过,进入待核对;确认依据后变为已接受 |
| 自然语言第 2 次 | 9 月 18 日,D2,引文准确 | 无法解析为 JSON,拒收为字段结果 |
| 字段第 2 次 | 2026-09-18,D2,引文准确 |
自动检查通过,进入待核对;确认依据后变为已接受 |
自然语言组完成了它收到的要求,可以直接用于文字展示;本例的字段接收程序需要另一种格式,因此不能把两次拒收计为模型答错。字段组则让程序可以直接按名称取值。这组实测体现的是输出方式与接收约定是否一致,没有显示日期判断正确率的提升,也不足以估计长期错误率。
如果所用接口支持生成格式约束,还可以在生成时约束输出。DeepSeek 的 JSON Output 官方示例用 JSON 输出参数配合提取指令,再通过 json.loads 读取结果。字段类型、必填项等约定也可以写成 JSON Schema,交给校验器或支持它的模型接口使用。生成约束能保证哪些条件,需要按具体接口核对;本次实测只使用文字要求,没有测试这些额外能力的收益。
3. 按顺序检查,让结果进入明确的处理状态
收到接口响应以后,程序依次解决三个问题。
第一,是否拿到了可用的完整回答? 响应中既有生成状态,也有模型生成的内容。应先确认生成正常完成,排除拒绝、空回答和未完成内容,再读取答案。流式返回需要等到最终状态;本例使用非流式响应。
第二,回答是否符合数据约定? 能解析为 JSON 只是开始。{"date":"2026-09-18"} 可以解析,却没有约定的三个字段。字段齐全后,还要检查值的类型、日期是否存在、出处编号是否属于本次资料,以及引文是否确实出现在对应通知中。这些条件可以由普通程序确定。
第三,已有证据是否足以接受这个候选? 三个值全为 null 时,进入资料核对,不输出截止结论。有候选日期时,本例先展示候选和两份原文,让核对者确认日期含义及通知效力,再决定接受或拒收。
#mermaid-svg-NHxyy8syT537BY1U{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NHxyy8syT537BY1U .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NHxyy8syT537BY1U .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NHxyy8syT537BY1U .error-icon{fill:#552222;}#mermaid-svg-NHxyy8syT537BY1U .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NHxyy8syT537BY1U .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NHxyy8syT537BY1U .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NHxyy8syT537BY1U .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NHxyy8syT537BY1U .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NHxyy8syT537BY1U .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NHxyy8syT537BY1U .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NHxyy8syT537BY1U .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NHxyy8syT537BY1U .marker.cross{stroke:#333333;}#mermaid-svg-NHxyy8syT537BY1U svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NHxyy8syT537BY1U p{margin:0;}#mermaid-svg-NHxyy8syT537BY1U .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NHxyy8syT537BY1U .cluster-label text{fill:#333;}#mermaid-svg-NHxyy8syT537BY1U .cluster-label span{color:#333;}#mermaid-svg-NHxyy8syT537BY1U .cluster-label span p{background-color:transparent;}#mermaid-svg-NHxyy8syT537BY1U .label text,#mermaid-svg-NHxyy8syT537BY1U span{fill:#333;color:#333;}#mermaid-svg-NHxyy8syT537BY1U .node rect,#mermaid-svg-NHxyy8syT537BY1U .node circle,#mermaid-svg-NHxyy8syT537BY1U .node ellipse,#mermaid-svg-NHxyy8syT537BY1U .node polygon,#mermaid-svg-NHxyy8syT537BY1U .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NHxyy8syT537BY1U .rough-node .label text,#mermaid-svg-NHxyy8syT537BY1U .node .label text,#mermaid-svg-NHxyy8syT537BY1U .image-shape .label,#mermaid-svg-NHxyy8syT537BY1U .icon-shape .label{text-anchor:middle;}#mermaid-svg-NHxyy8syT537BY1U .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NHxyy8syT537BY1U .rough-node .label,#mermaid-svg-NHxyy8syT537BY1U .node .label,#mermaid-svg-NHxyy8syT537BY1U .image-shape .label,#mermaid-svg-NHxyy8syT537BY1U .icon-shape .label{text-align:center;}#mermaid-svg-NHxyy8syT537BY1U .node.clickable{cursor:pointer;}#mermaid-svg-NHxyy8syT537BY1U .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NHxyy8syT537BY1U .arrowheadPath{fill:#333333;}#mermaid-svg-NHxyy8syT537BY1U .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NHxyy8syT537BY1U .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NHxyy8syT537BY1U .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NHxyy8syT537BY1U .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NHxyy8syT537BY1U .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NHxyy8syT537BY1U .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NHxyy8syT537BY1U .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NHxyy8syT537BY1U .cluster text{fill:#333;}#mermaid-svg-NHxyy8syT537BY1U .cluster span{color:#333;}#mermaid-svg-NHxyy8syT537BY1U div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NHxyy8syT537BY1U .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NHxyy8syT537BY1U rect.text{fill:none;stroke-width:0;}#mermaid-svg-NHxyy8syT537BY1U .icon-shape,#mermaid-svg-NHxyy8syT537BY1U .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NHxyy8syT537BY1U .icon-shape p,#mermaid-svg-NHxyy8syT537BY1U .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NHxyy8syT537BY1U .icon-shape .label rect,#mermaid-svg-NHxyy8syT537BY1U .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NHxyy8syT537BY1U .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NHxyy8syT537BY1U .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NHxyy8syT537BY1U :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
否
是
确认成立
确认不成立
尚不能确定
收到响应
生成完成且有可用回答?
拒收当前结果,保留原因
JSON、字段和基础条件合规?
有候选日期?
待核对:检查或补充资料
待核对:展示候选及通知原文
已接受:填写页面结果
这个顺序使每次停止都有具体原因。生成未完成时,不进入 JSON 修补;字段错误时,不补一个默认日期;依据尚未确认时,也不会因为程序已经解析成功,就自动把候选变成正式结果。
"拒收"是停止使用当前结果,不代表整个任务永远无法完成。格式错误可以反馈具体问题后有限次重试;因输出预算耗尽而截断,需要调整预算或缩小单次输出范围后重新生成,不能原样反复重试。信息不足则要补充资料。DeepSeek 的 JSON Output 文档也提醒了输出截断及空内容的情况。
4. 把接收、检查和核对接成一个能运行的程序
示例用终端中的"页面结果"模拟页面填写,处理从一份已取得的响应开始。网络请求失败或 HTTP 状态异常时,应先处理接口错误,不把错误页面送入这个接收程序。
Responses API 的返回里,status 说明整次生成是否完成,output 存放输出条目;其中 output_text 内容块的 text 才是模型回答。模型按要求生成 JSON 时,这个 text 仍是一段文字,程序还要把它解析成字段。解析接口响应和解析回答中的 JSON,是两个不同步骤。
先保存 response.json。以下取自字段组第 1 次真实响应,只保留本例会读取的字段,字段值未改动;完整响应也可以直接交给程序处理:
json
{
"status": "completed",
"output": [
{
"type": "message",
"role": "assistant",
"status": "completed",
"content": [
{
"type": "output_text",
"text": "{\"deadline_date\":\"2026-09-18\",\"source_id\":\"D2\",\"quote\":\"本次读书会报名截止日期提前到2026年9月18日,原通知中的截止日期作废。\"}"
}
]
}
]
}
这里的反斜杠用于在外层 JSON 字符串里表示双引号。读入响应文件后,text 的实际值就是第 2 节那段三字段 JSON。
再把下面完整代码保存为 receive_result.py,使用 Python 3.10 或以上版本运行,不需要安装第三方包。DOCUMENTS 保存程序本来就持有的教学通知;核对时展示全部两份,避免只看到模型选中的那份。
python
"""Receive a saved Responses API result. No network calls are made."""
import json
import sys
from datetime import date
from pathlib import Path
DOCUMENTS = {
"D1": "本次读书会报名将于2026年9月20日截止。",
"D2": "本次读书会报名截止日期提前到2026年9月18日,原通知中的截止日期作废。",
}
FIELDS = {"deadline_date", "source_id", "quote"}
def result(state, reason, candidate=None):
return {"state": state, "reason": reason, "candidate": candidate}
def unique_object(pairs):
obj = {}
for key, value in pairs:
if key in obj:
raise ValueError("JSON 中有重复字段")
obj[key] = value
return obj
def reject_constant(value):
raise ValueError("JSON 中有不允许的数值常量")
def parse_json(text):
return json.loads(text, object_pairs_hook=unique_object,
parse_constant=reject_constant)
def check_candidate(text):
try:
data = parse_json(text)
except ValueError as error:
raise ValueError("回答不是符合约定的 JSON") from error
if not isinstance(data, dict) or set(data) != FIELDS:
raise ValueError("必须恰好包含三个约定字段")
if all(value is None for value in data.values()):
return None
if not all(isinstance(value, str) and value.strip()
for value in data.values()):
raise ValueError("三个值应都是非空字符串,或全部为 null")
try:
deadline = date.fromisoformat(data["deadline_date"])
except ValueError as error:
raise ValueError("截止日期不是有效日期") from error
if deadline.isoformat() != data["deadline_date"]:
raise ValueError("截止日期必须使用 YYYY-MM-DD")
source_id = data["source_id"]
if source_id not in DOCUMENTS:
raise ValueError("出处不在本次资料中")
if data["quote"] not in DOCUMENTS[source_id]:
raise ValueError("引文不是该通知的连续原文")
return data
def receive_response(response):
if not isinstance(response, dict):
return result("拒收", "响应应是 JSON 对象")
if response.get("status") != "completed":
return result("拒收", "响应尚未正常完成,请检查完成状态及原因")
output = response.get("output")
if not isinstance(output, list):
return result("拒收", "响应缺少输出列表")
texts = []
for item in output:
if not isinstance(item, dict):
return result("拒收", "输出条目结构异常")
if item.get("type") == "reasoning":
continue # 推理条目不作为最终回答。
if item.get("type") != "message" or item.get("role") != "assistant":
return result("拒收", "本任务只接收助手回答消息")
if item.get("status") != "completed":
return result("拒收", "回答消息尚未完成")
content = item.get("content")
if not isinstance(content, list):
return result("拒收", "回答内容结构异常")
for part in content:
if not isinstance(part, dict):
return result("拒收", "回答内容块结构异常")
if part.get("type") == "refusal":
return result("拒收", "模型拒绝了本次请求")
if part.get("type") != "output_text" or not isinstance(part.get("text"), str):
return result("拒收", "本任务只接收文字回答")
texts.append(part["text"])
text = "".join(texts)
if not text.strip():
return result("拒收", "需要非空、完整的回答")
try:
candidate = check_candidate(text)
except ValueError as error:
return result("拒收", str(error))
if candidate is None:
return result("待核对", "模型未给出日期,需要核对资料或补充信息")
return result("待核对", "字段检查通过,需要核对日期含义与通知效力", candidate)
def finish_review(received, decision):
# 决定来自核对当前候选的调用方,不从模型回答读取。
if received["state"] != "待核对" or received["candidate"] is None:
return received
if decision == "y":
return result("已接受", "依据已核对,可填入通知页面", received["candidate"])
if decision == "n":
return result("拒收", "核对发现日期或依据不成立")
return received # 尚未确定时保留待核对状态。
def main():
if len(sys.argv) != 2:
sys.exit("用法:python receive_result.py response.json")
try:
response = parse_json(Path(sys.argv[1]).read_text(encoding="utf-8"))
except (OSError, ValueError):
sys.exit("拒收:无法读取响应文件,或文件不是有效 JSON。")
received = receive_response(response)
if received["candidate"] is not None:
print("待核对候选:", json.dumps(received["candidate"], ensure_ascii=False))
for source_id, body in DOCUMENTS.items():
print(f"{source_id}:{body}")
print("请核对:日期是否抄对、引文是否支持日期、是否有通知将其作废。")
decision = input("核对成立输入 y,不成立输入 n,尚不能确定直接回车:").strip().lower()
received = finish_review(received, decision)
print(f"处理结果:{received['state']};{received['reason']}")
if received["state"] == "已接受":
data = received["candidate"]
print(f"页面结果:截止日期 {data['deadline_date']};依据 {data['source_id']}")
if __name__ == "__main__":
main()
在这两个文件所在目录运行:
powershell
python receive_result.py response.json
程序先显示待核对的候选以及 D1、D2。对照原文可以确认:D2 明确修改了报名日期,并废止 D1 的旧日期。输入 y 后,应得到:
text
处理结果:已接受;依据已核对,可填入通知页面
页面结果:截止日期 2026-09-18;依据 D2
如果当前候选不能确认,直接回车就保留"待核对";确认不成立时输入 n,程序拒收,且不会输出页面结果。三个值全为空时没有可供确认的日期,程序也不会询问是否接受。
代码中的职责是连续的:receive_response 检查完成状态并取出文字,check_candidate 检查字段和基础条件,finish_review 接收对当前候选的核对决定。最后只有 state 为"已接受"时,才执行填写页面的分支。
其中 y 必须来自调用方对当前候选的核对,不能从模型生成的"已确认"字段读取。本例的终端输入只是这个决定的最小实现;实际页面还需要把核对动作绑定到当时看到的候选和资料。代码负责执行核对决定,核对者的判断质量仍需单独保障,不能无条件传入 y 来跳过核对。
parse_json 还主动拒绝重复字段及 NaN 等非标准数值,避免默认解析行为扩大接收范围。Python 的 JSON 文档说明了这些默认行为。代码直接表达了本例的少量字段规则,没有实现通用 Schema 校验器;遇到不同接口的响应结构,需要先按该接口约定调整提取部分。
5. 验证每个出口,并明确接受之后可以做什么
两份真实 JSON 响应重放后,都经历了"字段检查通过 → 待核对 → 对照通知确认 → 已接受"。只有成功路径还不够检查接收程序,因此还需要人为改变输入,观察其他分支。
下面全部是人工构造的程序测试条件,不是本次模型调用中出现过的错误;每行独立从有效的字段响应开始修改:
| 改变的条件 | 程序应停在哪里 | 后续怎样处理 |
|---|---|---|
整次响应状态改为 incomplete,即使文字看起来完整 |
完成状态检查,拒收 | 查看原因;若预算耗尽,调整预算或范围后重做 |
内容块改为 refusal |
回答提取,拒收 | 呈现拒绝状态,不当成截止日期 |
| 回答文字缺少结束括号 | JSON 解析,拒收 | 处理生成失败,不自动补括号使用 |
删除 source_id |
字段检查,拒收 | 指出缺少的字段,不静默补默认值 |
日期改为 2026-02-30,或出处改为 D9 |
日期或出处检查,拒收 | 指出不满足的具体条件 |
三个字段全为 null |
待核对资料,无候选可接受 | 检查输入及资料,必要时补充;不能按无截止日期处理 |
| 改成 D1、9 月 20 日及 D1 的真实原文 | 自动检查通过,停在待核对 | 查看 D2 可发现旧日期已作废,核对后拒收 |
| 保留正确候选,但暂不作核对决定 | 待核对 | 不填写正式结果;确认后再继续 |
旧通知这个条件说明了为什么需要明确检查范围:日期存在、编号存在、引文也存在,但这些检查没有判断"这项安排是否仍然有效"。本次真实模型已经把这层关系判断对了;人工样本只检验程序有没有把未经确认的候选直接放行。
对通知页面,本例的"已接受"表示:响应正常完成,字段符合约定,核对者确认资料支持这个日期。它允许填写日期与依据。如果用途改为自动创建提醒,还要明确具体时刻、时区及用户是否同意保存;只有日期的通知不能自动补出这些条件。
可靠接收最终要落实到程序的行为:读到约定的数据才能继续,遇到不合规结果有明确原因,依据不足时保留待核对状态,满足当前用途的条件后才使用结果。模型的实际表现由真实调用记录说明,接收程序的处理能力则通过这些具体分支验证。