Day68-结构化Prompt设计:XML标签法/Markdown法/JSON Schema

一、为什么 Prompt 必须"结构化"

Prompt 的本质是"自然语言版的接口契约"。如果不结构化,会出现 4 个真实的工程问题:

|-----------|------------------------|------------------|
| 问题 | 表现 | 根因 |
| 解析失败 | 下游解析 JSON / Bean 时格式抖动 | 模型输出的边界不规范 |
| 难以 review | PR 里 5000 字没人看得完 | 没有分块、没有角色标签 |
| 改动难追溯 | 不记得当时为什么这么写 | 改动位置和意图没标记 |
| 多语言模型切换 | GPT-4o 好用换 Claude 翻车 | 不同模型对格式的"理解偏好"不同 |

结论:把 Prompt 当代码写,需要 module、role、constraint 三件套------这正是结构化方法的共同特征。

下面三种方法,分别对应"读起来舒服"、"人类写起来高效"、"机器解析最稳"三个目标。

二、XML 标签法:最稳的结构化方案

XML 标签是 Anthropic 官方推荐的方式(《Claude Prompt Engineering Guide》明确提到 XML 在 Claude 上表现优于 Markdown)。它的核心思想是把 Prompt 切成有名字的分区

2.1 一个完整的 XML Prompt 模板

XML 复制代码
<role>
你是一名资深 Java 性能调优工程师,专精 JVM GC 和 Arthas 诊断。
</role>

<context>
应用的 JVM 参数:-Xms4g -Xmx4g -XX:+UseG1GC
应用的 QPS:2000,平均 RT:50ms
线上出现的问题:每 6 小时一次 Full GC,每次 STW 800ms
</context>

<task>
根据 <context> 提供的信息,分析 Full GC 频繁的根因,并给出可落地的调优方案。
</task>

<constraints>
- 回答必须用中文
- 长度控制在 500 字以内
- 必须按"根因分析 → 调优建议 → 验证步骤"三段式输出
- 不要列超过 5 条建议,每条都要量化收益
</constraints>

<output_format>
## 根因分析
- (3~5 条)

## 调优建议
- 建议 1:(具体参数 + 预期效果)
- 建议 2:...

## 验证步骤
1. ...
</output_format>

为什么用 XML 而不是 Markdown?

  1. 首尾匹配的语义清晰<context>...</context> 一对标签就是一块语义完整的区域,模型不会被同级的标题层级搞混。
  1. 可嵌套<context> 里可以再嵌 <jvm_params><qps>,适合多源信息融合。
  1. Claude 模型显著更稳:Anthropic 官方基准测试中,XML 标签法在 Claude 3.5/4 上的指令遵循率高 8%~15%。

2.2 配合 Spring AI 的 StringTemplate 落地

java 复制代码
// XML 标签法的 Spring AI 落地------用 StringTemplate 把 XML 模板化
// 依赖:spring-boot-starter-ai-openai-spring-boot-starter 1.0.0+
@Component
public class XmlPromptJVMAdvisor {

    private final ChatClient chatClient;

    // 推荐:把 XML 模板写在独立的 .st 文件里,这里为演示内联在代码中
    private static final String XML_TEMPLATE = """
        <role>
        你是一名资深 Java 性能调优工程师,专精 JVM GC 和 Arthas 诊断。
        </role>

        <context>
        JVM 参数:{jvmArgs}
        QPS:{qps},平均 RT:{rtMs}ms
        现象:{symptom}
        </context>

        <task>
        根据 <context> 给出 GC 调优方案,要求可落地。
        </task>

        <constraints>
        - 中文输出,长度 ≤ 500 字
        - 按"根因 → 建议 → 验证"三段式
        - 建议不超过 5 条,每条量化收益
        </constraints>
        """;

