Agent开发入门:提示词工程

大家好,我是 Ivan。

提示词工程这个名字,第一次听确实有点装。

Prompt Engineering,翻译过来叫提示词工程,好像突然从写业务代码变成搞研究了。说实话,我一开始也觉得,这不就是会不会提问吗?后来项目跑起来以后,我才发现它没这么简单。

尤其是做 Agent 的时候,Prompt 写不好,不只是回答丑一点,而是接口挂、JSON 解析挂、教师端页面挂、学生画像误判,最后后端还要背锅。

我之前做高校学情分析 Agent 的时候,就被这个东西反复折磨。模型回答看起来挺像那么回事,但程序就是吃不进去。那一刻我才意识到,提示词工程不是写几句漂亮话,而是要让模型输出能进入系统流程。

我这次基于 GPT-5.5 这类新一代模型的能力来讲。低于 GPT-5.5 的旧模型,结构化输出、工具调用、上下文处理能力可能会不一样。还有一点要提前说:不同平台的 API 参数名可能不同,别照着一篇文章就直接复制到生产环境。

一、先讲结构化输出,不是最基础,但最容易出事

正常教程一般会先讲什么是 Prompt、Zero-shot、Few-shot、角色提示。

我不按这个顺序来。

因为我在真实项目里最先被坑的,不是模型听不懂问题,而是它看起来答对了,但是程序解析失败。

这个痛苦谁接过后端接口谁知道。

场景很简单:系统输入学生最近 30 天的学习数据,让模型判断学习风险等级,然后返回给后端,后端再给教师端展示。

我最开始写的 Prompt 是这样:

text 复制代码
请分析下面学生是否存在学习风险,并严格返回 JSON,不要输出其他内容。

学生数据:
登录次数:3
作业提交率:52%
测验平均分:58

模型返回:

json 复制代码
{
  "riskLevel": "高风险",
  "reason": "学生登录次数较少,作业提交率偏低,测验成绩不及格。"
}

你看,它是不是挺合理?

但后端直接不认。

因为我后端枚举只认:

text 复制代码
low
medium
high

它给我来了个中文的高风险。

还有更烦的情况:

json 复制代码
{
  "riskLevel": "medium",
  "reason": "该学生存在一定学习风险",
  "suggestion": "建议老师关注"
}
// 以上是根据学生学习行为数据生成的分析结果

最后那行注释一出来,JSON.parse() 直接报错。

很多人以为写一句只返回 JSON 就够了,但这只是软约束。软约束的意思就是:模型大部分时候会听,但它偶尔不听,而你不知道它什么时候不听。

如果你做的是聊天机器人,这还能忍。

如果你做的是 Agent、RAG、自动评分、后台审核、数据抽取,这就不能靠运气。

要稳定,最好把 Prompt 约束和 API 层的结构化输出一起用。

也就是你不仅告诉模型:

text 复制代码
请返回 JSON。

还要告诉接口层:

text 复制代码
字段有哪些;
字段类型是什么;
枚举只能有哪些值;
不能多返回字段;
哪些字段必须存在。

这就是结构化输出。

比如你希望模型必须返回这种格式:

json 复制代码
{
  "userId": "S202604001",
  "riskLevel": "high",
  "reason": "近 30 天登录次数过低,测验平均分低于 60。",
  "suggestion": "建议教师进行一次学习状态确认。"
}

那你就不要只在 Prompt 里写格式示例,最好用 JSON Schema 约束它。

这里还有一个版本坑。很多人以为所有模型都支持结构化输出,但旧模型可能不支持。比如你把新接口参数套到旧模型上,可能会看到类似这种完整报错:

text 复制代码
BadRequestError: 400 Invalid parameter: 'response_format' of type 'json_schema' is not supported with this model.
type: 'invalid_request_error'
param: 'response_format'
code: null

这个报错我见过类似的。StackOverflow 上也有人问过,编号好像是 79 开头,反正搜关键词 json_schema not supported model 基本能找到。

这也是为什么我现在写 Prompt 不会只看文本本身,还会看模型版本、API 能力、SDK 版本和后端解析方式。

提示词工程不是脱离工程环境存在的技巧。

终于把最烦的一块讲完了。这部分我当初真的想直接关电脑。

二、提示词的本质

