让 AI 给前端生成一张任务卡片,乍看很简单:把用户的一段话交给模型,拿到 JSON,再把字段放进页面。

真正接入后,经常遇到的问题是:模型明明返回了内容,页面却显示空白;偶尔可以解析,数据却无法保存;同一段代码昨天正常,今天又因为少了字段报错。

这篇文章用一个Agent 生成任务卡片的例子,把 JSON 解析、结构校验、业务校验、截断、拒绝、重试和前端状态串起来。文末给出一个不调用真实模型、不依赖第三方库的 Python 离线示例,方便先把流程跑通。

AI 输出进入系统的校验链路

一、先看一个看似正常的返回

假设前端期待三个字段:任务标题 title、优先级 priority、截止日期 due_date。

{"title":"整理接口文档","priority":"2","due_date":"2026-11-12"}

这段文字能被 JSON 解析器读出来,却依然可能无法直接进入业务:如果后端要求 priority 是整数,上面的 "2" 是字符串;如果前端根据整数值映射样式,也可能出现展示异常。

再看这一段:

{"title":"整理接口文档","priority":2,"due_date":"2026-11-01"}

假设任务创建日期是 2026 年 11 月 8 日,这段数据语法、字段、类型都正确,但截止日期已经过去。如果业务不允许创建过期任务,就应当在业务校验阶段拦住。

核心区别:JSON 能解析、结构符合约定、符合当前业务,分别是三个不同的问题。

二、从模型到页面,数据会经过哪些环节?

一个可落地的链路通常包括:请求模型、识别响应状态、解析数据、校验结构、验证业务规则、保存或返回前端。调用链中的错误不一定都来自模型,也可能来自接口封装、前后端字段约定或数据库约束。

层次要回答的问题例子下一步
响应状态请求是否完成?拒绝、超时、输出不完整先处理状态,不能假定有完整 JSON
JSON 语法能否解析为数据?少一个右括号、出现非法字符记录解析失败,按策略处理
结构约定字段、类型、枚举是否符合要求?缺 title,优先级为字符串用 schema 或代码校验
业务规则数据放进当前业务是否合理?截止日期早于创建日期按业务上下文验证
持久化和展示能否保存、正确展示?重复提交、数据库约束失败幂等、事务和前端状态提示

例如 priority=2 在结构层面是合法的;但“这个任务真的应该是中优先级吗”,还要看用户输入和业务规则。类型正确不代表内容真实。

三、最常见的五类故障