    public XmlPromptJVMAdvisor(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public String advise(String jvmArgs, int qps, int rtMs, String symptom) {
        String prompt = XML_TEMPLATE
                .replace("{jvmArgs}", jvmArgs)
                .replace("{qps}", String.valueOf(qps))
                .replace("{rtMs}", String.valueOf(rtMs))
                .replace("{symptom}", symptom);

        return chatClient.prompt()
                .user(prompt)
                .call()
                .content();
    }
}

老梁踩坑 :模板里如果想强调某个变量,不能用 <b> 标签------模型会原样输出 <b>强调用"重要"二字包一层,或用 <critical> 这种语义化标签,不要用 HTML 的 b/strong/i

三、Markdown 法:人类阅读体验最好

Markdown 法是 LLM 训练数据里最常见的格式(GitHub、知乎、技术博客全是 Markdown),模型对它天然友好,适合给人类同事看的 Prompt 文档

3.1 一个完整的 Markdown Prompt

java 复制代码
````markdown
# 角色
你是一名 MySQL DBA,熟悉 InnoDB 锁机制。

# 背景信息
- 数据库版本:MySQL 8.0.32
- 表结构:`orders(id, user_id, status, created_at)`,无索引
- 当前 SQL:
  ```sql
  SELECT * FROM orders WHERE user_id = 123 AND status = 'PAID';
````

任务

分析这条 SQL 的性能问题,给出索引优化方案。

输出要求

  1. 列出 2~3 个根因
  1. 给出完整的 DDL 语句
  1. 用 EXPLAIN 验证优化后的执行计划
  1. 输出长度 ≤ 300 字
java 复制代码
**Markdown 优势**:

- **代码块天然支持**:用 ` ```sql ` 包 SQL,模型不会"误读"为自然语言。
- **层级清晰**:# / ## / ### 对应"角色→任务→细节"的认知层级。
- **写起来快**:VSCode、Jupyter、`resources/prompts/*.md` 文件都好管理。

### 3.2 配合 Spring AI 资源文件管理

把模板外置到 `src/main/resources/prompts/order_optimizer.md`,代码里读文件渲染:

//java
// Markdown 模板 + Spring AI 的标准用法
// 文件位置:src/main/resources/prompts/order_optimizer.md
@Component
public class MarkdownPromptSqlAdvisor {

    private final ChatClient chatClient;
    private final Resource templateResource;

    public MarkdownPromptSqlAdvisor(
            ChatClient.Builder builder,
            @Value("classpath:prompts/order_optimizer.md") Resource templateResource) {
        this.chatClient = builder.build();
        this.templateResource = templateResource;
    }

    public String advise(String mysqlVersion, String tableSchema, String sql) {
        // Spring AI 的 PromptTemplate 自动加载 .md 文件,{var} 占位符替换
        PromptTemplate template = new PromptTemplate(templateResource);
        Prompt prompt = template.create(Map.of(
                "mysqlVersion", mysqlVersion,
                "tableSchema", tableSchema,
                "sql", sql
        ));

        return chatClient.prompt(prompt).call().content();
    }
}

对应的 order_optimizer.md 内容(用 {``{var}} 占位):

java 复制代码
# 角色
你是一名 MySQL {{mysqlVersion}} DBA,熟悉 InnoDB 锁机制。

# 背景信息
表结构:`{{tableSchema}}`
当前 SQL:
```sql
{{sql}}

任务

分析这条 SQL 的性能问题,给出索引优化方案。

输出要求

  1. 2~3 个根因
  1. 完整 DDL
  1. EXPLAIN 验证计划对比
  1. 长度 ≤ 300 字
java 复制代码
**Markdown vs XML 怎么选?**

- **给模型看 + 强约束输出** → XML(标签首尾匹配,模型不易漏字段)
- **给同事 review + 跨模型兼容** → Markdown(人人能读,所有模型都认)
- **多个变量 + 需要代码块** → Markdown(XML 包代码块需要 CDATA,麻烦)
- **Claude 模型 + 多层语义嵌套** → XML(Claude 的最优解)

## 四、JSON Schema 法:机器解析最稳

当 Prompt 的输出要被下游系统**直接消费**(入库、渲染、写代码)时,必须用 JSON Schema 约束输出。这是结构化输出的"终极方案"。

Spring AI 提供了 `BeanOutputConverter` 和 `ListOutputConverter`,把 Java Bean 直接作为输出契约:

### 4.1 定义强类型的输出契约

```java
// 用 Java Bean 直接定义大模型的输出契约
// 依赖:spring-boot-starter-ai-openai-spring-boot-starter 1.0.0+
public record SqlOptimizationReport(
        List<String> rootCauses,           // 根因列表
        List<String> ddlStatements,        // DDL 语句列表
        String explainExpectedPlan,        // 优化后 EXPLAIN 预期
        List<String> validationSteps      // 验证步骤
) {}

4.2 用 BeanOutputConverter 一行搞定

java 复制代码
// Spring AI 的 BeanOutputConverter 自动把 Bean 描述转成 JSON Schema
@Component
public class JsonSchemaSqlAdvisor {