提示词 Prompt,是用户或系统输入给大语言模型的完整上下文。它可以包含任务说明、背景信息、输入数据、格式要求、示例、限制条件和安全边界。

提示词工程 Prompt Engineering,是通过设计、组织、约束和迭代提示词,使模型输出更接近目标结果的一组方法。

提示词不是代码。提示词不能像传统程序一样保证确定执行。

提示词也不是咒语。提示词不会因为写得玄学就变强。

提示词的作用,是在模型生成结果之前,提供任务边界、语义方向和输出约束。模型会基于这些上下文进行生成,提示词越清楚,输出越稳定;提示词越模糊,输出越容易发散。

结构化提示词通常包含以下部分:

text 复制代码
角色
背景
输入
任务
规则
输出格式
示例
安全边界

系统提示词 System Prompt 或 Instructions,通常用于定义模型的默认行为、身份、语气和不可违反的规则。

用户提示词 User Prompt,通常用于提交具体任务和输入数据。

在 Agent 开发中,提示词经常不是一次性文本,而是由后端模板、用户输入、检索内容、工具返回结果拼接而成。

三、分隔符 vs XML 标签,我项目里更偏向 XML

官方文档里经常推荐把指令放前面,然后用 ###、三个引号,或者类似分隔符把上下文隔开。

比如:

text 复制代码
请总结下面的内容。

###
这里是文章正文
###

这种写法没问题。简单任务很好用,摘要、翻译、分类,基本够了。

但我在复杂 Agent 项目里更常用 XML 标签。

不是因为 XML 高级。它一点都不高级,甚至有点土。但它清楚。

2026 年 4 月,我做过一个高校学情分析系统。项目不大,但数据源很碎:学生画像、课程访问记录、作业记录、测验分数、教师备注、RAG 检索出来的课程资料,全都要塞给 Agent。

当时我用普通编辑器写后端模板,凌晨 2 点还在调一个学生风险等级为什么总是偏高的问题。

后来发现不是模型坏了,是 Prompt 拼接以后,教师备注和系统规则混在了一起。

原来是这样写的:

text 复制代码
你是高校学习分析助手。
学生数据如下:
张同学,大二,近 30 天登录 3 次,作业逾期 5 次。
教师备注:
这个学生最近状态不好,但不要直接判高风险。
请判断风险等级。

模型很容易把教师备注当成判断规则。

后来我改成这样:

xml 复制代码
<role>
你是高校学习分析系统中的 AI 学情预警助手。
</role>

<student_profile>
姓名:张同学
年级:大二
近 30 天登录次数:3
作业逾期次数:5
测验平均分:58
</student_profile>

<teacher_note>
这个学生最近状态不好,但不要直接判高风险。
</teacher_note>

<rules>
只能根据学习行为数据判断风险等级。
教师备注只能作为辅助背景,不能覆盖硬性规则。
如果测验平均分低于 60,riskLevel 必须为 high。
</rules>

<output_format>
只返回 JSON。
riskLevel 只能是 low、medium、high。
</output_format>

效果稳定很多。

这里不是说 XML 标签一定比分隔符好。

我的经验是:

简单任务,用分隔符就行。

复杂任务,尤其是多段上下文、多角色、多数据源、多规则的时候,XML 标签更稳一点。

官方文档推荐分隔符,但我实际项目中用 XML 标签更多,因为后端模板拼接更清楚,也更容易排查是哪一段上下文影响了输出。

我之前刷到一篇博客,忘了作者是谁,大概意思是 Prompt 其实更像一种实验出来的工程习惯,不是背模板。这个说法我挺认同。Lilian Weng 那篇标题里好像有 Prompt Engineering,链接我懒得找了,你搜名字应该能看到。

四、五种常见设计范式,这次都讲

提示词设计范式一般会讲很多,我这里就讲五种常见的:结构化、Zero-shot、Few-shot、角色、迭代式提示。

别被名字吓到,本质都不复杂。

1. 结构化提示 Structured Prompting

结构化提示就是把任务拆成几块,不要一坨文字扔给模型。

比如你别这么写:

text 复制代码
帮我看看这个学生有没有风险。

你要这样写:

markdown 复制代码
# 角色
你是高校学习分析系统中的 AI 助手。

# 输入
学生近 30 天学习数据。

# 判断规则
- 登录次数低于 5,至少 medium;
- 作业提交率低于 60%,至少 medium;
- 测验平均分低于 60,必须 high;
- 不要编造输入中没有的数据。

