导读
你有没有遇到过这样的问题:调用大模型接口,期望返回一个干净的 JSON,结果模型"放飞自我",在 JSON 前面加了一段解释文字,外面还裹了一层 Markdown 代码块,字段名大小写乱改,空值填了个 "N/A"......下游系统直接解析失败,线上告警一片红。
本文将围绕两个核心主题展开:
- 结构化 Prompt ------ 通过精心设计的提示词约束,让模型稳定输出你指定的格式(JSON、XML 等),彻底解决"格式飘忽不定"的痛点。
- Meta Prompt(元提示词) ------ 让 AI 帮你写 Prompt,用"套娃"的方式批量生成、优化、评估提示词,大幅提升开发效率。
无论你是后端开发工程师、AI 应用开发者,还是对 Prompt Engineering 感兴趣的技术人,这篇文章都能给你带来可落地的实战技巧。
一、为什么模型输出的格式会"飘"?
在上下游系统交互中,数据格式的一致性是基本要求。你的后端服务期望接收一个标准的 JSON 对象,但大模型本质上是一个"文本生成器",它并不天然理解"只输出 JSON"这件事。
1.1 常见的"翻车"场景
| 问题 | 示例 |
|---|---|
| JSON 外面裹了 Markdown 代码块 | json { ... } |
| JSON 前面加了解释性文字 | 以下是分析结果:{ ... } |
| 字段名大小写被改了 | 期望 userName,输出 username |
| 空值填了 "N/A" 而非空字符串 | "address": "N/A" |
| 空数组被替换成 null | "tags": null 而非 "tags": [] |
这些问题的根源在于:Prompt 中缺少明确的约束,模型就会自由发挥。 就好比你让实习生帮你整理数据,如果你只说"整理一下",每个人整理出来的格式都不一样;但如果你给他一个明确的模板和填写规则,结果就会稳定得多。
1.2 Prompt 稳定性 > API 稳定性
很多同学会依赖框架层面的能力(比如 Spring AI 的 BeanOutputConverter)来实现 JSON 序列化。这当然很方便,但需要注意:BeanOutputConverter 本质上是在 Prompt 里自动附加了一个 JSON Schema 需求,模型并不会 100% 服从。
更关键的是,不是所有模型都支持 JSON 格式输出。因此,Prompt 层面的约束才是最可靠的基础保障,API 层面的工具只是锦上添花。
二、结构化 Prompt 的六大关键技巧
2.1 明确告诉模型"只输出 JSON"
这是最基本也是最重要的一条。你需要用非常明确的语言告诉模型。看一下我们项目中 StableStructuredOutputController 的真实 System Prompt 写法:
// com.jichi.prompt.controller.StableStructuredOutputController
private static final String SYSTEM = """
你是一个用户评论分析专家。
分析评论的情感倾向、优缺点和整体评分。
输出规则(严格遵守):
- 只输出合法的 JSON,第一个字符必须是 {,最后一个字符必须是 }
- 禁止在 JSON 前后添加任何文字、解释或 Markdown 代码块
- sentiment 只能是 POSITIVE / NEGATIVE / MIXED / NEUTRAL 之一
- 列表为空时填 [],不要填 null
- overallScore 范围 1-10 的整数
""";
很多人只写了"请以 JSON 格式输出",这远远不够。你需要把所有可能的"越界行为"都明确列出来并禁止。
2.2 对字段处理规则做显式说明
模型很"聪明",如果你不告诉它怎么处理特殊情况,它就会"自作主张"去猜测和推断。看看我们合同信息提取服务 ContractExtractionService 中的字段处理规则:
// com.jichi.prompt.service.ContractExtractionService
private static final String SYSTEM = """
你是一个合同信息提取专家,有 10 年法律文件分析经验。
提取规则:
1. 只提取文本中明确表述的信息,不推断不猜测
2. 日期统一转为 YYYY-MM-DD 格式;"永久"或"无限期"填 null
3. 金额提取数字部分(去掉"元"、"万元"等单位,但保留单位到 currency 字段)
例:"人民币壹拾万元整" → amount=100000, currency="CNY"
4. 如果某个字段在文本中没有明确表述,填 null
5. warnings 字段:记录提取过程中遇到的歧义或注意事项
""";
这就像给模型一份"数据字典",每个字段怎么填、边界情况怎么处理,都写得明明白白。
2.3 用"禁止"代替"不要"
这是一个非常实用的小技巧。在 Prompt 中,"禁止"的约束力远强于"不要"。原因很直觉------"禁止"这个词的语气更强硬、更绝对。
# 效果较弱
不要在 JSON 前面加解释性文字。
# 效果更强(我们项目中的真实写法)
禁止在 JSON 前后添加任何文字、解释或 Markdown 代码块
注意我们在 StableStructuredOutputController 中还用了具体的格式锚点------"第一个字符必须是 {,最后一个字符必须是 }",进一步收紧了约束边界。
2.4 将 Temperature 设置为 0
当你的任务是结构化输出(格式固定、不需要创意)时,把 temperature 参数设为 0。看看项目中的实际用法:
// com.jichi.prompt.controller.StableStructuredOutputController#analyzeReview
DashScopeChatOptions options = DashScopeChatOptions.builder()
.withTemperature(0.0)
.build();
类比:temperature 就像是模型的"想象力开关"。写诗的时候需要打开,填表格的时候必须关掉。
2.5 格式约束放 System Prompt,而非 User Prompt
System Prompt 相当于模型的"大脑",是全局性的指令;User Prompt 是每次对话的具体输入。格式约束这种"铁律",应该放在 System Prompt 中。看看我们客服配置 CustomerServiceConfig 中的实战写法:
// com.jichi.prompt.config.CustomerServiceConfig
@Bean
public ChatClient customerAiServiceClient(DashScopeChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("""
【角色】
你是"鸡翅 AI"电商平台的智能客服助手小鸡,专注于售前咨询和售后服务。
【任务】
- 解答用户关于商品、订单、物流、退款的疑问
- 引导用户完成购买决策
- 收集用户反馈
【约束】
- 只回答与鸡翅 AI 平台和商品相关的问题
- 不确定的信息直接告知用户"我需要帮您核实一下",不要编造
- 涉及退款、投诉等复杂问题,主动提出转人工
- 不评价竞争对手产品
【格式】
- 回复简洁,不超过 150 字
- 语气亲切友好,称呼用户为"您"
- 每条回复末尾可以追问用户是否还有其他问题
【示例】
用户:这个手机壳适合 iPhone 15 Pro 吗?
小极:您好!这款手机壳专为 iPhone 15 Pro 设计,完美贴合各按键和接口位置。
支持 MagSafe 磁吸充电,不影响无线充电。需要了解其他型号的适配情况吗?
""")
.build();
}
System Prompt 的约束力天然高于 User Prompt,这是模型架构决定的。注意这个配置类使用了 Spring AI 的 ChatClient.builder + defaultSystem,所有通过这个 ChatClient 发出的请求都会自动带上这段 System Prompt。
2.6 手动提供 JSON Schema 示例
当字段比较复杂时,与其用自然语言描述,不如直接给模型一个结构化的实体定义。在我们项目中,合同提取的输出实体定义如下:
// com.jichi.prompt.entity.ContractInfo
public record ContractInfo(
List<Party> parties,
String contractNumber,
String signDate,
String effectiveDate,
String expiryDate,
ContractValue value,
List<String> keyObligations,
List<String> terminationConditions,
String confidentialityClause, // STRICT/GENERAL/NONE
List<String> warnings
) {}
在 Service 层,我们通过 BeanOutputConverter 自动将这个 record 转换为 JSON Schema 追加到 Prompt 中:
private final BeanOutputConverter<ContractInfo> converter;
public ContractInfo extract(String contractText) {
String raw = chatModel.call(new Prompt(
List.of(
new SystemMessage(SYSTEM + "\n\n" + converter.getFormat()),
new UserMessage("提取以下合同的关键信息:\n\n" + contractText)
)
)).getResult().getOutput().getText();
return converter.convert(raw);
}
converter.getFormat() 会自动生成 JSON Schema 描述追加到 System Prompt 末尾,比手写模板更可靠,字段类型、枚举值都能显式描述。
三、兜底方案:应对模型的"偶发抽风"
即使你的 Prompt 写得再好,也要为最坏情况做准备。生产环境中,模型偶尔还是会输出带 Markdown 代码块的 JSON,这时就需要一个清洗函数做兜底:
public static String cleanJsonOutput(String raw) {
if (raw == null) return null;
// 去除 Markdown 代码块标记
raw = raw.replaceAll("```json\\s*", "")
.replaceAll("```\\s*", "")
.trim();
// 提取第一个 { 到最后一个 } 之间的内容
int start = raw.indexOf('{');
int end = raw.lastIndexOf('}');
if (start >= 0 && end > start) {
return raw.substring(start, end + 1);
}
return raw;
}
实战经验:在项目中加上 Prompt 约束 +
BeanOutputConverter+ 兜底清洗方案后,JSON 解析异常的问题就再也没有出现过。
四、完整实战示例:评论分析服务
下面用一个完整的例子把前面讲的技巧串起来。这是项目中真实的评论分析接口,输入一条评论文本,输出结构化的情感分析结果。
4.1 定义实体类
// com.jichi.prompt.entity.ReviewAnalysis
public record ReviewAnalysis(
String sentiment, // POSITIVE / NEGATIVE / MIXED / NEUTRAL
List<String> positiveAspects,
List<String> negativeAspects,
int overallScore, // 1-10
String summary
) {}
注意我们使用了 Java 的 record 类型,字段不可变,天然适合做数据传输对象。Spring AI 的 BeanOutputConverter 可以直接解析 record。
4.2 编写 Controller
// com.jichi.prompt.controller.StableStructuredOutputController
@RestController
@RequestMapping("/structured")
public class StableStructuredOutputController {
private static final String SYSTEM = """
你是一个用户评论分析专家。
分析评论的情感倾向、优缺点和整体评分。
输出规则(严格遵守):
- 只输出合法的 JSON,第一个字符必须是 {,最后一个字符必须是 }
- 禁止在 JSON 前后添加任何文字、解释或 Markdown 代码块
- sentiment 只能是 POSITIVE / NEGATIVE / MIXED / NEUTRAL 之一
- 列表为空时填 [],不要填 null
- overallScore 范围 1-10 的整数
""";
private final DashScopeChatModel chatModel;
private final BeanOutputConverter<ReviewAnalysis> converter;
public StableStructuredOutputController(DashScopeChatModel chatModel) {
this.chatModel = chatModel;
this.converter = new BeanOutputConverter<>(ReviewAnalysis.class);
}
@PostMapping("/analyze-review")
public ReviewAnalysis analyzeReview(@RequestBody String review) {
DashScopeChatOptions options = DashScopeChatOptions.builder()
.withTemperature(0.0)
.build();
String raw = chatModel.call(new Prompt(
List.of(
new SystemMessage(SYSTEM + "\n\n" + converter.getFormat()),
new UserMessage("分析这条评论:" + review)
),
options
)).getResult().getOutput().getText();
return converter.convert(raw);
}
}
这段代码完整体现了前面讲的所有技巧:System Prompt 约束格式规则、Temperature 设为 0、BeanOutputConverter 自动追加 JSON Schema、最终通过 converter.convert() 反序列化。
输入 "产品质量很好,但物流太慢了,等了快两周" 后,模型稳定输出:
{
"sentiment": "MIXED",
"positiveAspects": ["产品质量很好"],
"negativeAspects": ["物流太慢,等了快两周"],
"overallScore": 6,
"summary": "产品质量受好评,但物流体验较差"
}
五、Meta Prompt:让 AI 帮你写 Prompt
5.1 什么是 Meta Prompt?
Meta Prompt(元提示词),就是用来生成其他 Prompt 的 Prompt。
听起来有点"套娃"?确实如此。流程是这样的:
你告诉 AI 一个任务场景
→ AI 生成一个高质量的 System Prompt
→ 你用这个 Prompt 让 AI 完成实际任务
打个比方:你不直接写考试题,而是先写一个"出题规范",让助教按照规范去出题。Meta Prompt 就是那个"出题规范"。
5.2 为什么需要 Meta Prompt?
在实际项目开发中,需要写 Prompt 的场景非常多:
- 批量生成业务 Prompt:一个项目可能有 20 个功能,每个都需要 System Prompt,手写费时费力。
- 优化已有 Prompt:现有的 Prompt 效果不理想,但不知道从哪下手改,让 AI 帮你分析问题并重写。
- 多语言适配:同一个功能要支持中文、英文、日文等,每种语言的 Prompt 表达风格完全不同,用 Meta Prompt 批量生成。
- SaaS 多租户场景:不同租户需要不同的 AI 助手人设和能力边界,通过 Meta Prompt 动态生成定制化 Prompt。
5.3 Meta Prompt 的实战代码
场景一:生成 Prompt
我们项目中的 MetaPromptController 实现了两个核心功能------生成 Prompt 和优化 Prompt。先看生成部分:
// com.jichi.prompt.controller.MetaPromptController
@RestController
@RequestMapping("/meta-prompt")
public class MetaPromptController {
private static final String META_PROMPT = """
你是一个 Prompt 工程师,专门为 AI 应用设计高质量的 System Prompt。
用户会描述一个业务场景,你需要生成一个完整的 System Prompt,要求:
1. 包含明确的角色定位
2. 包含清晰的任务边界
3. 包含必要的行为约束
4. 包含输出格式要求(如果有需要)
5. 语言简洁,避免冗余
6. 针对业务场景加入具体的示例(如果有助于理解)
直接输出生成的 System Prompt,不要有任何解释或前缀。
""";
private final DashScopeChatModel chatModel;
@GetMapping("/generate")
public String generatePrompt(@RequestParam String scenario) {
return chatModel.call(new Prompt(
List.of(
new SystemMessage(META_PROMPT),
new UserMessage(
"请为以下场景生成 System Prompt:\n\n" + scenario)
)
)).getResult().getOutput().getText();
}
}
调用示例:GET /meta-prompt/generate?scenario=企业内部知识库问答助手,回答员工关于公司制度、流程、福利等方面的问题
AI 生成的结果:
你是一个企业内部知识库问答助手,专门回答员工关于公司制度、
行政流程、薪酬福利、考勤规范等方面的问题。
【能力范围】
- 公司规章制度解读
- 行政审批流程指引
- 薪酬福利政策说明
- 考勤与休假规则
【约束】
- 如果问题超出上述范围,明确告知"该问题不在我的服务范围内,
建议联系 XX 部门"
- 禁止提供法律建议或涉及具体薪资数字
- 回答要简洁明了,不超过 200 字
这个生成结果已经相当不错,基本可以直接使用,只需根据实际业务做少量微调。
场景二:优化 Prompt
同一个 Controller 中还提供了 Prompt 优化接口。注意它使用了结构化输出------优化结果会被解析为 PromptOptimizationResult 对象:
// com.jichi.prompt.controller.MetaPromptController
private static final String OPTIMIZE_META_PROMPT = """
你是一个 Prompt 优化专家。用户会提供:
1. 当前 Prompt(当前版本)
2. 期望的任务和输出
3. 现在存在的问题
你需要:
1. 分析当前 Prompt 存在的问题(逐条说明)
2. 给出优化后的 Prompt
3. 解释每处改动的原因
""";
public record PromptOptimizationRequest(
String currentPrompt,
String expectedTask,
String issues
) {}
public record PromptOptimizationResult(
List<String> issues,
String optimizedPrompt,
List<String> changes
) {}
@PostMapping("/optimize")
public PromptOptimizationResult optimizePrompt(@RequestBody PromptOptimizationRequest req) {
BeanOutputConverter<PromptOptimizationResult> converter =
new BeanOutputConverter<>(PromptOptimizationResult.class);
String userContent = String.format("""
当前 Prompt:
%s
期望的任务:
%s
存在的问题:
%s
%s
""", req.currentPrompt(), req.expectedTask(), req.issues(),
converter.getFormat());
String raw = chatModel.call(new Prompt(
List.of(
new SystemMessage(OPTIMIZE_META_PROMPT),
new UserMessage(userContent)
)
)).getResult().getOutput().getText();
return converter.convert(raw);
}
AI 的优化结果会被结构化为三部分:
- issues:当前 Prompt 存在的问题列表
- optimizedPrompt:优化后的完整 Prompt
- changes:逐条解释每处修改的目的
5.4 进阶:SaaS 多租户动态生成
在 SaaS 产品中,不同租户可能需要不同风格的 AI 助手。我们项目中定义了一个 TenantConfig 配置类:
// com.jichi.prompt.entity.TenantConfig
public record TenantConfig(
String tenantId,
String companyName,
String businessType,
List<String> capabilities,
List<String> restrictions,
String tone,
String specialRequirements
) {}
然后通过 TenantPromptService 利用 Meta Prompt 动态生成定制化的 System Prompt:
// com.jichi.prompt.service.TenantPromptService
@Service
public class TenantPromptService {
private static final String META_SYSTEM = """
你是 Prompt 生成引擎,根据客户配置生成定制化的 AI 助手 System Prompt。
生成的 Prompt 必须:
1. 准确反映客户的业务场景
2. 包含必要的约束和边界
3. 风格与客户的品牌调性一致
直接输出 System Prompt,不要其他内容。
""";
private final DashScopeChatModel chatModel;
public String generateTenantPrompt(TenantConfig config) {
String request = String.format("""
生成一个 AI 助手的 System Prompt,配置如下:
公司名称:%s
业务类型:%s
助手功能:%s
不能处理的问题:%s
语言风格:%s
特殊要求:%s
""",
config.companyName(),
config.businessType(),
String.join("、", config.capabilities()),
String.join("、", config.restrictions()),
config.tone(),
config.specialRequirements());
// 实际项目里应该缓存到数据库,不用每次重新生成
return chatModel.call(new Prompt(
List.of(new SystemMessage(META_SYSTEM), new UserMessage(request))
)).getResult().getOutput().getText();
}
}
Controller 层只需一行转发:
// com.jichi.prompt.controller.TenantPromptController
@RestController
@RequestMapping("/tenant-prompt")
public class TenantPromptController {
private final TenantPromptService tenantPromptService;
@PostMapping("/generate")
public String generateTenantPrompt(@RequestBody TenantConfig config) {
return tenantPromptService.generateTenantPrompt(config);
}
}
比如传入"极智商城"的配置后,AI 就能自动生成一个带有该商城品牌调性、业务范围约束、特殊规则(如"遇到投诉必须转接人工客服")的完整 System Prompt。
这种模式在很多 AI 平台创建智能体时都在使用------你填写智能体的名称和描述,平台就帮你生成 System Prompt,背后就是 Meta Prompt 在工作。
5.5 再进一步:用 AI 评估 Prompt 质量
生成了 Prompt,怎么知道质量好不好?我们项目中专门实现了一个 MetaPromptEvaluationController 来做这件事:
// com.jichi.prompt.controller.MetaPromptEvaluationController
@RestController
@RequestMapping("/meta-prompt-eval")
public class MetaPromptEvaluationController {
private static final String EVALUATION_META_PROMPT = """
你是一个 Prompt 质量评估专家。
对给定的 Prompt 从以下维度打分(1-10)并给出改进建议:
1. 清晰度:任务目标是否清晰无歧义
2. 完整性:是否涵盖必要的角色、任务、约束、格式
3. 安全性:是否有足够的约束防止越界行为
4. 简洁性:是否有冗余内容
5. 可操作性:模型是否能明确知道该怎么做
""";
private final DashScopeChatModel chatModel;
private final BeanOutputConverter<PromptEvaluation> converter;
@PostMapping("/evaluate")
public PromptEvaluation evaluatePrompt(@RequestBody String prompt) {
String userContent = "请评估以下 Prompt:\n\n" + prompt + "\n\n" + converter.getFormat();
String raw = chatModel.call(new Prompt(
List.of(
new SystemMessage(EVALUATION_META_PROMPT),
new UserMessage(userContent)
)
)).getResult().getOutput().getText();
return converter.convert(raw);
}
}
评估结果的实体类 PromptEvaluation 定义如下:
// com.jichi.prompt.entity.PromptEvaluation
public record PromptEvaluation(
Map<String, Integer> scores,
int overallScore,
List<String> strengths,
List<String> improvements
) {}
对于一个简单的 "你是一个助手,帮助用户回答问题" 这样的 Prompt,AI 的评估结果可能是:
| 维度 | 得分 | 说明 |
|---|---|---|
| 清晰度 | 4 | 角色定义模糊,未指定专业领域 |
| 完整性 | 5 | 缺少输出格式、边界情况处理 |
| 安全性 | 6 | 无防注入约束 |
| 简洁性 | 7 | 确实够简洁 |
| 可操作性 | 5 | 缺少具体的执行指引 |
这种"生成 → 评估 → 优化"的闭环,能让你的 Prompt 质量不断迭代提升。
六、Meta Prompt 的注意事项
6.1 AI 生成的 Prompt 只是起点
AI 生成的 Prompt 大约能覆盖 70%-80% 的内容,但人工审查和微调是必不可少的。常见需要人工补充的部分:
- 业务特有的约束条件(AI 不了解你的业务细节)
- 边界情况的处理规则
- 安全相关的约束(防越狱、防注入)
6.2 防止 Prompt 注入
如果你的 SaaS 产品允许用户自定义 Prompt 内容,一定要做严格的输入过滤。否则恶意用户可能通过 Prompt 注入,绕过你设定的安全约束。
6.3 生成结果要缓存
Meta Prompt 生成的结果应该持久化存储到数据库 ,而不是每次请求都重新生成。正如我们 TenantPromptService 代码中的注释所说:// 实际项目里应该缓存到数据库,不用每次重新生成。只有配置变更时才触发重新生成,既节省 Token 开销,也保证响应速度。
七、总结
本文覆盖了两个提升 AI 应用工程质量的核心技术:
结构化 Prompt 六大技巧:
- 明确告诉模型"只输出 JSON",列出所有禁止行为
- 对字段处理规则做显式说明(空值、日期、金额等)
- 用"禁止"替代"不要",并设定格式锚点
- 结构化任务将 Temperature 设为 0
- 格式约束放 System Prompt,具体输入放 User Prompt
- 复杂字段用
BeanOutputConverter自动生成 JSON Schema
Meta Prompt 四大应用场景:
- 批量生成业务 Prompt(
MetaPromptController#generatePrompt) - 分析并优化已有 Prompt(
MetaPromptController#optimizePrompt) - SaaS 多租户场景动态生成定制化 Prompt(
TenantPromptService#generateTenantPrompt) - 用 AI 评估 Prompt 质量,形成迭代闭环(
MetaPromptEvaluationController#evaluatePrompt)
记住一个核心原则:Prompt 的约束力是整个 AI 应用稳定性的基础。 与其在代码层面各种 try-catch 打补丁,不如在 Prompt 层面把规矩立好。先在源头把问题解决掉,再加一层兜底方案,这才是生产级 AI 应用的正确姿势。