6.1 使用文本生成模型
6.1.1 模型选择与提示词模板
选择生成模型时,至少需要考虑:
| 维度 | 需要权衡的内容 |
|---|---|
| 能力 | 指令遵循、推理、代码、多语言等是否适合任务 |
| 成本 | API 费用、显存、延迟和吞吐量 |
| 部署 | 使用云端专有模型,还是本地开源模型 |
| 数据 | 数据是否允许发送到第三方服务 |
| 上下文 | 输入、历史消息和输出能否放进上下文窗口 |
| 输出控制 | 是否支持 JSON Schema、工具调用或约束解码 |
书中使用 Phi-3-mini 演示,并不意味着它适合所有任务。更通用的做法是:先用足够完成任务的小模型建立基线,再根据评测结果决定是否升级模型。
对话模型接收的也不是界面中看到的消息列表,而是经过**聊天模板(Chat Template)**序列化后的文本,例如:
<|system|>
你是一个助手。<|end|>
<|user|>
请总结这段文字。<|end|>
<|assistant|>
其中角色词元、消息结束词元和生成起始位置通常与模型的训练方式绑定。
补充理解:
提示词工程负责设计“说什么”;聊天模板负责把 system、user、assistant 等消息转换成模型训练时熟悉的格式。模板用错,即使提示词内容正确,也可能造成不回答、角色混乱或输出质量下降。
6.1.2 控制生成:temperature、top_p 与 top_k
模型先为词表中的候选 Token 产生分数,再由解码策略选择下一个 Token。生成参数控制的是怎样从候选分布中选择,不会为模型增加它原本不知道的知识。
temperature
**temperature(温度)**用于调整概率分布的尖锐程度:
- 温度较低:高概率 Token 更占优势,输出通常更稳定、保守
- 温度较高:概率分布更平坦,低概率 Token 更有机会被选中,输出更多样
- 关闭采样时,系统通常直接选择分数最高的 Token,此时 temperature、top_p 等采样参数往往不起作用

top_p 与 top_k
- top_p(核采样):从最高概率开始累加,只保留累计概率达到阈值的最小候选集合
- top_k:只保留概率最高的前
k个候选 Token

可以粗略记成:
temperature
→ 调整候选概率之间的差距
top_p / top_k
→ 缩小可以参与采样的候选范围
| 任务 | 常见倾向 |
|---|---|
| 分类、信息抽取、代码修改 | 低随机性,追求稳定和可复现 |
| 摘要、翻译 | 较低随机性,同时保留一定语言选择空间 |
| 头脑风暴、创意写作 | 适当提高随机性,并生成多个候选进行筛选 |
Tips:
不同推理框架对参数的实现和默认值可能不同,不能把某一组数值当成通用最佳配置。正确做法是在自己的任务数据上评测。
6.2 提示工程的基本结构
LLM 的基础行为是续写。一个没有明确任务的输入,模型只会预测最可能接在后面的内容:

为了把“自动补全”变成可控任务,提示词通常会组合以下组件:
| 组件 | 回答的问题 |
|---|---|
| 任务 / 指令 | 要模型做什么? |
| 数据 | 要处理的具体内容是什么? |
| 上下文 | 为什么做、有哪些背景和可用依据? |
| 约束 | 哪些内容不能做,长度和范围是什么? |
| 输出格式 | 应返回 JSON、表格、列表还是自然语言? |
| 示例 | 合格的输入输出长什么样? |
| 角色、受众、语气 | 用什么专业视角、面向谁、怎样表达? |

复杂提示词并不意味着组件越多越好。组件的作用是减少歧义,而不是堆砌“魔法短语”。