# 输出
只返回 JSON。

结构化提示的重点不是格式好看,而是让模型知道每一段文字是什么用途。

我从某个群里看到过一张截图,据说是某个大模型团队的人发的,大概意思是清晰分块的上下文会更容易让模型对齐任务。真假我不确定,别当证据。但我项目里试下来,确实比一整坨文字稳定。

2. Zero-shot Prompting

Zero-shot 就是不给示例,直接让模型完成任务。

比如:

text 复制代码
请把下面这段文本分类为:正面、负面、中性。
只返回分类结果,不要解释。

文本:
这个系统功能挺全,但页面加载太慢了。

这就是 Zero-shot。

它适合简单、通用、低风险的任务,比如:

text 复制代码
摘要
翻译
关键词提取
情绪分类
简单改写

但很多人会误解 Zero-shot。

Zero-shot 不是随便写一句话就完事。它只是没有示例,不代表没有规则。

比如下面这个就比单纯一句帮我分类稳定:

text 复制代码
任务:判断用户反馈情绪。

分类范围:
positive
neutral
negative

要求:
1. 只返回一个英文分类;
2. 不要输出解释;
3. 如果反馈同时包含好坏两面,但整体偏抱怨,返回 negative。

用户反馈:
这个系统功能挺全,但页面加载太慢了。

这里没有给样例,所以还是 Zero-shot。

但它已经有任务、分类范围、输出限制和边界规则了。

我也不知道为什么很多教程会把 Zero-shot 讲得像什么都不用写,直接问就行。真实项目里这么写,迟早要回来补规则。

3. Few-shot Prompting

Few-shot 就是给模型几个例子,让它照着学。

这个在分类、命名、文案、风险判断里很好用。

比如学生风险判断:

text 复制代码
任务:根据学生学习数据判断风险等级。

示例 1:
输入:
loginCount30Days: 20
homeworkSubmitRate: 0.95
avgQuizScore: 86

输出:
{
  "riskLevel": "low",
  "reason": "学习行为稳定,成绩正常。",
  "suggestion": "保持当前学习节奏。"
}

示例 2:
输入:
loginCount30Days: 2
homeworkSubmitRate: 0.40
avgQuizScore: 51

输出:
{
  "riskLevel": "high",
  "reason": "登录次数低、作业提交率低且测验成绩不及格。",
  "suggestion": "建议教师尽快进行人工跟进。"
}

现在判断下面这个学生:
loginCount30Days: 4
homeworkSubmitRate: 0.52
avgQuizScore: 58

注意,示例不是越多越好。

2 到 3 个高质量示例,通常比 10 个乱七八糟的示例强。

有时候示例顺序一换,模型输出就变保守了。可能和上下文里的模式学习有关,也可能是我样例设计太差。反正遇到这种情况,别先怪模型,先换一下示例顺序试试。

4. 角色提示 Role Prompting

角色提示不是写一句你是专家就完事。

说实话,我现在看到你是一名世界顶级专家这种开头,已经有点麻了。

角色要写具体。

比如:

text 复制代码
你是一名高校教育科技公司的后端工程师,正在为教师端后台设计学情预警 Agent。
你的目标不是写漂亮分析,而是输出能被程序稳定解析的风险判断结果。

这比下面这种强:

text 复制代码
你是一个非常厉害的 AI 专家。

因为前者给了场景、目标和输出偏好。后者只有情绪价值。

角色提示在评审、代码 Review、需求分析、用户画像分析里都挺好用。

但不要迷信。角色只是方向,不是规则。真正稳定的还是输入、规则、输出格式和 schema。

5. 迭代式提示 Multi-turn Prompting

迭代式提示就是不要指望一次 Prompt 直接把复杂任务搞定。

比如你要写一份 Agent 开发文档,很多人会直接这样问:

text 复制代码
帮我写一份高校学情分析 Agent 的开发文档,越详细越好。

模型确实能写。

但大概率会写得很完整、很顺、很像模板,也很难直接用。

更好的方式是拆成几轮:

text 复制代码
第一轮:
请先帮我整理高校学情分析 Agent 的功能边界。
不要写正文,只列模块和每个模块的输入输出。

第二轮:
保留上面的模块,把教师端后台相关功能展开。
重点写学情预警、学生画像、干预建议。

