知识点思维导图
29 个知识节点
参考资料
Python(24) - Prompt 工程基础
读完后,你应能完成以下任务:
- 绘制“Python(24) - Prompt 工程基础 / 先给锚点:Prompt ≈ 给函数传参 + 写接口契约”的关键对象与数据流,解释“和大模型交互几乎一一对应——只不过「参数」是自然语言,「返回结构」要靠提示词去约束。”,并用源码位置、日志或 Trace 标注证据。
- 为“Python(24) - Prompt 工程基础 / 三种 role:system / user / assistant 怎么用”设计正常与异常输入,验证“这三个角色是所有 Prompt 工程的骨架,务必分清职责。”,输出首个偏差位置与回归测试结果。
- 实现“Python(24) - Prompt 工程基础 / temperature:随机性旋钮(类比 Math.random 的「强度」)”的最小代码或配置,检验“temperature 控制输出的随机程度。”,输出命令、结果与 Diff,并说明不适用边界。
上一篇你已经会用 SDK 把消息发给大模型、拿到一段文本回来(第 23 篇)。但很快你会撞到三个真实问题:① 模型答得「飘」,时灵时不灵;② 你想要的是能
JSON.parse的结构化数据,它却返回一段散文;③ 你想让模型「查个天气再回答」,但它根本没法上网。本篇把这三件事一次讲透——Prompt 不是玄学咒语,它本质是给模型写「调用契约」;结构化输出 ≈ 给返回值套上 TS 类型;函数调用(function calling)≈ 给模型注册一张「函数注册表」,让它决定调哪个。这也是后面第 25~27 篇做 Embedding / RAG / Agent 的地基。
阶段五依赖链先对齐:23(调 API)→ 24(本篇·结构化输出 + 函数调用)→ 25(Embedding)→ 26(RAG)→ 27(Agent)。本篇是从「能聊天」跨到「能干活」的关键一步。
一、先给锚点:Prompt ≈ 给函数传参 + 写接口契约
前端调后端接口时,你会做三件事:选对 endpoint、传对参数、约定好返回结构。和大模型交互几乎一一对应——只不过「参数」是自然语言,「返回结构」要靠提示词去约束。
先回顾第 23 篇的最小调用,建立锚点:
// JS 对照(openai 的 node SDK,和 Python 形状几乎一样)
// 注意:Anthropic 的 SDK 形状不同——用 client.messages.create,system 是顶层参数而非 message,回复取 content[0].text
const resp = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: '你是一个简洁的助手,只用一句话回答' },
{ role: 'user', content: 'Python 的 list 和 JS 的数组像吗?' },
],
temperature: 0.7,
})
console.log(resp.choices[0].message.content)
| 前端心智 | 大模型交互 | 说明 |
|---|---|---|
| 接口的「全局配置/请求头」 | system 角色 |
设定身份、规则、输出格式(最稳定的指令位) |
| 一次请求的「请求体参数」 | user 角色 |
本轮你给的输入 |
| 服务端历史/会话状态 | assistant 角色 |
模型之前说过的话,回填进去 = 多轮记忆 |
| 接口返回的 JSON 结构 | 需用提示词/参数显式约束 | 不约束就是自由文本,见第四节 |
边界(哪里不一样):调后端接口,返回结构是服务端代码写死的契约,你传错参数会报 400。大模型不一样——它默认返回自由文本,你不约束它就「自由发挥」;而且同样的输入,多次调用结果可能不同(除非把
temperature调到 0)。所以「Prompt 工程」要做的,就是用提示词 + 参数,把这个「不确定的文本生成器」逼成一个「行为可预期的接口」。
二、三种 role:system / user / assistant 怎么用
这三个角色是所有 Prompt 工程的骨架,务必分清职责。
⚠️ 前端最容易踩的坑:以为 API 像聊天软件一样「记得上下文」。它不记得。HTTP 接口是无状态的,每一次
create都是独立请求。多轮对话的「记忆」完全靠你手动把历史 messages 全部重新发过去。聊得越久,messages 越长,token 消耗越大——这也是为什么后面 RAG(第 26 篇)要做检索而不是把所有资料一股脑塞进去。
写 system prompt 的几条实用原则(类比写接口文档):
- 明确角色与边界:「你是 X,只做 Y,不做 Z」。比「你很厉害」有用一万倍。
- 给格式约束:要 JSON 就说「只返回 JSON,不要解释」;要列表就说「用 markdown 无序列表」。
- 给例子(few-shot):在 system 或 user 里塞 1~2 个「输入→期望输出」示例,比纯文字描述更稳。这就是 few-shot prompting,类比「给后端同学一个示例 request/response 让他照着实现」。
三、temperature:随机性旋钮(类比 Math.random 的「强度」)
temperature 控制输出的随机程度。OpenAI Chat API 官方取值范围是 0~2,默认值为 1(注意:默认不是 0.7,0.7 只是常见的「平衡」取值)。
| temperature | 行为 | 适用场景 |
|---|---|---|
0(或接近 0) |
几乎确定性,同输入基本同输出 | 提取信息、分类、要结构化数据、要可复现 |
0.7 |
平衡,有一定多样性 | 普通问答、对话 |
1.0(默认) |
默认值,较发散 | 通用场景 |
1.5+ |
更发散、有创意、也更易跑偏 | 头脑风暴、文案、起名 |
类比 + 边界:你可以把
temperature想成「Math.random()的音量旋钮」——调到 0 就基本不随机。但边界是:即使temperature=0也不保证 100% 逐字一致(底层浮点、模型更新都可能带来微小差异),它只是「尽量确定」,不是===级别的确定。所以要结构化数据时,光靠调 temperature 不够,还得配合下一节的结构化输出手段。
四、结构化输出:让模型返回能 JSON.parse 的数据
这是前端转 AI 编程最实用的一招。你做应用,几乎不会想要一段散文,而是想要 { "name": "...", "age": 25 } 这种能直接喂给前端、能 .属性 取值的对象。
4.1 最朴素的办法:提示词里要求 JSON(可用但不保险)
⚠️ 这个写法不保险:模型有时会画蛇添足,回成
```json\n{...}\n```带代码块围栏,或前面加一句「好的,结果如下:」,导致json.loads直接抛异常。所以纯靠提示词约束,你必须自己写容错(剥离围栏、try/except)。生产里别这么裸用。
4.2 开关式保险:response_format 强制 JSON
OpenAI 兼容 API 提供了一个参数,强制模型输出合法 JSON(JSON mode):
边界:
json_object模式只保证「是合法 JSON」,不保证字段名/类型符合你的期望——它可能把age返回成字符串"25",或漏掉字段。要连「形状」也锁死,得用下面的 Pydantic 方案。
4.3 最稳的工程做法:Pydantic 模型 + 解析(≈ 给返回值套 TS 类型 + zod 校验)
还记得第 15 篇里 FastAPI 用 Pydantic 校验请求体吗?同一套东西可以用来校验模型的输出。新版 openai SDK 提供了 parse 接口,直接把回复反序列化成 Pydantic 对象:
// JS/TS 心智对照:你平时这样定义接口返回类型 + 用 zod 在运行时校验
import { z } from 'zod'
const Person = z.object({ name: z.string(), age: z.number() })
type Person = z.infer<typeof Person>
// 拿到模型返回后:Person.parse(rawData) —— 校验不过就抛错
类比 + 边界:Pydantic 模型在这里扮演的角色,和你在前端用 zod 校验后端返回 / 表单数据几乎一模一样——定义形状、运行时校验、不符合就报错。差异在于:这里它还会被 SDK 翻译成 JSON Schema 下发给模型,相当于把「我要的返回类型」写进了请求契约,模型照着填。这是目前最稳的结构化输出姿势,优先用它。
五、函数调用(function calling):给模型注册一张「函数注册表」
这是本篇最重要的概念,也是第 27 篇 Agent 的内核。
5.1 先想通:模型为什么需要「调用函数」
模型本身不能上网、不能查数据库、不能读你的本地文件——它只是个文本生成器。所以遇到「北京今天天气怎样」「查一下订单 123 的状态」这类问题,它没法凭空知道。
function calling 的思路:你把一组可用函数的「说明书」告诉模型,模型不直接执行,而是返回「我想调哪个函数、传什么参数」,由你的代码去真正执行,再把结果喂回去。
类比:这完全就是前端的事件回调 / 函数注册表模式。你给某个库注册一堆回调(
on('click', fn)),库在合适时机告诉你「该触发哪个了」,但真正跑函数的是你的环境。这里模型扮演「决定调哪个」的大脑,执行权始终在你手里——模型只会说「请帮我调 get_weather('北京')」,绝不会自己偷偷联网。
5.2 三步闭环(记住这个循环,第 27 篇的 Agent 就是它套上 while)
1. 你下发:用户问题 + 函数清单(tools)
2. 模型回:我要调 get_weather,参数 {"city": "北京"} ← 它不执行,只告诉你意图
3. 你执行 get_weather("北京") 拿到结果,再把结果发回去 → 模型据此生成最终回答
5.3 完整代码:注册一个查天气函数
5.4 关键认知
| 误区 | 真相 |
|---|---|
| 「模型会自己执行函数」 | ❌ 模型只返回意图(调谁、传什么),执行永远是你的代码 |
| 「写了 tools 模型就一定会调」 | ❌ 调不调由模型判断,简单问题它直接答;靠 description 写清楚来引导 |
| 「参数是 dict 直接能用」 | ❌ call.function.arguments 是 JSON 字符串,要先 json.loads |
边界 + 前瞻:function calling 是「一问一调一答」的单步。把它放进一个 while 循环——「模型思考 → 调工具 → 拿结果 → 再思考 → 再调…直到它说『不用调了,这是最终答案』」——就是第 27 篇的 ReAct / Agent。所以本篇这套三步闭环吃透了,Agent 那篇就只是「给它套个带记忆的循环」而已。
六、前端新手最容易踩的几个坑
- 以为 API 有记忆。它无状态,多轮对话必须自己回填全部历史 messages(见第二节)。
- 裸用提示词要 JSON 不做容错。模型爱加代码块围栏、加前言,
json.loads直接炸。优先用response_format或 Pydantic.parse(第四节)。 - 把 Prompt 当咒语堆形容词。「你是世界顶级专家」没用。有用的是:明确角色边界、给输出格式、给 1~2 个例子(few-shot)。
- 忘了 token 是要花钱的。messages 越长越贵越慢,长上下文别无脑全塞——这正是 RAG(第 26 篇)存在的理由。
- 以为 function calling 模型会自己跑函数。它只给意图,执行在你手里;参数是 JSON 字符串要 parse。
- 温度调了就以为结果稳定。
temperature=0只是「尽量确定」,要锁结构还得靠 Pydantic 校验。
七、和前端的整体对照速查
| 前端做法 | AI 编程对应 | 篇内位置 |
|---|---|---|
| 接口全局配置 / 中间件 | system 消息 |
第二节 |
| 会话状态 / 历史记录 | 回填 assistant 消息(手动) |
第二节 |
Math.random() 强度 |
temperature |
第三节 |
JSON.parse(res) |
json.loads / response_format |
第四节 |
| zod / TS interface 运行时校验返回 | Pydantic 模型 + .parse |
第四节 |
on('event', callback) 注册回调 |
tools 注册函数 schema |
第五节 |
| 库回调「该触发哪个了」 | 模型返回 tool_calls 意图 |
第五节 |
八、总结
- 先给锚点:Prompt ≈ 给函数传参 + 写接口契约:和大模型交互几乎一一对应——只不过「参数」是自然语言,「返回结构」要靠提示词去约束。
- 三种 role:system / user / assistant 怎么用:这三个角色是所有 Prompt 工程的骨架,务必分清职责。
- temperature:随机性旋钮(类比 Math.random 的「强度」):temperature 控制输出的随机程度。
- 结构化输出:让模型返回能 JSON.parse 的数据:这是前端转 AI 编程最实用的一招。
- 函数调用(function calling):给模型注册一张「函数注册表」:这是本篇最重要的概念,也是第 27 篇 Agent 的内核。
- 前端新手最容易踩的几个坑:以为 API 有记忆。它无状态,多轮对话必须自己回填全部历史 messages(见第二节)。 -> 裸用提示词要 JSON 不做容错。 -> 把 Prompt 当咒语堆形容词。 -> 忘了 token 是要花钱的。
学完自测
选择所有正确答案;提交后逐项核对判断依据。