Prompt学习三:从角色提示到结构化接口

前两篇我分别在讲两件事:第一,Prompt 不是聊天技巧,而是结构化输入设计;第二,想让模型真正接进系统,就必须让输出更稳定。但继续往下学,很快会遇到第三个问题:如果目标不只是“答得差不多”,而是“行为边界清楚、输出结构稳定、结果能被程序消费”,Prompt 应该怎么继续往前走?

这也是我学习 system prompt、角色结构、Function Calling(工具调用)和 JSON Schema 之后最大的认知变化:到了这一步,Prompt 已经越来越不像“提问方式”,而更像一层写给模型看的接口定义

从“说清楚”到“约束清楚”

如果说第一篇解决的是“怎么把任务说清楚”,第二篇解决的是“怎么让结果更稳定”,那这一篇真正解决的是:

怎样让模型不只是看起来懂了,而是按你定义的边界来工作。

这一点在工程里非常关键。

因为很多 AI 应用并不是卡在模型完全不会,而是卡在下面这些更常见的问题上:

  • system prompt 写了,但只是空泛角色扮演
  • 用户任务和系统规则混在一起,边界不清
  • 输出偶尔是 JSON,偶尔又夹杂解释文本
  • 字段名字差不多,但结构并不稳定
  • 人能看懂,程序却没法可靠接入

也就是说,继续往前学 Prompt,重点已经不再是“怎么写得更像人”,而是“怎么把行为约束和输出契约定义清楚”。

system / user / assistant 不是聊天标签,而是输入分层

刚接触聊天模型时,很容易把 systemuserassistant 理解成对话界面上的角色标签。但从工程角度看,它们更像是三层不同职责的输入:

  • system:定义整体行为边界
  • user:提供当前任务和输入内容
  • assistant:承载历史回答,或者作为 few-shot 示例的一部分

这三层拆开之后,一个很重要的变化是:你开始知道什么信息应该长期稳定存在,什么信息只属于当前请求,什么内容只是给模型看的示范。

所以 system prompt 真正的作用,并不是让模型“扮演一个专家”这么简单,而是给模型设定稳定的工作边界。比如:

  • 回答时是否允许扩写
  • 信息不足时是否必须明确说不知道
  • 输出是否必须符合某种固定结构
  • 是否只能依据提供的上下文作答

当这些边界不清楚时,模型其实是在替你做决定;而一旦模型开始替你做决定,下游系统的不确定性也会一起放大。

这时候,Prompt 开始像接口设计

学到这里,我对 Prompt 的理解又往前推进了一步。

之前我更愿意把 Prompt 看成一种结构化输入;现在我会更进一步,把它看成一种给模型看的接口说明

这个接口里通常包含四类信息:

  1. 这个模型要完成什么任务
  2. 它可以基于哪些上下文做判断
  3. 它必须遵守哪些边界条件
  4. 它应该按什么结构返回结果

如果换成熟悉的软件工程语言,这其实已经非常像:

  • 方法说明
  • 调用上下文
  • 参数约束
  • 返回协议

Prompt 到这一层之后,已经不只是“让模型听懂”,而是在定义模型和系统之间怎么交互。

JSON Schema 为什么会突然变重要

学到结构化输出和 Function Calling 这一块时,我最大的一个体感是:

JSON Schema 并不是额外知识点,而是把 Prompt 从自然语言推进到工程契约的关键工具。

一开始看 schema,很容易把它当成“多写了一层格式说明”。但如果站在工程角度看,它做的是更本质的事情:

  • 定义字段有哪些
  • 定义字段类型是什么
  • 定义哪些字段必须出现
  • 定义哪些字段只能取固定值
  • 定义是否允许多余字段存在

也就是说,它不是在帮你“美化 JSON”,而是在收紧模型的输出空间。

比如一个信息抽取任务,如果你只是写:

1
请提取文章中的人名、地点和组织名,并输出 JSON。