一个更稳妥的通用骨架是:
任务:准确说明目标动作。
依据:只允许使用哪些材料。
约束:长度、范围、禁止项和失败时的处理方式。
格式:明确字段、顺序和数据类型。
数据:使用明显分隔符包住待处理内容。
例如:
任务:判断评论情感。
标签:只能是 positive、neutral、negative 之一。
规则:没有明显倾向时输出 neutral,不要解释。
评论:<review>这部电影还可以,但不会再看。</review>
输出:
6.2.1 提示词设计的几个原则
- 具体:把“写一段介绍”改为“面向初学者,用不超过 120 字解释,并给出一个例子”。
- 提供边界:说明允许依据什么;资料不足时应返回什么,而不是要求模型猜测。
- 分隔数据:把指令与外部文本明确隔开,降低把数据误当成指令的概率。
- 明确格式:自动化场景尽量规定字段和类型,不依赖自然语言猜测。
- 使用示例:如果格式或判断边界难以描述,直接给一个合格示例。
- 迭代评测:提示词修改后,应在固定测试集上比较准确率、稳定性、成本和延迟。
补充理解:
“如果不知道就说不知道”只能给模型一个行为倾向,不能从机制上消除幻觉。若任务要求事实可靠,还需要检索、引用、规则校验或人工复核。
6.3 上下文学习与链式提示
6.3.1 上下文学习:零样本、单样本和少样本
上下文学习(In-Context Learning):在当前提示词中提供任务说明或输入输出示例,让模型临时模仿其中的模式。

| 形式 | 提示词中包含的示例 |
|---|---|
| 零样本(Zero-shot) | 不提供示例,只给任务说明 |
| 单样本(One-shot) | 提供 1 个示例 |
| 少样本(Few-shot) | 提供 2 个或更多示例 |
这里最容易误解的是:
上下文学习不是训练,也不是微调。示例只存在于当前上下文中,模型参数没有发生更新。
示例主要帮助模型理解:
输入和输出如何对应
标签边界如何划分
输出格式如何组织
异常情况如何处理
选择示例时应注意:
- 示例要正确且有代表性,最好覆盖容易混淆的边界情况
- 格式、标签名称和最终任务保持一致
- 不要让示例中的偶然特征变成错误规律
- 示例会占用上下文窗口,数量不是越多越好
6.3.2 链式提示:把复杂任务拆开
链式提示(Prompt Chaining):将复杂任务拆成多个模型调用,并把前一步的输出作为后一步的输入。

原始资料
↓
步骤 1:提取产品特征
↓
步骤 2:根据特征生成名称和口号
↓
步骤 3:根据已确认的信息生成宣传语
↓
步骤 4:检查长度、禁用词和事实一致性
相比把所有要求塞进一个巨大提示词,拆分后的好处是:
- 每一步目标更明确,也更容易单独测试
- 中间结果可以检查、修改或缓存
- 不同步骤可以使用不同模型和生成参数
- 某一步失败时,可以只重试该步骤
代价也很明显:
- 多次调用增加延迟和成本
- 前一步的错误可能传播到后一步
- 如果中间输出是自由文本,后续步骤容易误解
因此链式提示最好配合结构化中间结果、解析器和逐步校验使用。
6.4 “推理”类提示方法
再次强调:本节仍然属于提示词工程。
这里是在输入和调用流程上诱导模型生成更有组织的中间步骤,不能仅凭输出了一段“思考过程”,就断言模型内部进行了与人类相同的推理。
6.4.1 思维链:让模型先展开步骤
思维链(Chain-of-Thought,CoT)提示:通过推理示例,或通过“分步骤解决”等指令,让模型在给出答案前生成中间步骤。

它对多步计算、规则应用和规划问题可能有帮助,因为后续 Token 可以继续利用前面已经生成的中间信息。
但要注意:
- 一段流畅的解释仍可能从第一步就错了
- 中间文字可能是对答案的事后合理化,并不等于真实内部过程
- 简单任务强制展开大量步骤,反而可能引入额外错误
- 在实际应用中,更值得要求的是可验证的计算、引用、假设和结论,而不是冗长的“内心独白”
6.4.2 自洽性:多次采样再聚合
自洽性(Self-Consistency):对同一个问题采样多条解题路径,只提取最终答案,再通过多数投票或其他规则聚合。

