LLM 接口与提示词工程
这是整个系列的地基。你会发现,后面 RAG、Function Calling、Agent、多 Agent……所有复杂的东西,本质都是在这一篇的几个基础概念上做乘法:messages、Token、流式、提示词。
这一篇回答一个核心问题:怎么跟大模型「正确地对上话」。 很多人一上来就去记一堆 API 参数,却没先搞清楚 LLM 的本质。读完你会发现,只要把「LLM 到底是什么」这一件事想透,后面所有的参数、套路、优化,都会自动串成一条线。
1. LLM 是什么:一个「按字收费的远程打字员」
在讨论任何 API、任何参数、任何框架之前,先把 LLM 的本质画像立起来。用你熟悉的场景打个比方:
LLM ≈ 一个按字计费的远程打字员
- 它文化很好(预训练时灌了海量语料),但完全没记性——每次都是全新雇佣,必须把背景重新交代一遍;
- 它按字双向收费(Token 计价),你发的和它回的都算钱;
- 它有精力上限(上下文窗口),交代的资料太长它直接罢工;
- 它只会打字,不会做事——除非你额外给它工具和「操作手册」(这是后面 Function Calling 的伏笔)。
把这四条记牢:后面所有的参数、套路、成本优化,都只是在解决这四条限制里的某一条。 这是本篇的「总纲」。
1.1 一个必须立刻建立的认知:记忆不在模型侧
这可能是整个系列最重要的一个底层认知,值得单独强调:
模型本身是无状态的,每一次调用都是一个纯函数。 它「记得」上一句,不是因为它有记忆,而是你把完整的对话历史原样发过去,它读一遍,再续写一句。
# 你以为:模型"记住"了对话
# 真相:每次都把完整历史重新发一遍
messages = [
{"role": "user", "content": "我养了只猫"},
{"role": "assistant", "content": "真可爱,什么品种?"},
{"role": "user", "content": "它叫什么名字好?"}, # ← 前两句被原样重放,模型才"接得上"
]这个认知,是后面「多轮对话失忆」「上下文截断」「Memory 机制」(08 篇)一切现象的根因。搞不懂它,后面那些「为什么对话突然失忆」的现象你就永远解释不通。
2. 为什么需要「提示词工程」
2.1 一个新手 100% 会遇到的挫败
很多人第一次调 LLM 的反应是:「我写了一句『帮我写代码』,它回了一堆我根本用不上的东西。」
这不是模型不行,是你交给它的「交接文档」写得烂。对比两种写法:
| 写法 | 输出 | 工程属性 |
|---|---|---|
❌ 帮我写代码 | 语言不定、功能随意、结构漂移 | 不可控,没法复用 |
✅ 你是资深后端工程师。用 Python 写一个查询订单的 REST 接口,输入订单号,返回 JSON(含 id/金额/状态)。只输出代码,不解释。 | 可直接运行 | 可控,可模板化 |
2.2 好的 Prompt 里藏着五个维度的约束
拆开那个「✅」的写法,你会发现它精确地锁住了五个维度:
- 角色(资深后端工程师)——定回答口径和视角;
- 技术栈(Python)——定实现语言;
- 输入输出契约(订单号 → JSON)——定做什么;
- 字段 schema(id/金额/状态)——定结果的精确结构;
- 格式要求(只输出代码,不解释)——定输出形式。
一句话记住这件事的定位,非常有用:
把 Prompt 当成「调用对方的接口文档」来写——参数、返回值、约束一样都不能少。
你写接口文档越规范,下游越不会来回找你对;你写 Prompt 越结构化,模型越不会跑偏。提示词工程,本质上就是「软件工程思维」在自然语言上的应用。
3. 核心协议:messages 契约
这是本篇的技术骨干。掌握了它,你就拿到了国内外几乎所有主流大模型的通用钥匙。
3.1 为什么所有国产大模型「都像 OpenAI」
2023 年 OpenAI 的 /v1/chat/completions 一声不响地成了事实标准。今天你能叫得上名字的厂商——DeepSeek、通义、智谱、混元、月之暗面——几乎都提供了「OpenAI 兼容」端点。
这件事对你的含义就一句话:同一份调用代码,只要换 base_url 和 api_key 就能切换模型。 这个兼容性,是你以后做「多模型路由」「成本优化」的基石——也解释了为什么 05 篇的框架能「改一行配置就换模型」。
3.2 messages 的结构
一个完整的请求体长这样(Python 示意):
messages = [
{"role": "system", "content": "你是一位资深架构师,回答精炼,只给结论不废话。"},
{"role": "user", "content": "为什么会有循环依赖?"},
{"role": "assistant", "content": "因为两个 Bean 互相依赖,实例化时……"},
{"role": "user", "content": "那怎么解决?"},
]关键点:messages 是一个按时序排列的数组,不是某个「会话对象」。每次请求都要把完整历史重放(呼应 1.1 的无状态)。四种角色:
| 角色 | 含义 | 优先级 |
|---|---|---|
system | 人设、规则、约束 | 最高(但非铁律,见下) |
user | 用户真实输入 | — |
assistant | 上一轮的模型回答 | 由客户端原样重放 |
tool | 工具调用结果 | 配合 Function Calling 用 |
一个必须警惕的事实:system 优先级高,但不是铁律——恶意构造的 user 内容仍然可能诱导模型违反 system(这就是后面的「提示注入攻击」,10 篇会专门讲)。所以安全边界永远不能只押在 system 一句话上,关键动作要在代码侧二次校验。
3.3 一次最小调用
import httpx
def chat(system_prompt: str, question: str, model: str = "deepseek-chat"):
resp = httpx.post(
"https://api.deepseek.com/v1/chat/completions",
headers={"Authorization": f"Bearer {KEY}"},
json={
"model": model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": question},
],
"temperature": 0.2,
},
)
return resp.json()["choices"][0]["message"]["content"]这段代码里没有「大模型魔法」——就是一个 HTTPS POST + JSON。你要清楚一件事:LangChain、任何框架,底层干的就是这件事,它们只是加了一层「类型安全的外壳」。 想透这一层,你往后用任何框架都不会「被框架糊弄」,而是清楚地知道它在帮你发哪个请求。
3.4 几个关键参数
只讲你一定会用到的几个,其余用到再查:
| 参数 | 作用 | 建议 |
|---|---|---|
temperature | 随机性 0~2 | 代码/结构化场景设低(0~0.2);文案设中高(0.7~1.0) |
max_tokens | 回复长度上限 | 设太小会被硬截断(不是优雅结束) |
stream | 是否流式 | 交互场景开,批处理关 |
一个反直觉的坑:
temperature=0也不是严格确定的(浮点采样仍有扰动),要追求可复现还得配合seed,且仍只是「近似」。别指望temperature=0给你「绝对可复现」。
4. 提示词工程的 4 个套路
「提示词工程」听着玄,其实就是 4 个可模板化的套路。它们不是「文采」,是「工程技巧」。
4.1 角色锚定
用 system 把「回答口径」锁死:
system: 你是资深架构师,只用中文,只给结论和关键代码,不废话。作用:让模型在整段对话里,稳定地保持一个「人设」和一个「口径」,避免输出漂移。
4.2 Few-shot:给例子比讲规则更有效
这是最反直觉但最有效的一招。要模型稳定输出「分类标签」,与其反复强调「只输出标签」,不如给它看 3 个例子:
system: 把问题分类为 BUG / FEATURE / QUESTION,只输出分类。
示例:
输入:登录后 500 报错 → BUG
输入:希望支持钉钉登录 → FEATURE
输入:怎么改密码? → QUESTION
user: 登录按钮点不动了模型输出:BUG(稳稳的)。
原理:LLM 本质是「续写」,给它看了 3 条「输入→输出」的模式,它会强烈倾向续写同样的模式。需要格式稳定的场景,优先用 Few-shot,而不是拼命强调规则。 因为「例子」比「规则」对模型更有约束力。
4.3 结构化输出
别让模型返回自然语言再费劲解析,直接要求 JSON:
system: 抽取订单信息,只返回 JSON,字段 {orderId, amount, status},不要解释。再配合请求参数 response_format={"type": "json_object"} 开启 JSON 模式,保证一定是合法 JSON。
这招的意义在于:把「不可控的自然语言」变成「可解析的结构化数据」,让下游程序能稳定消费模型的输出。这是「模型输出 → 系统输入」的关键一环。
4.4 思维链(CoT)
适用于需要推理的任务(写 SQL、算法、方案设计)。核心一句咒语:
「一步步思考,先列出推理步骤,再给最终结论。」
对简单题没必要(纯浪费 Token),对复杂题立竿见影——模型把推理过程「展开」后,最终答案准确率显著上升。判断标准:这个任务你自己也需要「打草稿」才能做对,就让模型也「打草稿」(CoT)。
5. Token 治理:成本是 LLM 场景的命门
一个有 Token 意识的工程师和一个没有的,在同一个需求上写出来的代码,成本可能差 10 倍。这是本篇的「命门」,也是「高效」维度在 LLM 场景的直接体现。
5.1 Token 是什么
模型既不按字符、也不按词计费,而是按 Token(子词单元)。粗换算:英文 1 Token ≈ 0.75 词;中文 1 汉字 ≈ 1.5~2 Token(汉字拆得更碎)。
为什么这个概念重要:因为「成本 = 输入 Token + 输出 Token」,而你写的每一个 Prompt 字、模型回的每一个字,都在这笔账里。有了 Token 意识,你才会去抠「这句话值不值得发」。
5.2 三个省钱的硬手段
| 手段 | 做法 | 节省 |
|---|---|---|
| 模型分级 | 简单任务用便宜模型,复杂任务才上旗舰 | 30%~90% |
| Prompt 精简 | 去寒暄、去冗余示例、压缩 Few-shot | 10%~30% |
| Prompt 缓存 | 稳定的 system/长文档放前段,命中厂商缓存 | 输入成本降到 1/4~1/10 |
选型启示(务实的一条):做「批量分类、格式转换、简单抽取」这类任务,优先用便宜档模型,成本比旗舰低一个数量级。别一上来就堆最贵的——「杀鸡用牛刀」在 LLM 场景是要真金白银付代价的。
6. 流式输出:让用户 1 秒内看到响应
同一个「写 500 字」的请求,非流式要等 8~15 秒整段生成完(用户像看白屏);流式 0.3~1 秒就出首字(打字机效果)。
任何面向用户的 LLM 应用都该上流式。 实现上就是把 stream=True 加到请求,响应变成一段段 SSE 事件,客户端负责把 delta.content 拼接起来。这个细节交给框架即可,你只需要知道「为什么必须流式」——因为它直接决定用户的「体感延迟」,而体感比真实延迟更能决定用户走不走。
7. 最常踩的 3 个坑
| 现象 | 根因 | 正确姿势 |
|---|---|---|
| 多轮对话突然「失忆」 | 拼接历史超过上下文窗口,被静默截断 | 加对话摘要,或走 RAG(03 篇) |
| 回答说一半就没了 | max_tokens 设太小被硬截断 | 放大上限,或要求「分条回答」 |
| 高并发下偶发 429 | 触发厂商限流 | 加指数退避重试 + 分级降级 |
这里多说一句「失忆」这个坑——它是新手最困惑的现象,而它的根因,正是 1.1 讲的那个「模型无状态 + 历史重放」。历史重放有个隐藏的陷阱:当历史超长、超过窗口时,系统会「静默截断」最旧的消息,而不是报错。 所以你会看到「刚才还聊得好好的,怎么突然就忘了前面的事」——不是模型坏了,是前面的历史被挤出去了。
8. 记住这一句
Prompt 是交接文档,Token 是计费单位,messages 是状态载体,流式是交互标配。
这四件事吃透,后面 RAG、Function Calling、Agent……所有复杂场景都只是在这四把钥匙上做乘法。
下一篇预告:LLM 能说话了,但它不知道你的私有知识——你问它「公司的请假流程」,它要么不知道、要么编一个。这就到了 03 · RAG 架构与工程落地。