模型当然有机会输出一个能看的结果,但问题仍然很多:

  • 字段名会不会漂移?
  • 某个字段缺失时怎么办?
  • 是返回字符串,还是数组?
  • 会不会额外加一段解释?

如果继续把结构定义清楚,情况就完全不一样了。你开始告诉模型:

  • people 是数组
  • locations 是数组
  • organizations 是数组
  • 这些字段必须全部出现
  • 不允许额外字段

到这一步,Prompt 的含义就从“请帮我提取一下”变成了“请按这个返回协议交付结果”。

一个很有帮助的类比:配置文件为什么也要有 schema

这次学习里,我顺手还看了一类带 schema 的 CLI 配置系统,这反而让我更容易理解 schema 在 AI 里的意义。

配置文件为什么要有 schema?因为它解决的从来都不是“文件写得漂不漂亮”,而是:

  • 哪些字段合法
  • 字段类型对不对
  • 枚举值是不是在允许范围内
  • 编辑器能不能提示
  • CLI 启动时能不能验证

换句话说,schema 的价值是:把自由文本变成可验证结构

这个思路放到 AI 里几乎是一样的。

当你要求模型返回结构化输出时,本质上也是在做一件事:把模型原本可能高度发散的自然语言结果,收敛成一个可以校验、可以消费、可以接入流程的结构化对象。

所以 schema 不是 AI 特有的新概念,它只是第一次以非常直接的方式进入了 Prompt 学习过程。

Function Calling 让我重新理解“模型会调用工具”这句话

以前看到 Function Calling(工具调用),我会下意识理解成“模型可以调用函数”。但真正把文档看进去之后,会发现这句话有一点容易误导。

更准确的说法应该是:

模型负责判断要不要用工具,以及应该传什么参数;真正执行工具的,仍然是你的程序。

这件事非常关键。

因为一旦这么理解,Function Calling 就不再神秘了。它本质上是在做下面这条链路:

  1. 用户给出自然语言请求
  2. 模型根据 tool 描述和参数 schema,产出调用意图
  3. 你的程序解析 arguments
  4. 真实函数 / API / 数据库查询被执行
  5. 结果再回到模型,或者直接流入业务系统

这意味着,Function Calling 解决的核心问题并不是“让模型更聪明”,而是:

  • 让模型知道有哪些外部能力可用
  • 让模型按约定格式提供调用参数
  • 让系统能稳定接住这些参数并继续执行

一旦理解到这一步,你会发现 tool description、parameter schema、字段约束这些东西,其实都已经属于接口设计范畴了。

Structured Outputs 真正解决的是“结果怎么落地”

如果说 Function Calling(工具调用)更像是在定义“调用接口”,那么 Structured Outputs(结构化输出)更像是在定义“返回接口”。

这个区别很重要。

有些任务,你需要模型决定是否调用工具;但另外一些任务,你只是想让它直接给你一个可消费的最终结构,比如:

  • 实体抽取结果
  • 分类标签
  • 表单填充数据
  • UI 渲染所需对象
  • 后续工作流的输入参数

这时候,如果输出只是“看起来像 JSON”,还远远不够。

真正可用的要求通常是:

  • 字段稳定
  • 类型稳定
  • 缺失规则明确
  • 枚举范围明确
  • 没有额外字段污染

这也是我这块学习里最强烈的一个感受:

结构化输出不是格式美化,而是系统边界的一部分。

当你开始这样理解时,就不会再把“输出 JSON”当成一个小技巧,而会把它看成系统设计中的交付协议。

一个足够说明问题的例子:让模型返回可校验的对象

这一块如果只停留在概念层,确实容易显得有点空。所以我更愿意用一个很小的例子把它讲透。

假设我们要从一段自然语言里提取用户信息,目标不是“总结一下”,而是得到一个后续程序能直接消费的对象。

如果按 Structured Outputs 的思路,约束会更像这样。下面这段不是 JSON Schema 裸结构本身,而是某类 API 在接收结构化输出约束时的配置包装;这里借它来说明 schema 是怎样进入模型调用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"type": "json_schema",
"json_schema": {
"name": "person",
"strict": true,
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "number", "minimum": 0 }
},
"required": ["name", "age"],
"additionalProperties": false
}
}
}