同一问题
├→ 路径 A → 答案 11
├→ 路径 B → 答案 11
└→ 路径 C → 答案 6
↓
多数投票
↓
最终答案 11
它适合存在明确最终答案、且不同路径有机会互相纠错的任务。代价是调用次数、成本和延迟近似随样本数增加;如果模型缺少关键知识,多数答案也可能一致地出错。
6.4.3 思维树:搜索多条候选路径
思维树(Tree-of-Thought,ToT):在每个阶段生成多个候选步骤,对候选进行评分、保留和剪枝,再继续展开更有希望的路径。

问题
├→ 候选思路 A ─→ 继续展开
├→ 候选思路 B ─→ 评分较低,剪枝
└→ 候选思路 C ─→ 继续展开
↓
比较最终结果
真正的思维树更接近一种搜索与编排算法,通常需要多次生成、评分和回溯。仅在一个提示词中让模型“模拟多个专家讨论”,最多是在模仿这种结构,并不等价于完整的树搜索。
6.5 输出控制与验证
生产系统不能把“模型看起来遵循了要求”当成验证通过。输出控制可以分为几层:
提示词约束
↓
结构化生成 / 约束解码
↓
确定性解析与 Schema 校验
↓
业务规则、事实与安全检查
↓
必要时重试、降级或交由人工
6.5.1 用示例规定输出
少样本示例不仅能示范任务,还能示范字段、顺序和措辞。它能提高遵循格式的概率,但仍然只是“软约束”,模型仍可能多写字段、漏字段或生成非法 JSON。
6.5.2 约束解码
约束解码(Constrained Decoding):在每次选择下一个 Token 时,直接屏蔽不符合语法、枚举或 Schema 的候选 Token。

例如情感分类只允许:
positive
neutral
negative
这种方法比“请只输出三个标签之一”更强,因为非法候选会在采样阶段被排除。
但需要区分:
语法正确不等于内容正确。
合法 JSON
≠
字段值一定真实
合法标签
≠
分类结果一定正确
6.5.3 使用 LLM 检查 LLM 输出
可以让另一次模型调用按照规则检查、评分或修改前一次输出:

