HOLLM-第6、7章 从提示到系统 —— LLM 应用工程

6.1 使用文本生成模型 6.1.1 模型选择与提示词模板 选择生成模型时,至少需要考虑: 维度 需要权衡的内容 能力 指令遵循、推理、代码、多语言等是否适合任务 成本 API 费用、显存、延迟和吞吐量 部署 使用云端专有模型,还是本地开源模型 数据 数据是否允许发送到第三方服务 上下文 输入、历史

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 等采样参数往往不起作用

temperature 越高,低概率词元被选中的机会越大

top_p 与 top_k

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

top_p 越高,候选词元集合通常越大

可以粗略记成:

temperature
→ 调整候选概率之间的差距

top_p / top_k
→ 缩小可以参与采样的候选范围
任务 常见倾向
分类、信息抽取、代码修改 低随机性,追求稳定和可复现
摘要、翻译 较低随机性,同时保留一定语言选择空间
头脑风暴、创意写作 适当提高随机性,并生成多个候选进行筛选

Tips:
不同推理框架对参数的实现和默认值可能不同,不能把某一组数值当成通用最佳配置。正确做法是在自己的任务数据上评测。


6.2 提示工程的基本结构

LLM 的基础行为是续写。一个没有明确任务的输入,模型只会预测最可能接在后面的内容:

没有指令时,模型只是继续补全文本

为了把“自动补全”变成可控任务,提示词通常会组合以下组件:

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

基本指令提示词由指令和相关数据构成

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

一个由多个模块组成的复杂提示词

一个更稳妥的通用骨架是:

任务:准确说明目标动作。
依据:只允许使用哪些材料。
约束:长度、范围、禁止项和失败时的处理方式。
格式:明确字段、顺序和数据类型。
数据:使用明显分隔符包住待处理内容。

例如:

任务:判断评论情感。
标签:只能是 positive、neutral、negative 之一。
规则:没有明显倾向时输出 neutral,不要解释。
评论:<review>这部电影还可以,但不会再看。</review>
输出:

6.2.1 提示词设计的几个原则

  1. 具体:把“写一段介绍”改为“面向初学者,用不超过 120 字解释,并给出一个例子”。
  2. 提供边界:说明允许依据什么;资料不足时应返回什么,而不是要求模型猜测。
  3. 分隔数据:把指令与外部文本明确隔开,降低把数据误当成指令的概率。
  4. 明确格式:自动化场景尽量规定字段和类型,不依赖自然语言猜测。
  5. 使用示例:如果格式或判断边界难以描述,直接给一个合格示例。
  6. 迭代评测:提示词修改后,应在固定测试集上比较准确率、稳定性、成本和延迟。

补充理解:
“如果不知道就说不知道”只能给模型一个行为倾向,不能从机制上消除幻觉。若任务要求事实可靠,还需要检索、引用、规则校验或人工复核。


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 输出

可以让另一次模型调用按照规则检查、评分或修改前一次输出:

使用 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 章的链式提示本质一致,只是工程化后,每一步有了明确接口。

一个稳健的多步骤链应做到:

  1. 每一步只承担清晰、有限的职责。
  2. 中间结果尽量使用结构化字段,而不是难以解析的大段自由文本。
  3. 每一步都可以单独重试、缓存和测试。
  4. 在进入下一步前校验关键字段。
  5. 记录使用的模型、提示词版本、参数、耗时和错误。

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,而是以下原则:

  1. 模型调用需要适配层:正确处理聊天模板、上下文、参数、停止条件和输出格式。
  2. 链用于确定性编排:把提示词、模型、解析器和校验器组织成可测试步骤。
  3. 记忆是状态注入:模型不会自动记住历史,应用必须选择并重新提供相关状态。
  4. 工具由程序执行:LLM 只提出调用意图,权限与执行权应掌握在宿主系统中。
  5. 智能体是动态控制器:更灵活,也更难测试、更昂贵且风险更高。
  6. 先确定性,后自主性:能写成固定流程的任务,不应为了“智能体化”而强行智能体化。
  7. 框架只是实现:LangChain、其他框架或自定义代码都可以承载这些结构,概念本身不依赖框架。

一句话总结:

第 6 章是在设计一次或多次模型调用中的提示词;第 7 章是在把这些调用与状态、验证和工具组织成一个可运行的系统。

HOLLM-第3章 LLM 的内部机制:从词元向量到下一个词元 2026-09-24
HOLLM-第 8 章定位:语义搜索与 RAG 2026-09-28

评论区