情况示例程序可能出现什么问题
无法解析{"title":"整理文档"JSON 解码报错
缺失字段{"title":"整理文档"}后续读取字段时报错
类型不一致"priority":"2"比较、运算或映射出现异常
枚举越界"priority":9前端找不到对应标签
业务不合理日期有效,但早于业务日期入库或后续流程被拒绝

还有一种容易被忽视的情况:返回对象有额外字段。是否允许额外字段应由接口契约决定,不能假设所有项目都一样。对于要求严格的接口,额外字段可以主动拒绝;对于兼容性要求更高的场景,也可以采用明确的忽略策略。

四、只靠提示词要求“返回 JSON”够吗?

提示词可以帮助模型理解目标,例如:“只返回包含 title、priority、due_date 的 JSON,优先级为 1、2、3。”但应用仍需要处理完整性、拒绝、解析和业务边界。

如果所用模型和接口支持结构化输出,可以定义 JSON Schema,让成功完成的结构化响应遵循约定的字段和类型。相比单纯要求返回 JSON,它能把结构约束放在接口能力层面。

需要区分两个概念:

方法主要约束仍需注意
提示词要求 JSON告诉模型输出目标输出可能无法解析或不符合字段要求
JSON 模式主要关注 JSON 格式合法 JSON 不等于符合业务结构
结构化输出使用受支持的 Schema 约束输出结构模型拒绝、输出不完整、业务真实性仍需处理
后端业务校验按实际业务上下文核验需要由应用维护规则和异常处理

不同厂商、模型和接口的支持范围会变化,接入前应查看对应文档。例如 OpenAI 的说明中明确介绍了结构化输出、拒绝和不完整响应的处理方式:Structured model outputs。不要把其中某一家的字段名直接当成所有模型服务的统一协议。

五、Schema 是合同,业务规则是运行现场

把 Schema 想成前后端约定的数据合同:

title     必填,字符串
priority  必填,整数,只允许 1、2、3
due_date  必填,YYYY-MM-DD 格式字符串

业务规则还要结合现场信息。比如截止日期是否早于当天、某用户是否有权创建任务、任务名称是否在同一项目内重复,都要读取可信的业务状态。

例如模型返回 {"priority":2},我们能验证它是允许的整数;但是无法仅凭这个数字断定优先级符合用户真实意图。对于高影响操作,应该进一步确认或者交给明确的业务规则判断。

建议顺序:先处理响应状态,再解析,再做结构校验,最后结合可信业务数据做验证。

六、拒绝和截断,不要硬塞进 JSON 解析器

模型响应除了成功,也可能被拒绝、在生成过程中结束,或者因为服务和网络问题没有拿到完整结果。接入时先查看服务提供方定义的状态,而不是无条件取一个文本字段并调用 json.loads()。

状态应用应考虑的处理方式
已完成且有输出尝试解析并验证
拒绝展示可理解的拒绝结果,按产品策略处理;不要无限重试
不完整或截断记录原因;允许时有限重试,或让用户缩小输入范围
超时、限流、临时失败区分错误类别,按服务文档与成本预算决定是否重试
未知状态明确记录并返回失败,不要当作成功

**流式输出也有这个问题:**前端收到的一段文本只是片段时,通常还不是完整 JSON。可以展示“正在生成”,但应在收到完成信号后再进行完整解析;如果确实需要边生成边展示结构,需要单独设计增量解析与更新协议。

七、一个完整的离线示例

下面的例子用固定数据模拟不同响应。它不调用任何模型、不连接数据库,也不产生业务副作用;completed、incomplete、refused 是教学用的统一状态,不是某个供应商的原始返回字段。

代码使用 Python 标准库,保存为 json_demo.py 后运行。示例将业务日期固定为 2026 年 11 月 8 日,便于以后重复运行时看到一致结果。

"""离线演示:模拟模型响应,不调用网络、不写入业务系统。Python 3.9+。"""
import json
from datetime import date

REQUIRED = {"title", "priority", "due_date"}


class InvalidOutput(ValueError):
    pass


def validate(text, today):
    try:
        data = json.loads(text, parse_constant=lambda _: (_ for _ in ()).throw(ValueError("非法常量")))
    except (ValueError, TypeError) as exc:
        raise InvalidOutput("JSON 无法解析") from exc
    if not isinstance(data, dict) or set(data) != REQUIRED:
        raise InvalidOutput("字段缺失或存在未允许的字段")
    if not isinstance(data["title"], str) or not data["title"].strip():
        raise InvalidOutput("title 必须是非空字符串")
    if type(data["priority"]) is not int or data["priority"] not in (1, 2, 3):
        raise InvalidOutput("priority 必须是整数 1、2 或 3")
    raw = data["due_date"]
    if not isinstance(raw, str):
        raise InvalidOutput("due_date 必须是 YYYY-MM-DD 字符串")
    try:
        parsed = date.fromisoformat(raw)
    except ValueError as exc:
        raise InvalidOutput("due_date 不是有效日期") from exc
    if parsed.isoformat() != raw:
        raise InvalidOutput("due_date 必须使用 YYYY-MM-DD 格式")
    if parsed < today:
        raise InvalidOutput("截止日期早于业务日期")
    return data


def create_task(responses, today, max_attempts=2):
    """演示 provider 状态与解析分支;每项响应是模拟数据。"""
    for attempt, response in enumerate(responses[:max_attempts], 1):
        status = response["status"]
        if status == "refused":
            return {"state": "refused", "attempts": attempt}
        if status == "incomplete":
            reason = "响应不完整"
        elif status == "completed":
            try:
                return {"state": "success", "data": validate(response["text"], today), "attempts": attempt}
            except InvalidOutput as exc:
                reason = str(exc)
        else:
            return {"state": "failed", "reason": "未知响应状态", "attempts": attempt}
        # 真实系统可将脱敏后的错误类别作为下一次生成的反馈;此处不发送网络请求。
    return {"state": "failed", "reason": reason if responses and max_attempts > 0 else "没有响应", "attempts": min(len(responses), max_attempts)}


if __name__ == "__main__":
    today = date(2026, 11, 8)  # 固定业务日期,保证演示可复现
    good = '{"title":"整理接口文档","priority":2,"due_date":"2026-11-12"}'
    cases = {
        "缺字段后修复": [{"status":"completed", "text":"{}"}, {"status":"completed", "text":good}],
        "类型错误": [{"status":"completed", "text":'{"title":"整理文档","priority":"2","due_date":"2026-11-12"}'}],
        "业务日期错误": [{"status":"completed", "text":'{"title":"整理文档","priority":2,"due_date":"2026-11-01"}'}],
        "响应被截断": [{"status":"incomplete"}, {"status":"completed", "text":good}],
        "模型拒绝": [{"status":"refused"}],
    }
    for name, replies in cases.items():
        print(name, json.dumps(create_task(replies, today), ensure_ascii=False))

运行:

python json_demo.py

你会看到:缺字段后给出正确响应的模拟场景最终成功;字符串优先级与过期日期被拒绝;截断后提供正确响应的场景成功;模型拒绝被直接标记为 refused。

这里的“重试”只是依次读取预先准备好的模拟响应。**它不代表模型真的根据错误自动修正,也不能作为真实成功率或性能数据。**在真实系统里,要把脱敏后的错误类别传给生成环节、执行下一次请求,并单独处理网络异常、费用和幂等。

八、重试为什么不能无限进行?

对解析失败或结构不符合的结果,可以在满足产品与供应商策略的前提下有限重试:说明哪个字段不符合要求,让模型重新生成。但重试次数应有上限,还要考虑总耗时和费用。

问题是否适合直接重试设计建议
JSON 无法解析可考虑有限重试提供简短、脱敏的错误类型
缺字段或类型错误可考虑有限重试明确指出字段约束
信息不足视场景而定让用户补充信息,不要让模型猜
模型明确拒绝不应盲目重试给用户展示合理提示
临时限流视接口政策有上限的退避并遵守服务规则
业务写入失败不能简单重复整个流程先确认是否已写入,使用幂等键与事务

尤其需要注意:**生成成功后调用了外部写操作,再整体重试,可能产生重复任务。**生成和验证可以独立重试;真正写入时应有幂等保护,并以实际业务系统的结果为准。

格式、结构和业务错误对比

九、前端应该如何展示?

如果所有异常都显示“AI 出错了”,用户不知道发生了什么,也不知道能否继续。更实用的做法是将后端状态转换为清晰的界面反馈。

页面状态用户看到什么可提供的操作
生成中任务正在生成等待或取消,具体取决于系统能力
成功标题、优先级和截止日期用户确认后再提交
信息不足需要补充哪些内容编辑并重新提交
生成失败简洁说明暂时无法完成重试或手动填写
被拒绝展示适当的拒绝说明修改输入或停止

页面不要直接展示原始堆栈、密钥或完整内部提示词;开发排查信息应进入受控日志。对用户展示的失败原因与开发者日志可以采用不同粒度。

任务卡片成功和失败界面示意

十、如何定位一次线上错误?

先别急着改提示词,按链路逐层查:

  1. 看请求是否成功返回,是否超时或被限流。
  2. 看响应状态是完成、拒绝还是不完整。
  3. 看解析是否成功,以及失败的位置和类别。
  4. 对照接口契约检查字段、类型和枚举。
  5. 核对业务规则需要的时间、用户和项目状态。
  6. 确认数据是否已写入,避免重复操作。
  7. 检查前端处理的是哪一种状态,有没有把失败渲染成空白。

日志可记录请求 ID、模型或服务版本、响应状态、校验类别、耗时和重试次数。真实用户内容是否记录、保存多久、谁能访问,都应遵循项目自己的隐私和数据管理要求;不要把密钥写入日志。

十一、接入生产项目时可以继续补齐什么?

教学示例为了清楚展示流程,使用了标准库手写校验。真实系统可以考虑使用 Pydantic 等工具集中管理字段规则,并生成或维护 Schema。使用 Pydantic 时,还要了解默认类型转换与严格模式的区别:例如业务不允许字符串数字自动转成整数,就需要明确设置严格校验。Pydantic 严格模式文档

此外,还可以补上输入和输出大小限制、字段长度、日期时区规则、敏感内容脱敏、服务端鉴权、幂等键和监控告警。Python 官方也提示,解析不可信 JSON 前应考虑输入规模限制:Python JSON 文档。

不同规则的责任方也要明确:模型负责生成候选内容;后端负责验证和授权;前端负责清晰展示;最终保存结果由业务系统决定。

十二、写在最后

AI 返回了一段 JSON,只表示应用拿到了候选数据。要让它稳定进入产品,需要先区分响应是否完成、内容能否解析、结构是否符合约定,再结合可信上下文检查业务规则。

从最小项目开始,可以先做三件事:定义字段合同、给后端加校验、为前端准备明确的成功和失败状态。之后再根据业务需要增加有限重试、幂等和监控。这样即使模型偶尔给出异常结果,系统也有机会把问题留在边界内,而不是让错误直接传到用户面前。

参考资料

Logo

更多推荐