如果输入是:

1
Jane, 54 years old

那我们真正想要的,不是模型自由发挥出一段解释,而是得到类似这样的结果:

1
2
3
4
{
"name": "Jane",
"age": 54
}

这个例子看起来很简单,但它已经把这一块最关键的东西都串起来了:

  • nameage 是明确字段,不允许模型临时改名
  • age 是数字,不是字符串
  • 两个字段都必须出现
  • 不允许多余字段污染结果

一旦你开始这样定义输出,模型返回的内容就不再只是“看着像 JSON”,而是一个可以继续做校验、渲染、入库或传给下游流程的结构化对象。

如果再往前一步接程序,这个链路就会变成:

  1. 模型按 schema 返回 JSON
  2. 程序解析结果
  3. 校验字段是否齐全、类型是否正确
  4. 校验通过后,再进入业务逻辑

所以这类例子真正要说明的,不是 API 长什么样,而是:

从这里开始,Prompt 已经不是单纯在“引导回答”,而是在“定义交付结果”。

返回值如何消费,才是这一块真正落地的地方

只学习“怎么让模型返回 JSON”其实还不够,因为真正的工程问题在下一步:这个 JSON 怎么被程序消费?

这里我对这块内容最重要的理解是:

LLM 输出本质上仍然是外部输入,不能因为它看起来合理,就跳过验证。

这和普通后端开发很像。你不会因为客户端传来的 JSON “像是对的”,就直接把它当成可信对象使用;同样地,也不应该因为模型“通常挺聪明”,就省掉解析和校验。

一个更可靠的链路应该是:

  1. 模型返回结构化结果
  2. 程序解析 JSON
  3. 用 schema 或 validator(校验器)做校验
  4. 校验通过后,再进入业务逻辑

也就是说,Prompt 到这一块,真正该建立的不是“更会写”,而是下面这个完整思维:

Prompt 负责定义输出契约,Schema 负责约束结果边界,Validator 负责保证程序消费安全。

这才是 AI 应用工程里真正稳定的输出链路。

我现在会怎样判断这一类 Prompt 是否已经可用

经过这块学习之后,我会用一组比以前更工程化的问题去验收 Prompt:

  • system prompt 定义的是行为边界,还是只是角色口吻?
  • 当前输入里,规则、上下文、示例、真实任务有没有分层?
  • 输出结构是否已经明确到程序能稳定消费?
  • 字段是否有缺失策略、类型约束和边界定义?
  • 如果模型多返回内容,下游系统会不会出问题?
  • 这个输出是“看起来像对”,还是“真的能接进后续流程”?

如果这些问题答不上来,那这个 Prompt 即使某一次结果不错,也更像 Demo,而不是可以长期维护的工程接口。

结语

第一篇我把 Prompt 理解成结构化表达,第二篇我开始理解怎样收窄输出空间,而到了第三篇,我真正开始意识到:

Prompt 的下一步,不是继续学会几种写法,而是学会定义模型和系统之间的接口。

system prompt 负责定义行为边界,system / user / assistant 这组消息角色负责组织输入分层,schema 负责约束参数和返回值,Function Calling 和 Structured Outputs 则分别把“调用工具”和“交付结果”变成可接入系统的结构。

到这一步,Prompt 学习就已经不只是“怎么和模型说话”,而是在进入 AI 应用开发最核心的一层:如何让模型产出的内容,真正成为系统里可以被校验、被安全消费的一部分。

参考文档

  • Prompt Engineering Guide - 基本概念
  • Prompt Engineering Guide - ChatGPT 提示工程
  • Prompt Engineering Guide - Function Calling
  • JSON Schema 官方文档
  • Understanding JSON Schema
  • OpenAI Function Calling Guide
  • OpenAI Structured Outputs Guide
  • OpenAI Responses vs Chat Completions
  • Zod
  • Pydantic