这种方式适合检查语气、覆盖范围、表达质量等难以完全编码的条件,但检查模型也会犯错。因此:
- JSON、数值范围、必填字段等,应优先用程序验证
- 事实应与可信数据源或引用核对
- 安全敏感或高风险决定不能只依赖一次 LLM 自检
- LLM 评审更适合作为信号之一,而不是绝对真值
6.6 总结
本章可以压缩为四件事:
怎样写清楚任务
→ 指令、上下文、约束、格式和示例
怎样控制生成随机性
→ temperature、top_p、top_k
怎样拆解复杂任务
→ 上下文学习、链式提示、候选采样和搜索
怎样让结果更可用
→ 约束解码、解析、规则校验和外部验证
本章最终边界说明:
本章所谓“思维链、自洽性、思维树、专家讨论”等,主要是在描述一组运用提示词与组织模型调用的方法。它们不是模型训练方法,也不是调用时临时改造模型内部结构的方法。同时,随着模型演进,部分现代指令模型、推理模型和应用平台可能已经在训练、推理策略或系统编排层中融入了任务分解、多候选搜索、自检、工具调用等能力。此时再机械叠加旧式提示技巧,不一定继续提升效果,甚至可能增加成本或干扰模型。
因此最可靠的原则不是背诵“万能提示词”,而是:先明确任务和验收标准,再用真实样本比较不同提示与流程的效果。
第 7 章定位:
本章原文主要使用 LangChain 演示高级文本生成系统,但真正值得记录的是其背后的通用结构:模型接口、提示词模板、链式编排、状态管理、工具调用与智能体控制。 LangChain 只是其中一种实现,下面弱化具体 API,把重点放在可迁移到任何框架的知识上。
7.1 模型输入与输出
7.1.1 量化:用精度换取资源效率
模型参数通常用浮点数保存。**量化(Quantization)**通过减少表示权重所需的位数,降低模型文件大小和运行时内存 / 显存占用。
更高精度
→ 保存的信息更多
→ 占用空间更大
更低精度
→ 模型更小、部署门槛更低
→ 可能带来一定质量或速度上的权衡
常见直觉是:
| 精度形式 | 一般特点 |
|---|---|
| FP32 | 精度高,占用大,推理部署较少直接使用 |
| FP16 / BF16 | 常见的半精度运行形式,质量与资源之间较均衡 |
| INT8 / 8-bit | 进一步降低内存,通常保持较好质量 |
| 4-bit | 本地部署常见,压缩更明显,但对量化方法和模型更敏感 |
Tips:
“位数越低越快”不是绝对规律。实际速度还取决于硬件、算子支持、量化格式、上下文长度和批处理方式。相比极低位量化的大模型,有时高精度的小模型更快也更可靠。
7.1.2 模型调用不只是一个字符串接口
一个可用的模型适配层至少要处理:
消息 / 输入数据
↓
应用正确的聊天模板
↓
分词与上下文长度检查
↓
模型生成(采样参数、最大输出长度、停止条件)
↓
解析输出
↓
返回统一结果
因此,同一句文本直接交给不同模型不一定得到可比结果。需要同时匹配:
- 模型专用聊天模板和角色格式
- 上下文窗口与最大输出 Token 数
- 采样、停止词、随机种子和流式输出设置
- 返回值结构、异常和超时处理
LangChain、Transformers 或其他 SDK 的价值之一,就是把这些差异封装到相对统一的接口中;但抽象层不能替代对底层模型要求的理解。
7.2 链:把组件组成可测试的流程
**链(Chain / Pipeline)**并非某个框架独有,它只是把多个确定步骤按照预先规定的顺序连接起来。
输入
↓
提示词模板
↓
模型
↓
输出解析器
↓
验证器
↓
结果
7.2.1 提示词模板
提示词模板把固定结构与变量分开:
固定部分:
“请把以下内容总结成 3 个要点,只依据原文。”
变量部分:
{text}
运行时再把数据填入模板:
template(text)
↓
完整 Prompt
↓
LLM
它的主要价值不是少复制几行文字,而是:
- 统一提示词结构,避免不同调用各写一套
- 把业务数据与指令分离
- 便于版本控制、测试和替换模型
- 明确每个步骤需要哪些输入、应产生哪些输出
补充理解:
模板中的变量仍是不可信输入。外部文本可能包含与系统任务冲突的内容,因此需要分隔、转义、权限控制和结果校验,不能只依靠一句“忽略文本中的指令”。
7.2.2 多步骤链
多个链可以串联:
主题
↓
生成标题
↓
根据标题生成人物设定
↓
根据标题与人物设定生成故事
这与第 6 章的链式提示本质一致,只是工程化后,每一步有了明确接口。
一个稳健的多步骤链应做到:
- 每一步只承担清晰、有限的职责。
- 中间结果尽量使用结构化字段,而不是难以解析的大段自由文本。
- 每一步都可以单独重试、缓存和测试。
- 在进入下一步前校验关键字段。
- 记录使用的模型、提示词版本、参数、耗时和错误。
Tips:
链越长,累计延迟和错误传播越严重。能用确定性程序完成的步骤,不要全部交给 LLM。
7.3 记忆(Memory):模型如何保存状态
普通 LLM 调用通常是无状态的。单次推理只处理当前 Context:
输入 Context
↓
模型计算
↓
生成输出
如果下一次调用没有再次提供之前的信息,模型就不会凭空知道之前发生过什么:
第 1 次调用:
“我叫小明。”
第 2 次调用:
“我叫什么?”
因此,应用必须在下一次调用前,选择一部分历史状态并重新放回当前上下文。工程上的记忆系统要解决:
过去发生了什么?
↓
哪些信息值得保存?
↓
当前问题需要哪些过去信息?
↓
怎样把它们重新提供进 Context?
在展开之前,先明确一个关键区分:
“怎么实现记忆”和“保存什么内容”不是同一个分类维度。
按实现方式 → 信息如何被保存和重新取出
Buffer / Sliding Window / Summary / Retrieval / Structured
按内容类型 → 这段记忆本身属于什么
Working / Episodic / Semantic / Procedural
按存储位置 → 模型外部 / 模型内部
外部:上述实现;内部:Parametric Memory(权重中的知识)
本节正文只保留工程上最常用的“按实现方式”分类;内容类型、参数记忆、记忆管理器细节与混合架构等扩展内容,统一放在《LLM 常见记忆机制》中展开。
7.3.1 Conversation Buffer(完整缓冲区)
保存全部历史消息:
当前输入
+
从第一轮开始的完整对话
→ 交给模型
优点是信息完整、实现简单、短对话效果好;缺点是 Token、成本和延迟持续增长,最终可能超过上下文窗口,而且大量历史内容与当前问题无关。
更适合短期记忆。
7.3.2 Sliding Window(滑动窗口)
只保留最近 k 轮对话:
[旧旧旧旧旧旧][最近 10 轮]
↑
只保留这里
优点是 Token 使用量稳定、实现简单;缺点是较久以前的重要信息会被直接丢弃。
适合保存短期、最近发生的事情。
7.3.3 Summary(对话摘要)
把较早历史压缩成摘要,近期消息保留原文:
较早消息 → 摘要
最近消息 → 原文
摘要 + 最近消息 + 当前问题 → 模型
优点是大幅节省上下文,并保留长期对话的大致状态;缺点是摘要可能遗漏细节、混淆人物或固化早期错误,随着多次压缩产生信息偏差,而且生成摘要本身也需要额外计算。
7.3.4 Retrieval(检索式记忆)
不把所有历史都塞进 Prompt,而是先保存历史,再从长期历史中只取当前相关的部分。它并不等同于向量数据库:
历史信息
↓
建立索引
↓
根据当前问题检索
├─ Vector Search(向量检索)
├─ Keyword Search(关键词检索)
├─ Structured Query(结构化查询)
├─ Metadata Filter(元数据过滤)
└─ Hybrid Search(混合检索)
↓
找到相关记忆
↓
重新注入 Context
优点是 Token 成本低、可跨很久以前的对话查找;风险是检索可能找不到真正相关内容,或找到相似但不相关的内容——Embedding 相似 ≠ 一定真正相关。
向量检索只是 Retrieval Memory 的一种常见实现方式,不是唯一方式。
7.3.5 Structured Memory(结构化记忆)
把重要事实和状态单独保存为明确字段,而不是混在一段聊天文本里:
{
"learning_topic": "LLM",
"current_chapter": 6,
"learning_style": "原理优先",
"understood_topics": ["QKV", "MHA", "GQA", "KV Cache", "RoPE"]
}
优点是信息清晰、容易修改与查询、不依赖语义检索,很适合 Agent 的状态管理;缺点是需要提前设计 Schema,且很难保存复杂的自由文本信息。
实际系统里经常是“结构化记忆 + 检索式记忆”一起使用。
7.3.6 Hybrid Memory(混合记忆)
现代系统通常不会只用一种机制:
最近几轮 → 原文保留(Buffer)
更早的历史 → 摘要(Summary)
长期用户偏好 → 结构化存储(Structured)
很久以前的具体事件 → 检索(Retrieval)
几种实现方式的权衡可以浓缩为:
| 实现方式 | 上下文成本 | 优势 | 主要风险 |
|---|---|---|---|
| Conversation Buffer | 持续增长 | 信息完整、实现简单 | 超出窗口、成本高 |
| Sliding Window | 相对固定 | 大小稳定 | 遗忘早期信息 |
| Summary | 较低 | 保留长期状态 | 摘要失真或遗漏 |
| Retrieval | 低 | 可跨久远历史 | 检索可能不准 |
| Structured | 极低 | 精确、可查询 | 需设计 Schema |
7.3.7 Memory Manager(记忆管理器)
现代 Agent 中常增加一个专门的 Memory Manager,负责决定:什么应该记、什么不用记、保存在哪里、什么时候取出来、哪些旧记忆应更新、哪些已经过期。
用户输入
↓
Memory Manager
├─ 查最近对话
├─ 查长期记忆
├─ 查结构化状态
└─ 查相关历史事件
↓
选择真正相关的信息
↓
Context
↓
LLM
↓
模型输出
↓
Memory Manager
↓
判断是否产生新的长期记忆
一句话总结:Memory 的本质不是模型参数被实时修改,而是应用保存状态,并在后续调用时把相关状态重新放回 Context。
7.4 工具与智能体
7.4.1 工具调用
LLM 本身只能根据当前输入生成 Token。它不会因为“知道搜索引擎”就自动访问网络,也不会因为“会写计算过程”就保证计算正确。
**工具(Tool)**是应用提供给模型的外部能力,例如:
搜索
计算器
数据库查询
文件读取
代码执行
业务 API
一次工具调用通常包括:
模型选择工具并生成结构化参数
↓
宿主程序校验参数和权限
↓
宿主程序真正执行工具
↓
工具返回 Observation(观察结果)
↓
结果重新进入模型上下文
↓
模型生成回答或继续调用工具
最关键的边界是:
模型负责提出调用意图,程序负责决定是否允许并真正执行。
7.4.2 智能体
智能体(Agent):让模型根据当前目标和已有观察,动态决定下一步执行什么工具,直到得到最终结果或触发停止条件。
常见的 ReAct 式循环可以概括为:
目标
↓
选择下一步动作
↓
调用工具
↓
读取观察结果
↓
判断是否还需继续
├→ 是:进入下一轮
└→ 否:返回最终答案
链和智能体的区别:
| 固定链 | 智能体 | |
|---|---|---|
| 路径 | 开发者预先规定 | 模型运行时决定 |
| 可预测性 | 较高 | 较低 |
| 测试难度 | 较低 | 较高 |
| 灵活性 | 适合稳定流程 | 适合步骤无法预先确定的任务 |
| 风险与成本 | 相对容易控制 | 可能循环、误用工具或产生额外调用 |
因此应遵循:
能用固定链完成,就优先使用固定链;只有任务路径确实无法预先写死时,再引入智能体。
7.4.3 工具系统的安全边界
为智能体接入工具时,应至少具备:
- 最小权限:只提供当前任务所需的工具和数据权限
- 参数校验:不要直接执行模型生成的任意命令或查询
- 步骤与预算上限:限制循环次数、Token、时间和费用
- 超时与重试策略:区分可重试错误与永久失败
- 高风险确认:付款、删除、发送、发布等操作在执行前要求人工确认
- 日志与可追踪性:记录模型决定、工具输入、工具输出和最终结果
- 不可信观察处理:网页和文档返回的内容可能包含提示注入,不能自动提升为高优先级指令
7.5 一个与框架无关的 LLM 应用骨架
将本章的通用知识组合起来,可以得到:
用户请求
↓
输入验证与权限判断
↓
加载必要的结构化状态 / 对话上下文
↓
提示词模板
↓
模型调用
├→ 固定链:按预定步骤继续
└→ 智能体:在受控范围内选择工具
↓
结构化解析
↓
Schema、业务规则、事实与安全校验
↓
返回结果,并记录可观测信息
其中每个模块都应该可以替换:
模型可以替换
提示词可以版本化
记忆策略可以替换
工具可以增减权限
编排框架可以更换
验证器可以独立测试
这比把系统完全绑定在某个框架的某组类名上更重要。
7.6 第 7 章总结
本章真正值得保留的知识不是某个 LangChain API,而是以下原则:
- 模型调用需要适配层:正确处理聊天模板、上下文、参数、停止条件和输出格式。
- 链用于确定性编排:把提示词、模型、解析器和校验器组织成可测试步骤。
- 记忆是状态注入:模型不会自动记住历史,应用必须选择并重新提供相关状态。
- 工具由程序执行:LLM 只提出调用意图,权限与执行权应掌握在宿主系统中。
- 智能体是动态控制器:更灵活,也更难测试、更昂贵且风险更高。
- 先确定性,后自主性:能写成固定流程的任务,不应为了“智能体化”而强行智能体化。
- 框架只是实现:LangChain、其他框架或自定义代码都可以承载这些结构,概念本身不依赖框架。
一句话总结:
第 6 章是在设计一次或多次模型调用中的提示词;第 7 章是在把这些调用与状态、验证和工具组织成一个可运行的系统。