当你第一次成功让模型返回一个完美的 JSON 结构时,那种“代码终于受控”的成就感溢于言表。但如果你直接在代码里写下 print(data[‘title’]),那么恭喜你,你已经为未来的生产事故埋下了伏笔。
在 AI 工程中,有一个残酷的现实:模型返回 JSON,并不代表程序可以无条件信任它。
即便你用了最强的 GPT-4 或 Claude 3,模型依然会偶尔“掉链子”。常见的异常场景包括:
1. 结构性“幻觉”
你要求返回一个数组,模型可能因为上下文太长,返回了一个被截断的字符串,或者干脆在 JSON 前后加了句“这是你要的结果:”。
- 后果:
json.loads()直接抛出ValueError或JSONDecodeError。
2. 字段“失踪案”
模型可能会漏掉你认为“必填”的字段,或者自作聪明地修改了键名(比如把 user_name 改成了 username)。
- 后果:程序访问时触发
KeyError。
3. 类型“变色龙”
最典型的例子:你期待 tags 是一个数组 [“A”, “B”],模型却返回了逗号分隔的字符串 “A, B”。
- 后果:下游的
.map()或foreach逻辑直接崩溃。
在传统工程中,我们对用户提交的表单、第三方接口的返回都会进行严格的校验。AI 输出本质上也是一种“外部输入”,而且是比第三方接口更不稳定、更不可控的输入。
优秀的 AI 工程师不相信“概率”,只相信“防御”。
不要只写 json.loads。一个工业级的解析流程应该是这样的:
❌ 试玩版代码(极脆)
response = get_ai_response(prompt) data = json.loads(response) print(data[‘tags’][0]) # 万一 tags 是字符串?万一 data 是空?
✅ 工程版代码(稳健)
def safe_parse_tags(ai_output: str):
try: data = json.loads(ai_output) # 1. 结构校验 if not isinstance(data, dict): return [] # 2. 字段与类型强校验 tags = data.get('tags') if isinstance(tags, list): return tags elif isinstance(tags, str): # 这里的“防御”体现为:即便模型给错了类型,我们也尝试修复它 return [t.strip() for t in tags.split(',')] return [] # 兜底逻辑 except (json.JSONDecodeError, Exception): return [] # 彻底失败时的安全降级(降级思维)
在复杂的 AI 项目中,手动写 isinstance 太痛苦了。业界标准的做法是引入 Pydantic(Python)或者 Zod(TypeScript)。
from pydantic import BaseModel, Field, validator from typing import List
class ProductSchema(BaseModel):
title: str price: float tags: List[str] = Field(default_factory=list)
def handle_tags_string(cls, v): if isinstance(v, str): return [t.strip() for t in v.split(',')] return v
通过 Schema,你可以把“不确定”的模型输出,强行约束为“确定”的程序对象。这就是从“调包”到“工程”的跃迁。
很多人问:会调 API 就算 AI 工程师了吗?
我的回答是:当你开始不再迷信模型输出,而是像对待任何“不可信输入”一样去解析、校验和兜底时,你才算真正开始了 AI 工程化的修行。
一句话总结:结构化输出解决的是“模型怎么说”,而结构化消费解决的是“程序怎么活”。
讨论区:你在解析 AI 返回的 JSON 时遇到过哪些离谱的“幻觉”?你是怎么做兜底的?欢迎交流!
本文首发于掘金,记录前端开发者向 AI 工程转型的防御性编程实践。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/258693.html