    private final ChatClient chatClient;
    private final BeanOutputConverter<SqlOptimizationReport> converter;

    public JsonSchemaSqlAdvisor(ChatClient.Builder builder) {
        // 关键:把转换器声明出来,Spring AI 会自动生成 JSON Schema
        this.converter = new BeanOutputConverter<>(SqlOptimizationReport.class);
        this.chatClient = builder.build();
    }

    public SqlOptimizationReport advise(String sql, String tableSchema) {
        // 把 JSON Schema 注入 system 消息,告诉模型必须按这个格式输出
        String systemPrompt = """
                你是 MySQL 8.0 性能优化专家。
                %s
                """.formatted(this.converter.getFormat());

        String userPrompt = """
                表结构:%s
                待优化 SQL:%s
                """.formatted(tableSchema, sql);

        // chatClient 返回的是 String,需要 converter 转成 Bean
        String rawJson = chatClient.prompt()
                .system(systemPrompt)
                .user(userPrompt)
                .call()
                .content();

        // 直接反序列化为强类型 Bean,下游代码无 if-else
        return this.converter.convert(rawJson);
    }
}

converter.getFormat() 会自动生成类似下面的指令注入到 system 消息里:

java 复制代码
Your response should be a JSON object with the following structure:
{
  "rootCauses": ["string"],
  "ddlStatements": ["string"],
  "explainExpectedPlan": "string",
  "validationSteps": ["string"]
}

4.3 列表场景用 ListOutputConverter

如果只要一个字符串列表,更轻量:

java 复制代码
// ListOutputConverter 用于"输出必须是 List<String>"的简单场景
@Component
public class BugRootCauseExtractor {

    private final ChatClient chatClient;
    private final ListOutputConverter converter;

    public BugRootCauseExtractor(ChatClient.Builder builder) {
        this.converter = new ListOutputConverter(new DefaultConverterService());
        this.chatClient = builder.build();
    }

    public List<String> extract(String stackTrace) {
        String systemPrompt = """
                你是 Java 异常分析专家。请从堆栈中提取 N 个独立根因。
                %s
                """.formatted(converter.getFormat());

        return (List<String>) converter.convert(chatClient.prompt()
                .system(systemPrompt)
                .user("堆栈:%s".formatted(stackTrace))
                .call()
                .content());
    }
}

JSON Schema 的两条血泪经验

  1. Bean 的字段加 JSR-380 注解@NotBlank@Size(max=200) 不会自动约束大模型,但能让 Bean 校验失败时立刻抛错,比让模型给你一个空字符串好排查得多。
  1. 大模型 100% 不会严格遵循 JSON Schema :实测 GPT-4o 的 JSON 格式正确率约 95%、Qwen 约 88%。下游必须 有容错(@JsonIgnoreProperties(ignoreUnknown = true) + 字段默认值),不能假设模型给的就是合规 JSON。

五、三种结构化方案对比

|--------------|-------------------|---------------------------|----------------------------|
| 维度 | XML 标签法 | Markdown 法 | JSON Schema 法 |
| 人类可读 | 中 | | 差 |
| 模型可读 | 优(Claude 首选) | 优 | 中 |
| 机器解析 | 中(需 regex 提取) | 中 | 优(强类型映射) |
| 多变量模板 | 中 | | 中 |
| 嵌套语义 | | 良 | 良 |
| 跨模型兼容 | 良 | | 优 |
| 适用输出 | 长文本、结构化文本 | 长文本、代码块 | 强类型数据 |
| Spring AI 支持 | 手动 StringTemplate | PromptTemplate + Resource | BeanOutputConverter 原生 |

老梁的项目用法 :团队协作的场景统一用 Markdown ;调用 Claude API 的场景叠一层 XML 标签 ;输出要入库的场景用 JSON Schema。三者不是互斥,是组合拳。

六、建议

