AI 应用工程入门(三):如何减少幻觉和格式错误?
用普通人能听懂的方式解释为什么模型输出会不稳定,以及如何用约束、格式和异常处理提高可靠性。
上一篇讲的是 Prompt 的基本结构,这一篇继续往下:为什么同样一个任务,大模型有时输出很好,有时格式乱掉,有时还会补出不存在的信息?要做 AI 应用,就必须把这些不稳定因素提前纳入设计。
题目 1:为什么 AI 输出经常不稳定?
标准答案:大模型是概率生成模型,不是传统确定性函数。即使输入相似,也可能因为上下文、模型版本、采样参数和提示词细节不同,生成不同结果。
工程要点:任务越模糊,输出越飘;输出格式越宽松,越容易不可解析;约束越少,越容易幻觉。要提高稳定性,就要把角色、任务、边界、格式和异常情况写清楚。
题目 2:如何减少幻觉?
标准答案:让模型只基于给定材料回答,并明确告诉它资料不足时不要编造。
工程要点:可以在 Prompt 里写:“请只基于我提供的资料回答;如果资料中没有相关信息,请返回资料中未提到;不要根据常识或猜测补充答案。”这不是万能防线,但能明显降低凭空补全。
题目 3:如何让模型稳定返回 JSON?
标准答案:明确要求只返回合法 JSON,并给出完整结构,不允许 Markdown 代码块和额外解释。
工程要点:结构化输出任务最好配合 JSON Schema 或后端校验。即使模型大多数时候按格式返回,也要准备解析失败后的处理方式,比如重试一次、降级为人工处理、或返回明确错误。稳定性不能只靠相信模型听话。
一种常见工程策略是“先解析,失败再修复一次”,但它只适合修格式,不适合修事实:
async function askForJson(prompt: string) {
const raw = await callModel(prompt);
try {
return JSON.parse(raw);
} catch {
const fixed = await callModel(
"把下面内容修复为合法 JSON,只返回 JSON:\n" + raw
);
return JSON.parse(fixed);
}
}
如果第一次输出的是错误事实,第二次修复 JSON 只会把错误包装得更整齐。事实类任务仍然要依赖资料来源、引用和评测。
题目 4:角色提示有多重要?
标准答案:角色可以影响表达风格、判断标准和输出侧重点,但角色不是权限系统,也不能保证事实正确。
工程要点:角色负责风格,约束负责边界,输出格式负责可解析性。只写“你是资深专家”并不能解决幻觉、权限、格式和事实校验问题。复杂控制要靠系统设计,而不是靠角色一句话。
题目 5:异常情况为什么要写进 Prompt?
标准答案:因为真实用户输入经常不完整、太短、太长或与任务无关。模型如果没有异常出口,就容易强行回答。
工程要点:比如文章标题生成任务,可以规定“如果文章内容太短或信息不足,返回空 titles 数组”。知识问答任务可以规定“资料未提到时返回 null 和原因”。允许模型说不知道,是减少错误答案的重要设计。
完整示例:稳定性要靠 Prompt、Schema、校验一起做
减少幻觉和格式错误,不能只靠一句“不要胡说”。模型生成的是概率文本,稳定性要由多层机制共同完成:Prompt 限定资料来源,Schema 限定输出结构,服务端校验结果,失败时重试或降级。课程里要掌握的是这套组合,而不是某个神奇句式。
事实类任务可以先给模型一个明确的资料边界:
请只基于【参考资料】回答。
如果资料中没有答案,请返回:资料中未提到。
不要根据常识、经验或猜测补充事实。
回答中必须给出 sourceIds,sourceIds 只能来自参考资料。
【参考资料】
[doc-1] 年假申请需提前 3 个工作日在 OA 系统提交。
[doc-2] 病假需提交医疗证明。
【用户问题】
年假要提前多久申请?
结构化输出任务则要把字段写清楚,并在服务端做校验。模型返回 JSON 不代表业务上可用,字段缺失、类型错误、引用来源不存在,都要拦住。
type AnswerResult = {
answer: string;
sourceIds: string[];
confidence: "high" | "medium" | "low";
};
function validateAnswerResult(result: AnswerResult, allowedSources: string[]) {
if (!result.answer.trim()) return false;
if (!Array.isArray(result.sourceIds)) return false;
return result.sourceIds.every((id) => allowedSources.includes(id));
}
解析失败可以重试一次,但重试只能解决格式问题,不能解决事实问题。比如模型把 JSON 写成了 Markdown,重试修复是合理的;如果模型说“提前 7 天申请”,但资料里写的是“提前 3 个工作日”,那不是格式问题,而是忠实度问题,需要回到 Prompt、检索和评测。
async function callWithJsonRepair(prompt: string) {
const first = await callModel(prompt);
try {
return JSON.parse(first);
} catch {
const repaired = await callModel(
"修复为合法 JSON,只返回 JSON,不要改动字段含义:\n" + first
);
return JSON.parse(repaired);
}
}
这一课的完整答案是:稳定输出来自四件事,明确边界、结构化格式、服务端校验、失败处理。任何一层缺失,Demo 也许能跑,产品就会出问题。
这一篇最该带走的结论是:稳定不是靠一次写出完美 Prompt,而是靠明确边界、结构化输出、异常处理和后端校验共同完成。