Prompt、Context 与结构化输出:把自然语言变成接口
核验日期:2026-07-16。不同模型支持的消息角色、JSON Schema 子集、工具与缓存能力不同;本文以“版本化输入、运行时验证、有限恢复”为稳定契约,具体参数以当前供应商文档为准。
Prompt 是程序输入的一部分,不是散落在代码中的咒语。它把任务、约束、可用数据和期望输出交给模型;Context 是这次调用实际可见的全部内容;Structured Output 则把概率文本压到机器可验证的形状。三者只有和权限、来源、错误处理与评测一起设计,才能成为工程接口。
指令层级与数据来源
应用通常有平台/系统、开发者、用户和工具/检索内容等来源。高优先级规则定义安全与产品边界,用户定义当前目标,外部内容只提供数据。不要指望 XML 标签、Markdown 引号或“以下内容不可信”能够从根本上阻止提示注入;它们帮助模型辨认边界,但真正防线是最小权限、参数验证、允许列表、审批和输出处理。
Context Builder 应输出 manifest:每一块的来源、生成时间、权限范围、token 估算、内容哈希、是否可信和裁剪理由。这样一次失败可以回答“模型没看到事实”还是“看到了但没有使用”。直接拼接字符串会丢掉这些证据。
先写任务规格,再写措辞
规格至少包含:目标、输入字段、输出 Schema、允许的数据源/工具、必须引用的主张、拒绝/澄清条件、风险级别、时限和验收样例。Prompt 只是规格的一种实现。若团队无法写出可判定的成功条件,就无法可靠优化 Prompt。
清晰的模板通常按职责组织:角色/目标;不可违反的约束;数据与来源;处理要求;输出契约;少量覆盖边界的示例。不要堆同义强调词,也不要让几十条规则相互冲突。长策略拆成确定性前置/后置检查,模型只处理确实需要语义判断的部分。
Few-shot 示例能展示字段关系和边界,但示例会占上下文,也会把偶然风格变成模式。优先选择互补的正例、拒绝例和歧义例,保留独立测试集;不要把测试答案泄漏进示例。
结构化输出有两层正确性
第一层是语法/结构:JSON 能解析,字段、类型、枚举、必填和额外属性符合 Schema。第二层是语义:technologyId 是否存在,引用是否支持结论,日期范围是否合理,当前用户是否有权限。模型原生 Structured Output 能提高第一层成功率,不能替代第二层业务验证。
type Recommendation = {
answer: string;
technologyIds: string[];
citations: Array<{ sourceId: string; claim: string }>;
needsClarification: boolean;
};
function validateRecommendation(value: unknown): Recommendation {
if (!value || typeof value !== "object") throw new Error("输出必须是对象");
const v = value as Record<string, unknown>;
if (typeof v.answer !== "string" || !Array.isArray(v.technologyIds)) {
throw new Error("answer 或 technologyIds 不符合契约");
}
if (!Array.isArray(v.citations) || typeof v.needsClarification !== "boolean") {
throw new Error("citations 或 needsClarification 不符合契约");
}
return v as Recommendation;
}局部示例用原生检查说明边界;生产可复用项目已有 JSON Schema/Ajv 或 Zod,不为一个对象再造验证框架。Schema 应设置合理长度、枚举和 additionalProperties 策略,避免“合法但无限大”的数组。供应商只支持 JSON Schema 子集时,在启动期检测或集成测试,不在运行时静默降级。
缺失、null、空值与未知字段
四者必须有业务语义。字段缺失可能表示“模型没有决定”,null 表示“明确无值”,空字符串可能无效,未知字段可能来自模型/Schema 版本漂移。不要用 value || default 把合法的 false、0 和空数组吞掉。跨服务契约最好版本化,消费者在兼容窗口内接受旧/新字段,但关键动作仍按当前规则重验。
对工具参数尤其严格:Schema 合法后仍规范化资源标识、限制数量、检查租户与权限,不把模型生成的路径、URL、SQL、命令直接执行。显示给用户的“预览参数”和最终执行参数应使用同一个规范化对象,避免审批后被重新生成。
解析失败与恢复
错误先分类:传输中断、模型拒绝、输出截断、JSON 语法、Schema 不匹配、语义冲突、策略拒绝。传输错误按幂等和 deadline 重试;截断可缩小输入或提高合理输出限额;Schema 错误最多做有限修复,并把具体 validation issue 提供给模型;语义冲突通常需要重新取数或向用户澄清。所有重试共享总预算。
“把坏 JSON 再交给另一个模型修复”会隐藏原始错误,还可能改变业务含义。若使用修复,保存原始值与差异,修复后重新做完整验证,高风险动作禁止自动修复。能由确定性 parser 修复的外围代码围栏可用小函数处理,不能把任意文本猜成命令。
引用是字段,不是装饰
回答中的可核验主张应关联 sourceId 与片段范围。模型生成 URL 不可信,应用只接受本次检索 manifest 或工具结果里存在的来源标识,再由服务端解析为链接。验证至少检查:来源存在且用户可见;片段版本未删除;主张与引用有语义支持;高风险结论是否需要多个独立来源。
引用覆盖率与引用正确性不同。每句话都带链接可能覆盖率高,却不代表链接支持它。评测应抽取 claim-source 对单独评分,并为无证据结论定义拒答或措辞降级。
会话历史与压缩
会话不是把所有 messages 永久回放。保存规范化事件:用户输入、模型结果、工具调用、工具结果、审批与系统状态;从事件生成当前 context。内容与状态分开,任务状态不能只藏在自然语言摘要中。对于“用户已批准哪个精确参数”“已执行哪个 call id”之类事实,使用结构化字段。
裁剪采用可解释顺序:安全/产品指令不可被摘要覆盖;当前任务和未完成工具状态优先;相关历史通过检索选择;旧对话可摘要;低价值寒暄丢弃。摘要生成失败时保留最近原文并降级,不能让整个会话不可用。
流式结构化输出
流式文本是增量 UI,不是逐 chunk 的完整字符串。UTF-8 字节、token、JSON 字段和 Markdown 标签都可能跨 chunk。客户端应消费类型化事件或累积 parser,不对每段 JSON.parse。服务端把 text-delta、tool-call、tool-result、error、finish 等事件区分;未知事件按版本策略处理。
结构化对象通常在完成后才能整体校验。若 UI 展示部分对象,要标记 pending,禁止在验证前触发副作用。客户端取消通过 AbortSignal 传播到模型、检索和工具;已经提交的外部副作用不能靠断开连接撤销,而需任务状态与补偿。
Prompt 版本、缓存与可观测性
Prompt 模板与代码同仓版本化,记录模板 ID、内容哈希、变量 Schema、模型/工具/语料版本和变更理由。不要把含密钥或个人数据的完整 prompt 默认写日志。trace 可保存长度、哈希、来源清单和脱敏样本;受控调试需权限与到期删除。
缓存键至少考虑模型、参数、模板版本、规范化输入、上下文/语料版本、权限范围和工具可用集。概率输出缓存会固化旧错误;只缓存适合重复且允许陈旧的结果,并保存 TTL/失效规则。不能跨租户共享含私有上下文的响应。
测试范围
模板单测检查变量缺失、转义、预算和 manifest;结构测试用固定假模型返回正确、额外字段、错误类型、截断与拒绝;语义测试覆盖不存在 ID、越权来源、引用不支持和日期冲突;流测试随机切 chunk、取消和中途 error;真实模型评测覆盖正常、歧义、对抗和长上下文。
Prompt 变更要像代码变更:先离线数据集比较,再少量 canary;指标同时看任务成功、安全、拒绝率、延迟和 token。只展示三个精选对话不能证明改进。失败样例进入数据集前去重、脱敏并防止把用户秘密写进仓库。
排查清单
模型忽略规则时,先确认规则实际进入请求、没有被裁剪、角色/顺序符合 API、后续高优先级消息未覆盖;再看冲突和长上下文。JSON 失败先看 finish reason 和截断,再看 Schema 支持。引用错先查检索候选与 source mapping。一次修改修好样例却伤害整体时,回看独立数据集分层。
合格交付应提供模板与变量 Schema、context manifest、输出 Schema、语义验证器、错误分类、重试预算、流事件协议、脱敏 trace 和回归数据,而不是只有一个“效果不错”的 prompt 字符串。