  1. 团队统一一套结构化规范

    别让团队里有的同事写 XML、有的写 Markdown、有的写大段散文。建议制定一个 30 行以内的"团队 Prompt 规范"塞进 wiki------只规定「角色 / 背景 / 任务 / 约束 / 输出」五个分区,强制每条 Prompt 都按这个分区写。可以参考我团队的简化版:

    java 复制代码
    [角色] 一句话定义专家身份
    [背景] 关键参数、上下文
    [任务] 用动词开头的一句话目标
    [约束] 长度、格式、语言
    [输出] Markdown / JSON / 文本

    写完后强制走 PR review,Prompt 跟代码一样要 review

  2. JSON Schema 输出必须有兜底解析

    即使 95% 准确率,在 100 万次调用里也有 5 万次格式错误。建议用 Spring AI 的 BeanOutputConverter + Jackson 容错配置:

    java 复制代码
    ObjectMapper mapper = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);

    或者外加一层"AI 输出→Java Bean"的包装,把解析失败统一转成 Optional.empty() 抛给上层重试。

  3. 跨模型切换要做 Prompt 兼容性测试集

    我团队的踩坑:同一份 Prompt 从 DeepSeek 切到 GPT-4o 后,JSON 格式正确率从 92% 跳到 97%(好),但 XML 标签下的语义遵循率从 95% 跌到 88%(差,因为 GPT-4o 对 XML 不如 Claude 敏感)。结论:每个模型至少备 50 个标注样本做格式回归测试,别凭感觉切。


结构化 Prompt 不是为了好看,是为了让你半夜被叫起来排查时,能 30 秒看清这份 Prompt 在干什么。

下篇预告 :Prompt 写出来不是结束------你在生产里会改它 A/B、把它回滚、用 Git 管它。下一篇我们聊 《Prompt 调试与版本管理:像管理代码一样管理 Prompt》,教你用 Langfuse + Git 做 Prompt 的灰度发布和回滚。

往期回顾:

相关推荐
LayZhangStrive2 小时前
Prompt - 如何生成贴合我们业务需求的有效prompt
ai·prompt·agent·提示词
小白羊丨6 小时前
为什么微调而不是大模型 + Prompt?正确率如何得到,是否过拟合?
大数据·人工智能·prompt
hasty15 小时前
从 Prompt Injection 到越界写文件:Theia Agent Mode 的信任边界为何失效
安全·prompt
“AI国潮设计-小江”20 小时前
Python实战 | SDXL精准控制“普宁英歌舞×星空蛋糕”IP落地,附核心Prompt与商用授权思路
开发语言·人工智能·python·prompt·aigc
阡陌数智1 天前
多模型兼容Prompt工程体系:统一结构化指令如何提升全模型输出稳定性
prompt
AI 思录1 天前
Prompt 事故档案(五):AI 道歉,还是“道德漂白”?
人工智能·安全·prompt·用户体验·ai安全·ai幻觉
长空任鸟飞_阿康2 天前
三、《从零手撸 Agent》 · system prompt 与核心参数:调好你的旋钮
人工智能·python·ai·prompt
长谷深风1112 天前
AI记忆会过期,会冲突,更需要治理
人工智能·ai·大模型·prompt·memory·aiagent
LlmCraft|大模型工程实践2 天前
09 大语言模型(LLM)演进与 Prompt 入门
人工智能·语言模型·prompt