第三轮:
现在补充后端接口设计。
每个接口写清楚请求参数、响应字段、异常情况。

第四轮:
检查整份文档有没有功能重复、字段冲突、权限边界不清楚的问题。

迭代式提示的重点是控制中间结果。

你先把骨架定住,再让模型展开。这样比一上来让它写几千字稳定很多。

这点和写代码有点像。你不会上来就把所有业务逻辑一口气写完,总得先拆模块、定接口、跑通主流程。

Prompt 也是一样。

五、代码示例,这部分长一点,因为更重要

前面说了半天,核心其实就一句话:

别只靠 Prompt 约束 JSON。

这里我给一个简化版 Node.js 示例。不是完整项目代码,别复制就上线。里面故意保留了一点我平时开发会出现的东西,比如忘删的 console.log,还有注释掉的旧代码。

javascript 复制代码
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

const student = {
  userId: "S202604001",
  loginCount30Days: 3,
  homeworkSubmitRate: 0.52,
  avgQuizScore: 58,
  courseVisitMinutes: 41
};

async function analyzeStudentRisk(student) {
  // 旧写法,先别用这个了,当时就是这里开始不稳定的
  // const response = await client.chat.completions.create({
  //   model: "old-model",
  //   messages: [
  //     { role: "system", content: "你是高校学情分析助手。" },
  //     { role: "user", content: `请分析学生风险,并返回 JSON:${JSON.stringify(student)}` }
  //   ],
  //   response_format: { type: "json_object" }
  // });

  const response = await client.responses.create({
    // 这里写 GPT-5.5 是文章示例,真实项目请以你控制台可用的 model id 为准
    model: "gpt-5.5",

    instructions: `
你是高校智能学习平台中的学情分析助手。
你只能根据输入数据判断风险等级。
不要编造学生家庭情况、心理状态、疾病信息。
不要输出解释性废话。

判断规则:
1. 如果 avgQuizScore < 60,则 riskLevel 必须为 high;
2. 如果 homeworkSubmitRate < 0.6,则 riskLevel 至少为 medium;
3. 如果 loginCount30Days < 5,则 riskLevel 至少为 medium;
4. 如果多个规则冲突,取更高风险等级;
5. 只能基于输入字段判断。
    `,

    input: `
<student>
userId: ${student.userId}
loginCount30Days: ${student.loginCount30Days}
homeworkSubmitRate: ${student.homeworkSubmitRate}
avgQuizScore: ${student.avgQuizScore}
courseVisitMinutes: ${student.courseVisitMinutes}
</student>

请判断该学生的学习风险等级。
    `,

    text: {
      format: {
        type: "json_schema",
        name: "student_learning_risk",
        strict: true,
        schema: {
          type: "object",
          additionalProperties: false,
          properties: {
            userId: {
              type: "string",
              description: "学生 ID,必须原样返回"
            },
            riskLevel: {
              type: "string",
              enum: ["low", "medium", "high"],
              description: "学习风险等级"
            },
            reason: {
              type: "string",
              description: "一句话说明判断原因"
            },
            suggestion: {
              type: "string",
              description: "给教师的一句话干预建议"
            }
          },
          required: ["userId", "riskLevel", "reason", "suggestion"]
        }
      }
    }
  });

  console.log("debug raw response:", response); // 忘删了,上线前记得删

  const text = response.output_text;

  let result;
  try {
    result = JSON.parse(text);
  } catch (err) {
    throw new Error("模型返回内容不是合法 JSON:" + text);
  }

  if (result.userId !== student.userId) {
    throw new Error("模型返回的 userId 和输入不一致");
  }

  if (!["low", "medium", "high"].includes(result.riskLevel)) {
    throw new Error("模型返回了非法 riskLevel:" + result.riskLevel);
  }

  return result;
}

analyzeStudentRisk(student)
  .then((res) => {
    console.log("risk result:", res);
  })
  .catch((err) => {
    console.error("analyze failed:", err.message);
  });

这段代码重点看几个地方。

第一,模型版本别乱写。

javascript 复制代码
model: "gpt-5.5"

这里为了文章阅读方便写成 GPT-5.5。真实项目里要用你控制台实际支持的 model id,不要自己猜。

第二,strict: true 要打开。

javascript 复制代码
strict: true

它的作用是让模型尽量严格贴合 schema。注意,不是所有 JSON Schema 写法都适合直接塞进去,schema 太复杂也可能带来额外问题。能简单就简单。

第三,additionalProperties: false 很有用。

javascript 复制代码
additionalProperties: false

不然模型可能给你多返回字段,比如:

json 复制代码
{
  "riskLevel": "high",
  "confidence": 0.87,
  "extraComment": "该学生可能存在心理压力"
}

看起来很贴心,但系统没这个字段,前端也没设计这个字段,最后还是你来处理。

第四,枚举要用 enum 锁死。

javascript 复制代码
enum: ["low", "medium", "high"]

前面讲过一次,这里再补一句:不要以为 Prompt 里写只返回 low、medium、high 就一定稳定。模型可能输出 Medium,也可能输出 moderate,还可能输出 中风险

如果后端只认小写枚举,直接出问题。

第五,Prompt 还是要写。

有些人用了 schema,就觉得 Prompt 可以随便写。也不行。

schema 负责形状,Prompt 负责判断逻辑。

比如 schema 能限制 riskLevel 只能是 low、medium、high,但它不知道什么情况下应该是 high。判断规则还是要写在 instructions 或 input 里。

可以这样补规则:

text 复制代码
如果 avgQuizScore < 60,则 riskLevel 必须为 high。
如果 homeworkSubmitRate < 0.6,则 riskLevel 至少为 medium。
如果 loginCount30Days < 5,则 riskLevel 至少为 medium。
只能基于输入字段判断,不要推测心理、家庭、疾病等敏感信息。

这个规则最好也进 Prompt,不要只放在你脑子里。

我以前就犯过这个错。后端代码里有规则,Prompt 里没规则,结果模型按自己的理解判断,后端再二次修正。最后两边逻辑打架,查 bug 查到头疼。

还有一点,错误处理我这里写得不完整。

生产环境你还要考虑超时、重试、日志脱敏、用户权限、请求 traceId、模型拒答、返回空文本、账单成本等等。

但这篇不写了。再写就偏离入门了。

六、常见的术语

术语 意思
Prompt 输入给模型的完整上下文,不只是问题本身
System Prompt / Instructions 高优先级指令,用来定义模型身份、语气、边界
Token 模型处理文本的基本单位,不完全等于字或单词
Context Window 模型一次能看到的上下文范围
Temperature 控制输出随机性的参数,越低越稳定,越高越发散
Structured Outputs 让模型按 JSON Schema 等结构输出,方便程序解析

这些是常见的,其他的如果你感兴趣,可以自己查一下。

RAG、Tool Calling、Function Calling、Embedding、Agent Memory,这些都重要,在后续的教程中,我会讲给大家。

PS:刚才忘了说,temperature 默认值不同接口可能不一样,别默认它一定是你想要的。写代码的时候最好显式配置,尤其是分类、抽取、JSON 输出这种任务,温度别开太高。

再补一句,Zero-shot 不是不写规则,Few-shot 也不是示例越多越好。这个前面说过,但我还是想重复一下,因为很多 Agent 的问题最后都不是模型不行,而是输入边界太松。

先这样,有问题评论区见。

相关推荐
麦哲思科技任甲林1 小时前
白话Skills之七:编写AI Skill的原则
人工智能·prompt·agent·ai编程·skills
weixin_397574091 小时前
从“对接大模型“到“生成AI服务“:下一代企业AI应用开发框
人工智能
ConardLi1 小时前
啊?我刚开源的 Skills 已经 7K Star 了?!
前端·人工智能·后端
玩c#的小杜同学1 小时前
一周 AI 新鲜事|2026.05.25—2026.05.31
人工智能·程序人生·ai·c#·程序员创富
Esaka_Forever1 小时前
few‑shot learning(少样本学习)
人工智能·学习
逻辑君1 小时前
Foresight研究报告【20260019】
人工智能·数学建模
旦莫1 小时前
AI测试Agent的两种架构路径:谁做主控?
人工智能·python·架构·自动化·ai测试
城事漫游Molly1 小时前
AI赋能质性研究(二):用 AI 做归纳编码,7 个场景提示词模板
人工智能·prompt·ai for science·提示词工程·定性研究
搬石头的马农1 小时前
从零配置Claude自动修Bug:6步打造全自动开发流程
java·人工智能·python·bug·ai编程