《Prompt Engineering for LLMs:实用指南》

目录

第一部分:理解你的工具——面向 Prompt Engineer 的 LLM 基础

  • 第一章:LLM 如何处理你的指令
    • 核心内容:Tokenization 对 Prompt 的影响与优化;理解和管理 Context Window 限制;利用 LLM 的自回归特性(无法回溯)指导 Prompt 设计;掌握信息流方向对指令排序的重要性。
  • 第二章:控制 LLM 的输出
    • 核心内容:使用 Temperature 等参数调整输出的确定性与创造性;通过 Logprobs 评估模型置信度;设置 Stop Sequences 精确控制生成结束。

第二部分:核心技术——打造高效能 Prompt

  • 第三章:核心原则——让 Prompt 像训练数据
    • 核心内容:应用“小红帽原则”,模仿常见模式构建 Prompt。
  • 第四章:明确指令与期望格式
    • 核心内容:编写清晰指令的技巧;Few-Shot 示例的应用与陷阱。
  • 第五章:结构化你的 Prompt
    • 核心内容:选择合适的文档结构(对话、报告、结构化文档);有效格式化信息片段(Snippets)的技巧。
  • 第六章:搜集与筛选关键信息
    • 核心内容:区分和利用静态与动态内容;寻找上下文(Context)的方法;应用 RAG 引入外部知识(Lexical 与 Neural Retrieval);长文本摘要技术(Summarization)。
  • 第七章:组装与优化 Prompt 内容
    • 核心内容:基于重要性和依赖关系选择与排序 Prompt 元素;应用组装策略(如 Additive/Subtractive Greedy)管理 Token 预算;处理可变长度上下文(Elastic Snippets)。

第三部分:进阶应用——构建智能体与工作流

  • 第八章:引导 LLM 深度思考
    • 核心内容:应用 Chain-of-Thought Prompting 提升复杂任务表现;实施 ReAct 模式解决多步骤推理和信息检索问题。
  • 第九章:赋予 LLM 行动能力——工具使用
    • 核心内容:定义和调用工具的最佳实践;理解工具调用的内部机制与 Prompt 设计;安全地处理工具执行与错误。
  • 第十章:构建对话式智能体(Conversational Agents)
    • 核心内容:管理对话状态与历史信息;在多轮对话中整合上下文(用户输入、历史、工具结果、Artifacts);设计有效的用户交互界面。
  • 第十一章:自动化复杂任务——LLM 工作流(Workflows)
    • 核心内容:将复杂目标分解为任务;实现 LLM 驱动的任务;构建和优化工作流(Pipeline, DAG, Cyclic Graph);探索高级工作流模式(如 LLM 驱动路由、状态化任务智能体)。

第四部分:质量保证——评估与迭代

  • 第十二章:评估你的 Prompt 和应用
    • 核心内容:实施 Offline 评估(Example Suites, Functional Testing, LLM-as-Judge);开展 Online 评估(A/B 测试);选择和解读评估指标。

第一部分:理解你的工具——面向 Prompt Engineer 的 LLM 基础

第一章:LLM 如何处理你的指令

要成为一个能巧妙运用 Prompt 解锁 LLM 知识和处理能力的“LLM 沟通大师”,你首先需要理解 LLM 是如何处理信息——即它们如何“思考”的 。本章将剥茧抽丝,带你了解那些对于设计高效 Prompt 至关重要的 LLM 运作机制。  

1. Tokenization 对 Prompt 的影响与优化

我们习惯将文本视为字符序列,但 LLM 并非如此看待 。LLM 通过一个称为 Tokenizer 的组件,将你输入的文本(Prompt)分解为一系列多字符的块,这些块被称为 Tokens 。这些 Tokens 通常是 3 到 4 个字符长,但也可能包含常见的单词或字母序列 。模型处理的是这些 Tokens(内部表示为数字),而不是单个字符。在你收到回复前,模型生成的 Tokens 序列会被转换回文本 。  

这对你的 Prompt 设计意味着什么?

  • 理解 Token 边界: LLM 使用的是确定性的 Tokenizer 。这意味着像 “ghost” 这样的词可能是一个 Token,而拼写错误的 “gohst” 则会被分解成 “g-oh-st” 三个 Tokens。虽然 LLM 通常对训练集中常见的拼写错误有一定抵抗力 ,但罕见字符或非英文文本会被分解成更短、更多的 Tokens,这会影响效率。例如,一个 Unicode 笑脸 ☺ 可能被分成两个 Tokens 。  
  • 避免需要拆解 Token 的任务: LLM 难以执行需要分解或重组 Token 内部字符的任务。例如,要求 LLM 反转单词中的字母通常效果很差,因为它不直接操作字母。
    • 优化技巧: 如果你的任务包含需要操作 Token 内部结构的部分(如检查特定字母开头、统计字母出现次数),考虑将这部分逻辑移到应用的前处理或后处理中完成,而不是交给 LLM 。  
  • 注意大小写和格式: 人类认为大写 ‘A’ 和小写 ‘a’ 差别不大,但对 LLM 而言,包含它们的 Tokens 可能完全不同。例如,”strange new worlds” 可能被 Tokenize 为 4 个 Tokens,而 “STRANGE NEW WORLDS” 可能变成 6 个 Tokens。频繁要求 LLM 在大小写之间转换会增加其处理负担,可能影响核心任务的质量。
    • 优化技巧: 尽量保持 Prompt 内文本格式(尤其是大小写)的一致性,避免不必要的转换负担。

2. 理解和管理 Context Window 限制

LLM 处理的文本长度是有限的,这个限制称为 Context Window 。它指的是 LLM 在一次处理中能够“看到”和考虑的最大 Token 数量 。  

  • Prompt + Completion ≤ Context Window: 你输入的 Prompt 的 Token 数量必须小于 Context Window 大小 。同时,Prompt 加上 LLM 生成的 Completion 的总 Token 数也不能超过这个限制 。  
  • Token 数量决定成本和速度: 模型处理 Prompt 的时间大致与 Prompt 的 Token 数成正比 。生成 Completion 的时间与生成的 Token 数成正比 。计算成本通常也是按处理和生成的总 Token 数收费的。因此,精确计算 Token 数对于控制成本和预期延迟至关重要。  
  • Token 计数工具: 你需要使用与你的目标 LLM 相匹配的 Tokenizer 来计算 Token 数 。可以使用如 Hugging Face 或 tiktoken 等库来实现 。记住,字符数和 Token 数之间没有固定换算比例,英文通常每 Token 约 4 字符,其他语言或特殊字符效率更低。
    • 优化技巧: 时刻关注你的 Prompt 的 Token 总量,确保其在目标模型的 Context Window 限制之内。在后续章节中,我们将学习如何筛选和压缩信息以适应这一限制。

3. 利用 LLM 的自回归特性(无法回溯)指导 Prompt 设计

LLM 是 自回归(Auto-Regressive) 模型 。这意味着它们通过一次预测一个 Token 来生成文本 。每预测一个新 Token,这个 Token 就会被添加到已有序列的末尾,成为预测下一个 Token 的输入的一部分。  

  • 无法编辑或回溯: 一旦一个 Token 被生成,LLM 就无法返回去修改它 。它也不会像人类写作那样,意识到之前的错误并进行修正说明 。
    • 设计含义: 这意味着如果模型早期生成了错误或不理想的内容,它可能会继续沿着这个错误的方向生成下去 。你需要设计应用逻辑来检测和处理这种情况,而不是期望模型自我修正 。  
  • 可能陷入重复: 由于模型倾向于延续已有的模式,有时它们会偶然创建一个模式并难以跳出,导致输出非常重复。模型不会感到“无聊” 。
    • 应对策略: 在应用层面检测并过滤掉重复内容,或者使用稍高的 Temperature 来增加输出的随机性。

4. 掌握信息流方向对指令排序的重要性

LLM(特指基于 Transformer Decoder 的模型,如 GPT 系列)处理信息具有明确的方向性。其内部结构,特别是 Attention 机制,使得信息主要 从左到右(或者说,从前到后)流动 。  

  • 单向信息流: 在生成文本时,每个位置的 Token 只能“关注”到它之前(左侧)的 Tokens,而不能“看到”它之后(右侧)的 Tokens。这意味着模型无法预知后面的内容来调整当前的生成。
  • “思考”必须“出声”: LLM 没有真正的内部思考过程。如果需要模型进行多步推理或在回答前进行分析,这个“思考过程”必须作为文本显式地生成出来(例如,使用 Chain-of-Thought Prompting)。后续生成的 Token 会基于之前生成的“思考”文本。
  • 指令顺序至关重要: 由于模型一次性从头到尾处理 Prompt 且无法回头看,它在处理文本开头部分时,并不知道结尾处会有什么关键指令或问题。如果一个任务的指令或关键信息放在 Prompt 的末尾,模型在处理前面内容时可能没有给予足够关注,导致最终效果不佳。
    • 优化技巧: 将最重要的指令、任务描述或问题放在 Prompt 的开头。如果需要在末尾重申或聚焦问题(Refocus),确保模型在此之前已经接收到了所有必要的上下文信息。对于需要模型关注特定细节的任务,应尽早提供这些细节。

理解这些基础运作机制是进行有效 Prompt Engineering 的第一步。掌握了 LLM 如何“看待”和处理文本,你就能更有针对性地设计 Prompt,引导模型产生期望的输出。


第二章:控制 LLM 的输出

上一章我们了解了 LLM 如何处理输入,现在我们将聚焦于如何控制 LLM 的输出,确保它不仅生成内容,而且生成的是你真正需要的内容。本章将介绍三个关键控制手段:调整生成策略(如 Temperature)、利用 Logprobs 评估置信度,以及使用 Stop Sequences 精确结束生成。

1. 使用 Temperature 等参数调整输出的确定性与创造性

LLM 在生成每个 Token 时,实际上是计算了词汇表中所有可能 Token 的概率 。选择哪个 Token 作为最终输出的过程称为 Sampling 。最常用的控制 Sampling 过程的参数是 Temperature。  

  • Temperature 的作用: Temperature 是一个大于等于 0 的数值,它决定了模型在选择下一个 Token 时的“创造性”或随机性程度 。
    • Temperature = 0: 模型总是选择概率最高的那个 Token 。这使得输出接近确定性(但可能因浮点数精度问题略有差异 ),推荐在需要高准确性和可重复性的场景下使用。但有时可能导致输出陷入重复模式 。  
    • Temperature 0.1 – 0.4: 如果有多个 Token 的概率都非常接近最高者,模型会有较小的几率选择次优选项 。适用于需要少量不同解决方案(例如,你有方法筛选最佳方案)或希望输出比 Temperature=0 时更“多彩”、更有创意一点的场景。  
    • Temperature 0.5 – 0.7: 允许偶然性对结果产生更大影响,即使某个 Token 概率明显低于最优选项,也有可能被选中 。适用于需要大量(例如 10 个以上)独立解决方案的场景 。  
    • Temperature = 1: Token 的选择分布将镜像训练集中的统计分布 。例如,如果训练集中 “One, Two,” 后面 51% 是 “Buck”,31% 是 “Three”,那么多次运行后,模型大约会以这个比例生成这两个 Token。这有助于生成符合训练数据普遍特征(如列表长度)的输出。  
    • Temperature > 1: 输出比训练集更“随机”,模型更倾向于选择非标准、甚至奇怪的续写。高 Temperature 可能导致输出质量逐渐下降,因为模型可能会模仿之前生成中的错误,并在此基础上引入更多随机错误,使得文本像“喝醉了”一样。
  • 权衡: 低 Temperature 带来确定性和准确性,但也可能导致重复;高 Temperature 带来多样性和创造性,但也可能牺牲准确性和连贯性。根据你的具体需求(准确性、多样性、生成数量)来选择合适的 Temperature 值。
  • 其他 Sampling 方法: 除了 Temperature Sampling,还有 Beam Search 等方法,它会向前看几个 Token 来选择整体概率最高的序列,可能产生更准确的结果,但计算成本更高,较少在应用中使用。

2. 通过 Logprobs 评估模型置信度

除了最终选择的 Token,LLM 还能提供关于它在做出选择时的“想法”——即每个可能 Token 的概率。这些概率通常以 Logprobs(概率的自然对数)的形式返回。

  • 理解 Logprobs: Logprob 是一个负数(或 0)。数值越接近 0,表示模型认为该 Token 是下一个 Token 的可能性越高 。Logprob 为 0 意味着模型 100% 确定 。你可以使用 exp() 函数将 Logprob 转换回标准概率 。  
  • 获取 Logprobs: 许多 LLM API(如遵循 OpenAI 规范的)允许你通过设置特定参数(例如 logprobs=True 或指定返回 Top N Logprobs)来获取这些值。获取 Logprobs 通常不增加额外的计算负担,因为模型内部已经计算了这些概率 。但需注意,部分商业模型可能出于保密原因禁用此功能 。  
  • 评估生成质量: Logprobs 可以反映模型对其生成的每个 Token 的“信心”。
    • 平均 Logprob/Probability: 计算整个 Completion 或其开头部分 Token 的 Logprobs(或转换后的 Probabilities)的平均值,可以作为衡量整体生成质量或模型信心的指标。高平均值通常意味着更高质量和置信度。
    • 应用场景: 这个质量指标可用于:设置置信度阈值(例如,只在模型高度自信时显示修正建议);在模型信心不足时发出警告、请求更多上下文或重试;切换到更强大的模型;或者,只有在高度确定需要帮助时才主动打扰用户(避免成为像 Clippy 那样的助手)。
    • 结合 Temperature: 你可以通过设置较高的 Temperature 生成多个候选 Completion,然后利用 Logprobs 计算每个 Completion 的置信度得分,并选择得分最高的那个作为最终输出 。使用 API 的 n 参数可以并行生成多个 Completion 。  
  • 用于分类任务: 在分类任务(如判断情感、回答“是/否”问题)中,比较不同类别对应 Token(例如 “Yes” 和 “No”)的 Logprobs 可以得出模型的预测及其置信度。你可以通过调整 Logprobs 的阈值或添加 Logit Bias 来校准模型的决策边界,使其更符合你的业务需求。
  • 分析 Prompt 本身: 通过设置 API 参数(如 echo=True)获取 Prompt 中每个 Token 的 Logprob,可以帮助识别模型认为“意外”或信息密度高的部分,例如检测拼写错误或理解文本的难点。

3. 设置 Stop Sequences 精确控制生成结束

默认情况下,LLM 会一直生成 Token,直到达到模型内置的结束标记(如 End-of-Text Token )、达到你设置的最大 Token 数限制(max_tokens),或者有时根据训练(如 Chat 模型生成 <|im_end|> )自然停止。然而,很多时候你需要更精确地控制生成在何处停止,以避免生成多余内容、节省成本和降低延迟。  

  • 使用 Stop Sequences: 许多 API 允许你提供一个 Stop Sequences 列表 。当模型生成的文本匹配到列表中的任何一个字符串时,生成过程会立即停止 。
    • 选择 Stop Sequences: 选择那些明确标志着你所需内容结束、后续内容不再需要的字符串。例如,在 Markdown 报告中,下一个章节标题的模式(如 \n##)可以作为 Stop Sequence。在代码生成中,下一个函数或类的定义(如 \ndef 或 \nclass,注意前面的换行符以区分缩进的方法定义)可能是合适的 Stop Sequence。
    • 优势: 这是最直接、最有效的控制生成长度的方式,能最大限度地节省时间、计算资源和费用 。  
  • 结合 Streaming 和取消: 如果模型 API 支持 Streaming 模式(即逐个或小批量返回 Token),你可以在客户端监控生成的文本。一旦检测到表示结束的模式(即使该模式不适合作为固定的 Stop Sequence),你可以发送取消生成的请求 。
    • 优势与劣势: 这种方法更灵活,可以处理更复杂的结束条件。但由于网络延迟,取消指令不会立即生效,因此节省的成本不如 Stop Sequences 多 。  
    • 结合使用: 你可以将最常见的结束模式设为 Stop Sequences,同时使用 Streaming 和取消来处理其他不常见的结束情况 。  

熟练运用这些控制手段,你就能更好地驾驭 LLM 的输出,使其更精确、高效地满足你的应用需求。


第二部分:核心技术——打造高效能 Prompt

第三章:核心原则——让 Prompt 像训练数据

在构建 Prompt 时,首要且贯穿始终的原则是:让你的 Prompt 尽可能地像 LLM 在训练期间“见过”的文本。 我们称之为 “小红帽原则” 。  

为什么这个原则如此重要?

回想第一部分,LLM 的核心能力是基于其庞大的训练数据进行文本补全或模仿。它们学习了网络上、书籍中、代码库里等各种文本的模式、结构和风格。当你的 Prompt 与这些训练数据中的常见模式越相似,LLM 就越容易“理解”你的意图,并生成符合预期的、稳定可靠的 Completion 。  

想象一下小红帽的故事:当她沿着熟悉的、安全的路径前进时,一切顺利;一旦偏离路径进入陌生的森林,就遇到了麻烦。你的 Prompt 也是如此。如果你构建的 Prompt 结构混乱、语言晦涩或者格式前所未见,就等于把 LLM 带离了它熟悉的“路径”,其输出的可预测性和质量就会大大降低。

如何应用“小红帽原则”?

  1. 模仿常见文档类型: 根据你的任务,思考它最接近哪种常见的文档类型,并模仿其结构。
    • 代码相关任务? 让 Prompt 像一个包含注释、函数签名或代码片段的源文件 。  
    • 问答或指令? 模仿对话记录或 Q&A 格式。
    • 分析或报告? 使用报告结构,包含标题、引言、正文、结论等部分。
    • 数据提取? 可以模仿包含结构化数据的文本,如带有表格或列表的文章。
    • 即使是 Chat 模型, 其训练数据也是结构化的对话记录(如 ChatML 格式 )。在用户或系统消息内部,模仿常见的文本模式依然有效。  
  2. 使用标准格式和标记: 利用 LLM 熟悉的标记语言来帮助它理解结构。
    • Markdown: 由于其在网络上的广泛使用,LLM 对 Markdown 非常熟悉。使用 # 定义标题层级,用 * 或 – 创建列表,用 ““` 包裹代码块等,都能帮助模型更好地解析内容结构。
    • 结构化数据格式(如适用): 如果任务涉及处理或生成结构化数据,可以考虑在 Prompt 中使用(或要求模型生成)如 JSON、XML 或 YAML 等常见格式。这些格式在技术文档和数据交换中很常见,模型通常能够理解和处理。
  3. 保持语言自然和规范: 使用清晰、语法正确的语言。避免行话、俚语(除非目标是模仿特定角色)或过于复杂的句子结构。如果你的 Prompt 看起来像一篇写得很好的、符合逻辑的文章或对话,模型的输出也更倾向于如此。
  4. 优先使用现有模式,而非创造新模式: 尽量利用 LLM 已经从训练数据中学到的模式来表达你的需求,而不是发明一套全新的指令格式或数据表示法 。  

如何了解模型熟悉的模式?

虽然 LLM 提供商通常不公开具体的训练数据,但你可以通过一些方法来探索模型熟悉的模式:

  • 直接提问: 向模型询问特定任务或信息通常以哪种文档格式出现。例如,问:“描述公司财务信息的正式文件有哪些类型?” 。  
  • 请求示例: 要求模型生成一个特定类型文档的例子(如一份市场分析报告的提纲、一个 Python 函数的文档字符串),观察其结构和用语 。  
  • 观察 Chat 模型的行为: 对于 Chat 模型,观察其默认的交互方式、如何格式化代码或列表、如何使用表情符号等,这些都反映了其训练数据的特征。

总结:

“小红帽原则”——即让 Prompt 模仿 LLM 训练数据的常见模式——是构建高效 Prompt 的基石。当你遵循这条原则,使用清晰的语言、标准的格式和熟悉的结构时,你就为 LLM 指明了通往高质量输出的“安全路径”,从而提高其理解力和生成结果的可靠性。在后续章节中,我们将具体探讨如何应用这一原则来选择文档类型、格式化内容以及给出清晰指令。


第四章:明确指令与期望格式

仅仅让 Prompt 看起来像训练数据是不够的,你还需要清晰地告诉 LLM 你希望它做什么,以及你期望的输出格式是什么样的。本章将介绍两种关键方法:给出明确的指令和使用 Few-Shot 示例。

1. 编写清晰指令的技巧

向 LLM 解释你的需求,比向人类解释更需要清晰和精确,因为 LLM 无法像人类那样通过追问来澄清歧义。清晰的指令有助于确保模型稳定地、一致地理解并执行任务。

  • 明确说明要做什么(和不要做什么): 直接告诉模型你的要求。例如,“使用 Markdown 格式”,“不要包含超链接” 。有时,详细列出“Do’s and Don’ts”清单是必要的,尤其是在复杂的工业级应用中。  
  • 正面指令优于负面指令: 尽量告诉模型“要做什么”,而不是“不要做什么”。例如,与其说“不要写得太长”,不如说“请保持回答简洁,限制在两段以内”。
  • 解释原因: 如果可能,为你的指令提供理由。这有助于模型更好地理解约束的重要性。例如,与其说“不要包含 2024-03-03 之后的信息”,不如说“请不要引用 2024-03-03 之后的日期,因为你的知识截止于此” 。  
  • 避免绝对化(适当时): 有时过于绝对的指令(如“绝不”)可能适得其反。考虑是否可以用更灵活的表述,比如“尽量避免……”或“仅在……情况下……”。
  • 利用系统消息(针对 Chat 模型): 对于基于 RLHF 的 Chat 模型(如 ChatGPT、Claude),系统消息是放置明确指令的最佳位置,因为模型经过训练会特别注意并遵循系统消息中的指示。

虽然经过 RLHF 训练的模型通常更擅长遵循指令 ,但没有模型是完美的。明确、具体的指令是提高模型依从性的第一步。  

2. Few-Shot 示例的应用与陷阱

除了直接说明,另一种强大的指令方式是 演示。通过在 Prompt 中包含几个完整的输入-输出示例(称为 Few-Shot Prompting),你可以非常有效地向 LLM 展示你期望的行为和输出格式。

  • 为什么 Few-Shot 有效?
    • 模式学习: LLM 极其擅长识别并延续 Prompt 中的模式 。示例提供了一种隐式的、基于模式的指令,往往比明确的规则更有效。  
    • 演示格式和风格: 示例是传达精确输出格式(如 JSON 结构、特定的措辞风格、列表格式等)的绝佳方式。
    • 设定微妙预期: 示例可以帮助塑造模型的回应“人设”,例如是应该像一个严厉的评论家还是一个友好的助手。
    • 处理隐性规则: 很多时候,我们对于期望的输出有种“我看到就知道”的感觉,但很难用明确规则完全描述。Few-Shot 示例在这种情况下特别有用 。  
  • Few-Shot Prompting 的陷阱与注意事项:
    • 陷阱 1:处理长上下文时扩展性差 (Scales poorly with context)
      • 如果你的主要问题本身包含大量上下文信息(例如,用户详细的个人资料),那么为 Few-Shot 示例构建同样丰富的上下文会非常困难,并且可能超出模型的 Context Window。即使能放下,大量相似但属于不同示例的重复信息也容易让模型混淆。
      • 应对: 使用简化的示例可能无法传达足够的细微差别。一个折衷方案是,用 Few-Shot 示例主要演示输出格式或其他特定方面,而不是试图完全复制主问题的复杂性。
    • 陷阱 2:示例引入偏见 (Biases the model toward the examples)
      • 示例会给模型一个关于“典型”情况或数值分布的预期,这可能不符合现实,从而影响模型的判断(称为 Anchoring 效应)。例如,如果示例中评分 1 到 5 均匀分布,模型可能在缺乏信息时倾向于猜测中间值 3,即使现实中 5 星评价最常见。
      • 应对: 尽量让示例的分布接近你所知的真实分布。如果有现成的真实历史数据,可以从中抽取有代表性的样本作为示例 。同时,为了确保覆盖边界情况 (Edge Cases),即使它们在现实中不常见,也值得在示例中包含,因为这能明确告知模型如何处理这些特殊情况。  
    • 陷阱 3:可能暗示虚假模式 (Suggest spurious patterns)
      • 少量示例可能偶然呈现出某种模式(如数值升序或降序、特定类型问题总对应特定类型答案),模型可能会捕捉到这种虚假模式并错误地延续下去。例如,如果示例总是先展示成功案例再展示失败案例,模型可能会在处理你的主问题时也倾向于预测失败。
      • 应对: 注意示例的顺序。如果可能,随机打乱示例顺序 。系统性地选择示例以覆盖不同类别和边界情况,而不是按某种简单逻辑排序。使用 Prompt 优化工具(如 DSPy)可能有助于系统性地选择和排序示例。  
  • 何时使用 Few-Shot?
    • 当你需要精确控制输出格式或风格时。
    • 当任务的某些方面难以用明确指令说清楚时。
    • 当你需要明确告知模型如何处理特定边界情况时。
    • 注意: 如果任务本身对模型来说已经足够清晰,或者你的 Context Window 非常紧张,那么不必强行使用 Few-Shot。它会增加 Prompt 长度并可能引入上述问题。

通过结合使用清晰的指令和精心挑选的 Few-Shot 示例,你可以更有效地引导 LLM 理解你的需求,并生成符合期望格式和内容的输出。


第五章:结构化你的 Prompt

仅仅收集了信息片段和明确了指令还不够,你还需要将它们有效地组织成一个连贯的 Prompt。本章将探讨如何选择合适的文档结构来承载你的 Prompt 内容,以及如何格式化信息片段(Snippets),使其既清晰又高效。

1. 选择合适的文档结构

将 Prompt 和最终的 Completion 视为一个完整的“文档”。遵循“小红帽原则”,选择一个 LLM 在其训练数据中可能经常遇到的文档类型,有助于模型更好地理解你的意图并生成可预测的输出 。以下是几种常用且有效的文档结构:  

  • 对话/建议型对话 (Advice Conversation):
    • 结构: 模拟两人之间的对话,通常是一方寻求帮助,另一方(模型扮演的角色)提供建议。对于 Chat 模型(如使用 ChatML 的模型),这是最自然的结构。即使是 Completion 模型,也可以通过模仿对话脚本格式来实现。
    • 优点: 交互自然,易于人类理解和构建;适合多轮交互,可以将复杂问题分解;易于集成工具使用(无论是 Chat 模型还是 Completion 模型)。对于 Completion 模型,可以通过“扮演”回答者的角色来精确控制回答的起始部分(Inception 方法)。
    • 格式示例(Completion 模型): 可以是自由格式的叙述 ,脚本格式(如 Me: … Husband: …) ,无标记格式(直接换行分隔) ,或结构化标记格式(如 <me>…</me>)。结构化标记格式最清晰。  
    • 适用场景: 问答、任务指导、需要多轮交互澄清或执行步骤的场景、Chatbot 应用。
  • 分析报告 (Analytic Report):
    • 结构: 模仿商业、科学或学术报告的格式,通常包含引言、背景、分析、结论等部分。
    • 优点: 格式规范,LLM 非常熟悉;易于通过“范围”(Scope) 部分明确界定任务边界,模型通常能更好地遵守;倾向于客观分析,减少模型模拟社交互动的认知负荷 ;非常适合需要详细推理过程的任务(如 Chain-of-Thought) 。  
    • 推荐格式: 强烈推荐使用 Markdown 编写报告式 Prompt 。Markdown 通用、简洁、易于渲染,其标题有助于组织结构,代码块能处理需要精确缩进的内容。还可以利用 Markdown 的链接和目录功能来引导模型或控制输出(例如,通过目录添加“草稿区”或设置“附录”作为停止点)。  
    • 适用场景: 需要进行分析、评估、比较或总结的任务;需要详细推理过程的任务;需要生成结构化书面输出(如报告、文档草稿)的场景。
  • 结构化文档 (Structured Document):
    • 结构: 使用具有明确规范的标记语言(如 XML、YAML,有时也用 JSON)来组织 Prompt 内容。每个信息片段都对应文档中的一个特定元素或字段。
    • 优点: 格式极其明确,便于模型理解和应用逻辑解析输出;非常适合需要提取或生成精确结构化数据的任务;可以包含元数据(如 XML 属性或 YAML 字段)来传递额外信息;易于通过添加特定标签(如 Anthropic 的 <antThinking> 和 <antArtifact>)来引导模型进行内部思考或生成特定类型的输出。
    • 格式选择:
      • XML: 适合层级结构,标签可以包含属性,适合较短的元素内容。需要注意转义字符。
      • YAML: 通过缩进来表示层级,特别适合需要精确控制缩进的内容(如代码)。多行文本字段处理更方便,无需转义。
      • JSON: 虽然可读性稍差且需要大量转义,但由于其在 API(尤其是 OpenAI 的工具 API)中的广泛使用,模型(特别是 OpenAI 的模型)对其处理能力较强。
    • 适用场景: 数据提取、数据生成、需要精确控制输出结构的任务、与需要结构化输入的系统集成。

2. 有效格式化信息片段(Snippets)的技巧

选择好文档结构后,你需要将收集到的信息(上下文、指令、示例等)转化为适合放入该结构的 Snippets。格式化的目标是实现:

  • 模块化 (Modularity): Snippet 应该是可以相对独立地插入或移除的文本块,便于组装和调整 Prompt。
  • 自然性 (Naturalness): Snippet 应该像是所选文档类型中有机的一部分。例如,在代码 Prompt 中,自然语言信息应放入注释;在对话中,API 返回的数据应转换成自然的对话语句。
  • 简洁性 (Brevity): 在不损失信息的前提下,尽量用更少的 Token 表达内容 。  
  • 惰性/独立性 (Inertness): 理想情况下,一个 Snippet 的 Tokenization 不应影响相邻 Snippet 的 Tokenization,以便能独立计算其 Token 长度。这通常通过在 Snippets 之间使用明确的分隔符(如换行符或空格)来实现,但要注意 Tokenizer 处理空格和换行符的特定行为(例如,GPT Tokenizer 通常包含以空格开头的 Token,但不包含以空格结尾的 Token;会合并多个换行符)。

格式化示例:

  • 对话结构: 将 API 返回的数据(如天气信息)编织进对话轮次中,模拟一方提问,另一方回答。
  • 报告结构: 将 API 调用结果或特定信息点格式化为报告中的一个带有标题的简短段落或列表项。
  • 结构化文档: 将信息直接映射到预定义的 XML 标签或 YAML 字段中 。  
  • 旁注/显式标记: 对于一些辅助性上下文,可以使用明确的旁注格式(如 “// <consider this snippet from …>” 或 “As an aside, …”)来引入,提示模型这是参考信息,但不强制其必须以特定方式使用。

格式化 Few-Shot 示例:

你可以明确地将示例标记为“示例”,或者更自然地将它们作为文档中先前已完成的任务或对话轮次来呈现。后者尤其适用于对话结构,能让模型认为自己之前已经成功地以某种方式解决了类似问题,从而更有可能在当前任务中继续沿用该模式。

通过精心选择 Prompt 的整体结构,并细致地将信息片段格式化融入其中,你可以构建出既清晰传达意图、又便于 LLM 处理的高效能 Prompt。


第六章:搜集与筛选关键信息

构建高效 Prompt 的核心在于为其提供恰当的信息。LLM 的强大之处在于它能处理各种“杂乱”的文本信息,并从中提取模式和意义,但这需要你首先找到并提供这些信息。本章将指导你如何系统地搜集、区分和处理用于 Prompt 的内容,重点关注静态与动态内容、上下文(Context)的查找方法、利用 RAG 引入外部知识以及处理长文本的摘要技术。

1. 区分和利用静态与动态内容

在组织 Prompt 内容时,一个重要的区分是 静态内容(Static Content)动态内容(Dynamic Content) 。  

  • 静态内容:
    • 定义: 通常是硬编码在你的应用或 Prompt 模板中的文本,对于同一类任务,这部分内容基本不变。
    • 作用: 主要用于向 LLM 解释通用任务、澄清问题、给出固定指令或提供 Few-Shot 示例。例如,“请根据以下用户信息推荐一本书”或者“输出格式必须是 JSON”。
    • 来源: 由 Prompt Engineer 根据应用需求预先设计和编写。
  • 动态内容:
    • 定义: 针对每次具体请求而变化的信息,通常是关于当前用户、具体情境或查询对象的细节 。  
    • 作用: 为 LLM 提供解决当前特定问题所需的具体背景信息(上下文) 。例如,“该用户最近阅读了《白鲸》”或者“当前室外温度是 75 华氏度”。  
    • 来源: 在请求时从用户输入、数据库、API 调用、传感器读数等实时或可变来源获取。

虽然有时界限可能模糊(例如,某个指令是通用的静态规则还是针对特定用户的动态约束?),但理解这种区分有助于系统性地规划你需要搜集哪些信息以及它们在 Prompt 中的作用。静态内容定义了“做什么”,动态内容提供了“针对什么做”。

2. 寻找上下文(Context)的方法

动态内容,即上下文,是你花费最多时间和精力的地方,因为它直接关系到 LLM 能否给出个性化和准确的答案 。搜集上下文是一个创造性的过程,但可以借助系统性方法来确保全面性:  

  • 思维导图法 (Mind Mapping):
    • 方法: 以核心问题(如“我该读哪本书?”)为中心,向外发散思考可能影响答案的各种因素。关注问题中的关键词(如“我”、“书”、“下一本”)并进行扩展。例如,“我”可以引申出用户的阅读历史、偏好、人口统计信息等;“下一本”可以引申出用户最近的情绪、目标等 。  
    • 优点: 有助于全面探索可能相关的上下文维度。
    • 局限: 产生的想法可能难以实际获取(如用户隐私数据)。
  • 从可获取性出发 (Availability-First):
    • 方法: 反向思考,列出你的应用系统能够访问到的所有信息源,然后再评估其相关性。可以按以下维度系统梳理:
      • 按信息源远近 (Proximity): 从应用内部状态(如屏幕内容、时间)、已保存数据(用户配置)、可记录数据(用户活动历史)、公共 API 数据(天气、新闻)、到需用户授权数据(购买记录、邮件)排序。通常距离越远,获取越难。
      • 按信息稳定性 (Stability): 从基本不变的信息(用户档案)、缓慢变化的信息(购买历史)、到短暂易变的信息(当前交互状态、时间)排序。稳定性越低,越难提前准备,对实时获取能力要求越高。
    • 优点: 更贴近工程实践,确保考虑的上下文都是可行的。
  • 结合使用: 推荐结合两种方法,先用思维导图发散思考,再用可获取性清单进行筛选和落地。

搜集上下文的考量因素:

  • 延迟 (Latency): 你有多少时间来搜集上下文?实时交互应用(如输入建议)要求极低延迟,可能限制了复杂的上下文检索;而非实时任务(如邮件摘要)则允许更耗时的搜集过程。
  • 可准备性 (Preparability): 哪些动态信息变化缓慢,可以提前计算或缓存,以减少实时延迟?
  • 可比较性 (Comparability): 你搜集到的上下文片段是否能进行重要性比较?你需要一种方法(如下一章将讨论的评分或优先级)来决定在有限的 Prompt 空间内优先包含哪些信息。

3. 应用 RAG 引入外部知识(Lexical 与 Neural Retrieval)

LLM 的知识仅限于其训练数据,无法访问最新事件或私有信息(如公司内部文档、用户个人数据)。直接询问这些信息,模型要么拒绝回答,要么(更糟地)幻觉 (Hallucinate) 出看似合理但错误的答案 。  

检索增强生成 (Retrieval-Augmented Generation, RAG) 是解决此问题的关键技术。其核心思想是:在生成答案之前,先从外部知识库(如公司文档、数据库、网页)中 检索 (Retrieve) 与当前问题相关的最新或私有信息,然后将这些检索到的信息作为上下文注入到 Prompt 中,供 LLM 参考。

RAG 的核心是检索,即如何从大量信息中找到最相关的片段。 常见的检索方法分为两类:

  • 词汇检索 (Lexical Retrieval):
    • 原理: 基于查询字符串和文档片段之间的 词语重叠度 来判断相关性。简单的方法如计算 Jaccard 相似度(考虑共有词 / 总独特词),通常需要移除停用词 (Stop Words) 和进行词干提取 (Stemming)。更复杂的方法如 TF-IDF 或 BM25 会考虑词语的重要性(稀有词权重更高)。
    • 优点: 实现相对简单,技术成熟(如 Elasticsearch、Algolia),速度快(尤其对于中小型文档库),易于调试(不匹配的原因是明确的词语不匹配)。
    • 缺点: 无法理解语义相似性(同义词、不同表述方式无法匹配),对拼写错误、语言差异敏感。
  • 神经检索 (Neural Retrieval):
    • 原理: 使用 Embedding 模型 将文本片段(查询和文档)转换为高维向量(Embeddings),这些向量在“语义空间”中捕捉文本含义。语义相似的文本片段,其向量在空间中距离更近(通常用余弦相似度或欧氏距离衡量)。检索过程变成:将查询转换为向量,然后在向量数据库(如 FAISS, Pinecone)中查找最接近的文档向量,并返回对应的文本片段。
    • 优点: 能理解语义相似性,不受具体用词影响,甚至可以跨语言或跨模态(如文本搜图片)检索。
    • 缺点: 需要预先对所有文档进行 Embedding 并构建索引,成本较高;调试困难(向量的含义不直观,难以理解为何不匹配);对 Embedding 模型和向量数据库有依赖 。  
    • 关键步骤:
      • 文档切片 (Snippetizing): 将长文档切分成大小适中(小于 Embedding 模型限制,通常包含一个核心观点)的片段,可以使用固定窗口滑动或按自然边界(段落、章节)切分。
      • 选择 Embedding 模型: 可以使用托管服务(如 OpenAI)或自托管模型,考虑模型对特定语言或代码的支持度。
      • 选择向量存储: 使用 FAISS 等库或 Pinecone 等托管服务来存储和高效查询向量。

注意: RAG 引入的上下文也可能分散模型注意力。确保检索到的信息确实相关,避免“契诃夫之枪”谬误(模型强行解释不相关的信息)。

4. 长文本摘要技术(Summarization)

当需要作为上下文的信息本身非常长(例如一整本书、一篇长报告、冗长的对话历史),超出了 Context Window 限制时,就需要 摘要 (Summarization) 技术。

  • LLM 用于摘要: LLM 本身就很擅长生成摘要。你可以直接要求模型总结一段文本 。  
  • 处理超长文本——分层摘要 (Hierarchical Summarization):
    • 方法: 当原始文本过长无法一次性放入 Prompt 时,采用分治策略:
      1. 将文本分割成多个较小的、能放入 Context Window 的块(最好按自然边界,如章节、段落分割) 。  
      2. 分别对每个小块进行摘要。
      3. 将这些小块的摘要拼接起来。
      4. 如果拼接后的摘要仍然过长,则重复步骤 2-3,对摘要的摘要进行再摘要,直到最终摘要长度合适。
    • 成本: 只要每层摘要长度远小于原文,总成本主要由原文总 Token 数决定 。  
    • 风险: 存在“谣言传播”风险,即每一层摘要都可能引入或放大误解。
  • 通用摘要 vs. 特定摘要 (General vs. Specific Summaries):
    • 通用摘要: 对文本进行客观、全面的总结。优点是可复用性强,适用于多种后续任务 。  
    • 特定摘要: 针对你的最终应用目标进行摘要,只保留与该目标最相关的信息。例如,为推荐书籍而摘要社交帖子时,只关注提及书籍、阅读偏好或相关主题的内容。优点是摘要更聚焦、更短。缺点是无法复用于其他目标,如果目标改变需要重新摘要 。  
    • 实现特定摘要: 在摘要 Prompt 中明确指示摘要的目标和需要关注的方面,甚至可以通过 Few-Shot 示例来演示 。  

通过有效地搜集上下文、利用 RAG 补充外部知识,并运用摘要技术处理长文本,你可以为 LLM 提供解决特定问题所需的丰富而精炼的信息基础。


第七章:组装与优化 Prompt 内容

在前面的章节中,我们收集并格式化了各种信息片段(Snippets)。现在,是时候将这些零散的构建块组装成一个最终的、高效的 Prompt 了。这不仅仅是简单的拼接,而是一个需要策略性选择、排序和优化的过程,以确保在有限的 Token 预算内最大化 Prompt 的效果。本章将介绍如何根据重要性和依赖关系来筛选和排序内容,应用组装策略来管理 Token 预算,并处理长度可变的上下文。

1. 基于重要性和依赖关系选择与排序 Prompt 元素

在你将所有潜在的 Prompt 元素(包括指令、示例、上下文片段等)准备好后,需要考虑它们之间的关系,这决定了它们如何被组合进最终的 Prompt。主要有三个维度:

  • 位置/顺序 (Position):
    • 含义: 元素应该出现在 Prompt 的哪个位置。
    • 重要性: 错误的顺序会使 Prompt 难以理解。例如,引用的内容应保持原始顺序,对话应按时间顺序,相关信息应归入正确的章节。LLM 对 Prompt 开头和结尾的信息更敏感,中间部分的信息容易被“遗忘”(称为“失落的中间”或“嗯谷”现象,Valley of Meh)。
    • 操作: 在准备元素时记录其逻辑顺序(如使用索引或位置值) 。将最关键的信息(如核心指令、最终问题)放在 Prompt 的开头或结尾,避免放在中间区域 。  
  • 重要性 (Importance):
    • 含义: 该元素对于模型理解任务或生成有用回复的关键程度。
    • 重要性: 在 Token 预算有限的情况下,必须优先包含最重要的信息。重要性不同于位置,例如开头的引言可能比中间的某个细节更重要。
    • 操作: 为每个元素分配一个重要性度量。可以使用:
      • 优先级分层 (Priority Tiers): 将元素分为少数几个重要性等级(如“必须包含”、“重要”、“可选”),优先保留高层级的元素。核心指令和输出格式说明通常是最高优先级。
      • 数值评分 (Numerical Scores): 对同一层级内的元素或所有元素进行更细致的评分,以反映其相对价值。评分时可考虑元素长度,短小精悍的元素通常更受欢迎。重要性评估需要判断,并通过后续测试进行验证和调整 。  
  • 依赖关系 (Dependency):
    • 含义: 一个元素的包含与否如何影响其他元素。
    • 重要性: 忽略依赖关系可能导致 Prompt 逻辑不通或信息冗余/缺失。
    • 常见类型:
      • 需求 (Requirements): 包含元素 A 必须先包含元素 B。例如,必须先介绍人物 Richard,才能说“他来自英国”。
      • 不兼容 (Incompatibilities): 包含元素 A 就不能包含元素 C。常见于同一信息有不同表达方式(如摘要 vs. 详细解释)的情况。
    • 操作: 在准备元素时记录其依赖关系。组装 Prompt 时必须满足所有需求关系,并在不兼容的元素间做出选择。

当你为每个潜在的 Prompt 元素都确定了其位置、重要性和依赖关系后,你就拥有了组装最终 Prompt 所需的所有元数据。

2. 应用组装策略管理 Token 预算

组装 Prompt 的核心是一个优化问题:在满足依赖关系和不超过 Token 预算的前提下,选择一组元素以最大化总重要性(或价值)。这是一个类似于“0-1 背包问题”的组合优化问题,但通常还需要处理依赖关系。虽然没有现成的标准工具,但你可以根据应用需求实现自己的组装引擎。以下是一些常见的策略:

  • 最小化/仅末尾策略 (Minimal/Suffix-Only):
    • 方法: 最简单的方法,尤其适用于初期或对延迟要求极高的场景。按顺序排列所有元素,然后从末尾开始尽可能多地包含元素,直到达到 Token 预算。
    • 优点: 实现简单,速度快。
    • 缺点: 忽略了元素的重要性,可能丢弃开头或中间的关键信息。
    • 适用场景: 对最新信息最敏感的应用(如代码补全、聊天),或者作为快速原型验证。
  • 加性贪心策略 (Additive Greedy Approach):
    • 方法: 从空 Prompt 开始,在每一步迭代中,选择当前价值最高(例如,得分最高或最高优先级)且满足所有依赖关系(其需求已被包含,且不与已包含元素冲突)并且加入后不超过 Token 预算的元素,将其添加到 Prompt 中,直到无法再添加任何元素。最后,将选中的元素按其原始位置排序。
    • 优点: 优先保证高价值内容,实现相对简单,即使候选元素远超预算也能有效筛选。
    • 缺点: 对于复杂的依赖关系(如循环依赖或高价值元素依赖低价值元素)可能不是最优解。
    • 适用场景: 大多数情况下的通用策略,特别是当高价值内容是首要目标时。
  • 减性贪心策略 (Subtractive Greedy Approach):
    • 方法: 从包含所有元素的完整 Prompt 开始(假设能放下),然后迭代地移除价值最低的元素,直到 Prompt 满足 Token 预算。在移除过程中,需要检查并可能移除因依赖关系失效而变得无效的元素 。  
    • 优点: 对于处理元素间的不兼容关系或可变长度元素(见下文)通常更方便。
    • 缺点: 如果初始元素总数非常大,则效率较低;处理复杂依赖关系同样可能非最优。
    • 适用场景: 当大部分元素都比较重要,只需要移除少量内容时;或者需要灵活处理不兼容关系和弹性元素时。

这些只是基本的组装策略原型。你可以根据应用的具体需求进行调整和组合,例如结合贪心算法和有限的替代方案探索。

3. 处理可变长度上下文(Elastic Snippets)

有时,同一份上下文信息可以有不同详细程度的版本。例如,对于一段引文,你可以只引用核心句子,也可以包含周围的段落,甚至整个章节。这种情况下,你可以采用 弹性元素 (Elastic Snippets) 的概念:

  • 方法 1:弹性 Prompt 元素:
    • 操作: 为同一个信息源创建多个版本(从最短到最长),每个版本对应一个 Prompt 元素,并赋予不同的 Token 长度和可能不同的重要性值。在组装时,不再是问“是否包含这个元素?”,而是问“对于这个信息源,我们能放下的最长(或最有价值)的版本是哪个?”。
  • 方法 2:定义不兼容关系:
    • 操作: 将不同详细程度的版本创建为独立的 Prompt 元素,并明确它们之间是不兼容的(即只能选择其中一个)。在组装时,算法会根据重要性评分和可用空间来选择最合适的版本 。  

处理弹性元素使得 Prompt 组装更加灵活,能够在 Token 预算变化时动态调整包含信息的详细程度。减性贪心策略通常更容易处理这种情况。

通过结合对元素重要性、顺序和依赖关系的理解,以及应用合适的组装策略和处理弹性上下文的方法,你可以构建出既能充分利用可用信息、又严格遵守 Token 限制的优化 Prompt。


第三部分:进阶应用——构建智能体与工作流

第八章:引导 LLM 深度思考

基础的 Prompt 技术可以引导 LLM 完成许多任务,但面对需要复杂推理、多步骤规划或结合外部信息的问题时,简单的问答式 Prompt 往往力不从心。LLM 本质上是基于统计模式生成“听起来合理”的文本,它们缺乏真正的内部思考或反思过程。本章将介绍两种强大的进阶 Prompting 技术——Chain-of-Thought (CoT) 和 ReAct——它们通过引导 LLM “思考出声”,显著提升其在复杂任务上的表现。

1. 应用 Chain-of-Thought Prompting 提升复杂任务表现

  • 核心问题: LLM 没有“内心独白”。当你直接问一个需要推理的问题时(例如,“梨子会沉入水中吗?”),模型可能会直接给出一个“是”或“否”的答案,这个答案更像是基于模式匹配的直觉猜测,其后的解释更像是为了合理化这个猜测,而不是真正思考的结果。
  • CoT 的原理: Chain-of-Thought (CoT) Prompting 的核心思想是,在要求 LLM 给出最终答案之前,先引导它生成一步一步的推理过程 。因为 LLM 的生成是自回归的,后面生成的 Token 会受到前面 Token 的影响。当模型先生成了思考步骤后,其最终答案就会基于这些“公开的思考”过程,从而更有逻辑、更可能正确。  
  • 如何实现 CoT?
    • Few-Shot CoT: 在 Prompt 中提供几个示例,每个示例都包含问题、详细的思考步骤和最终答案。模型会学习这种“思考-回答”的模式。例如:

Q: 仓鼠会为其他动物提供食物吗? A: 仓鼠是被捕食的动物。被捕食者是捕食者的食物。因此,仓鼠为某些动物提供食物。所以答案是肯定的。

  • Zero-Shot CoT: 对于足够强大的 LLM,有时甚至不需要提供示例,只需在你的问题后加上一句简单的引导语,如“让我们一步一步地思考”(Let’s think step-by-step)。这也能有效地触发模型的链式思考能力。
  • 效果: CoT 被证明能显著提高 LLM 在多种需要推理的任务上的表现,包括常识问答、数学应用题和符号推理等。例如,在数学应用题上,使用 CoT 可以将模型的解题成功率提升数倍。

2. 实施 ReAct 模式解决多步骤推理和信息检索问题

CoT 主要关注内部推理,但许多现实问题还需要与外部世界互动,比如查找信息或执行操作。ReAct (Reason + Act) 框架将推理与行动结合起来,特别适用于需要检索信息或执行多步骤计划的任务。

  • ReAct 的循环: ReAct 模式引导 LLM 在一个 思考(Thought) -> 行动(Action) -> 观察(Observation) 的循环中工作:
    1. 思考 (Thought): 模型分析当前情况,判断需要什么信息或下一步该做什么。
    2. 行动 (Action): 模型决定执行一个动作,通常是调用一个 工具 (Tool),如 Search[entity](搜索信息)、Lookup[keyword](在已有信息中查找关键词)或 Finish[answer](给出最终答案并结束任务)。
    3. 观察 (Observation): 模型接收执行动作(通常是工具调用)后返回的结果。
    4. 模型基于观察结果进入下一轮思考,如此循环,直到能够 Finish 任务。
  • 如何实现 ReAct?
  • 定义工具: 首先需要定义 LLM 可以使用的工具(下一章将详细介绍工具的使用)。
  • Prompt 指导: 在 Prompt 中明确指示模型遵循 Thought-Action-Observation 的模式。通常需要提供清晰的说明和几个 ReAct 风格的 Few-Shot 示例来演示这个过程。例如,处理 HotpotQA 问题(如“哪个杂志先创刊,Arthur’s Magazine还是First for Women?”)的 ReAct 过程会清晰展示如何通过搜索获取两个杂志的创刊年份,然后比较并得出结论。
  • 效果与重要性:
  • 结合外部信息: ReAct 使 LLM 能够主动获取其知识库之外的信息,显著提高了处理需要实时或特定领域知识的任务的能力。
  • 复杂规划与执行: 对于需要分解目标、制定计划、跟踪进度并处理异常的任务(如在模拟环境中完成任务的 ALFWorld 基准测试),ReAct 中的“思考”步骤至关重要。它帮助模型进行任务分解、注入常识、理解观察结果并调整计划,其表现远超仅有“行动”而缺乏明确“思考”步骤的方法。
  • Fine-tuning 增强: 虽然 ReAct Prompting 本身就有效果,但通过在 ReAct 格式的示例上进行 Fine-tuning,可以进一步显著提升模型性能,甚至让较小的模型达到或超过未 Fine-tune 的更大模型的表现。
  • 超越 ReAct 的思路:
  • Plan-and-Solve: 在 ReAct 的循环开始前,先让模型制定一个整体计划。
  • Reflexion: 在任务执行后(或失败后),让模型反思结果,从错误中学习,并在后续尝试中改进计划。
  • Branch-Solve-Merge: 让多个独立的 LLM 实例(或同一个 LLM 的不同运行实例)从不同角度解决问题,然后将各自的解决方案合并成一个更好的最终方案。

通过应用 Chain-of-Thought 和 ReAct 等技术,你可以引导 LLM 进行更深入、更结构化的“思考”,使其能够处理更复杂的推理任务,并有效地结合外部信息和工具来解决现实世界的问题。


第九章:赋予 LLM 行动能力——工具使用

LLM 本身是强大的语言处理引擎,但它们与外部世界是隔离的。它们无法获取最新信息、执行计算、访问私有数据或直接操作现实世界的系统。为了克服这些限制,“工具使用” (Tool Usage) 应运而生。本章将深入探讨如何让 LLM 使用工具,包括定义和调用工具的最佳实践、理解其内部工作机制以及如何安全地处理执行过程。

1. 定义和调用工具的最佳实践

要让 LLM 有效且可靠地使用工具,你需要精心设计工具的定义和交互方式。遵循以下准则,这些准则基于两个直觉:1)人类更容易理解的东西,LLM 通常也更容易理解;2)模仿训练数据的模式(小红帽原则)效果最好。

  • 选择合适的工具:
    • 限制数量: 避免一次性给模型提供过多工具,这会增加其混淆的可能性 。  
    • 功能划分: 工具应尽可能覆盖所需功能领域,同时避免功能重叠 。  
    • 保持简单: 不要直接将复杂的 Web API 暴露给 LLM。创建更简单、参数更少的封装工具。
  • 命名工具和参数:
    • 有意义且自解释: 名称应清晰反映工具或参数的用途 。  
    • 遵循约定: 如果模型熟悉某种命名约定(例如,OpenAI 模型看到的 TypeScript 风格),尽量遵循该约定(如使用驼峰命名法),避免难以解析的名称(如 retrieveemail)。
  • 描述工具定义:
    • 简洁清晰: 定义应尽可能简单,同时包含足够的信息让模型(或人类)理解如何使用它。避免过度复杂的“法律术语”式描述。
    • 利用先验知识: 如果工具对应一个模型可能已知的公共 API(如 GitHub API),可以设计一个简化版的工具,但保留原 API 的核心概念、命名和风格,以利用模型的预训练知识。
  • 处理参数 (Arguments):
    • 保持简单: 参数数量宜少不宜多。使用清晰的数据类型(如 string, number, integer, boolean),并可利用 enum 和 default 值进一步指导模型。
    • 注意复杂类型和描述: 目前(截至原文写作时),一些复杂的 JSON Schema 约束(如 minItems, pattern)或嵌套参数的描述可能不会被模型(如 OpenAI 的)完全利用。
    • 小心长文本参数: 对于需要 JSON 编码的参数(如 OpenAI 的工具调用),长文本(尤其是包含换行符和引号的代码)需要转义,模型容易出错。XML 等其他编码方式可能对此更友好。
    • 应对参数幻觉: 模型有时会为未在对话中明确提到的参数(如 org, repo)编造占位符值 。应对方法包括:如果应用已知该值,则不在工具定义中包含该参数;提供 default 值;或指示模型在不确定时提问(但模型不一定会遵守)。  
  • 处理工具输出:
    • 预期明确: 工具定义应让模型能预期输出内容的类型(自然语言文本或结构化 JSON)。
    • 避免无关信息: 输出中不要包含过多“以防万一可能有用的”额外信息,这会分散模型注意力。

2. 理解工具调用的内部机制与 Prompt 设计

虽然工具调用感觉与文本补全不同,但其底层机制仍然是 LLM 在完成一个特定结构的“文档”。以 OpenAI 的实现为例:

  • 工具定义在 Prompt 中的表示:
    • 工具定义通常被注入到系统消息中,紧随你提供的初始系统指令之后 。  
    • 它们常被格式化为类似代码的结构(例如,OpenAI 使用类似 TypeScript 的语法),包含函数名、参数(带类型和描述)、返回类型等。这种格式利用了模型在代码理解上的优势,使参数传递更规范(例如,要求命名参数而非位置参数),减少错误。
    • 理解这种表示有助于你编写更有效的工具描述,并估算工具定义占用的 Token 空间。
  • 工具调用和响应在 Prompt 中的表示:
    • 模型通过生成特定的语法来表示它想要调用一个工具。例如,在 OpenAI 的内部实现中,助手消息可能包含 to=functions.function_name {json_arguments} 这样的特殊标记。
    • 这个生成过程是分步决策的:模型先决定是否调用工具,再决定调用哪个工具,然后是选择参数,填充参数值,最后结束调用标记。每一步都是基于前面所有文本的 Token 预测。
    • 工具执行后的结果,由应用程序获取,并使用特定的角色(如 OpenAI 的 tool 角色)和标识符(如 tool_call_id)插入回对话历史中,以便模型在下一轮看到并使用该结果。

理解这个机制的关键在于: 即使是工具调用,LLM 仍然是在进行模式匹配和文本生成。你的 Prompt(包括系统消息、对话历史、工具定义、工具调用和响应的格式)需要共同构建一个清晰、一致的模式,引导模型在正确的时机、以正确的方式“生成”工具调用请求。

3. 安全地处理工具执行与错误

  • 处理工具错误:
    • 当工具执行失败时,将错误信息返回给模型是有价值的,因为模型可能从中学习并修正其行为。
    • 不要直接返回原始错误日志。 错误信息应以模型能理解的方式呈现,并与工具定义相关联。如果是验证错误,指出哪个参数有问题;如果是其他可处理的错误,提供有用的上下文。
  • 执行“危险”工具:
    • 核心原则:绝不允许 LLM 直接触发任何可能对用户产生负面影响(如花费金钱、删除数据、发送重要邮件)的操作,除非得到用户的明确授权
    • 不要依赖 Prompt 指令来约束模型。 告诉模型“执行前必须询问用户”是不可靠的,模型总有概率会“忘记”或“误解”这条指令。
    • 正确的做法: 让模型自由地生成调用危险工具的请求。但是,在应用程序层面拦截这些请求。在实际执行 API 调用之前,必须向用户展示请求详情,并获得用户的显式批准(例如,点击确认按钮)。

通过遵循这些关于工具定义、调用机制理解和安全处理的最佳实践,你可以更有效地赋予 LLM 与外部世界交互的能力,构建出功能更强大、同时又安全可靠的应用。


第十章:构建对话式智能体(Conversational Agents)

前面的章节介绍了如何让 LLM 进行推理和使用工具。现在,我们将这些能力整合起来,构建对话式智能体 (Conversational Agents)。对话式智能体通过与用户的多轮对话来完成任务,它不仅能理解和回应,还能利用工具获取信息、执行操作,并记住之前的交互内容。本章将重点讨论如何管理对话状态,整合各种上下文信息,以及设计有效的用户界面来支持这种交互。

1. 管理对话状态与历史信息

与一次性的 Prompt 调用不同,对话式智能体需要记住之前的交互内容,即管理对话状态

  • 核心需求: 为了让智能体能够理解后续指令(例如,“把它改回原来的温度”),它必须能够访问之前的对话轮次。这意味着应用程序需要存储和检索对话历史。
  • 实现方式:
    • 最基本的方法是将整个对话历史(包括用户消息、助手消息、工具调用和响应)作为一个列表传递给 LLM 。  
    • 随着对话变长,可能会超出 Context Window 限制。需要采取策略来删减历史记录:
      • 截断 (Truncation): 简单地丢弃最早的消息 。这是最简单的方法,但可能丢失重要信息。  
      • 摘要 (Summarization): 对早期的对话部分进行摘要,保留核心信息,减少 Token 占用 。这更复杂,但能更好地保留长期上下文。  
      • 选择性遗忘: 判断哪些历史信息与当前话题相关性较低,并将其移除。这可以基于时间(如丢弃旧会话 )或内容相关性(可能需要另一个 LLM 或模型来判断 )。  

2. 在多轮对话中整合上下文

一个典型的对话智能体 Prompt 需要整合多种类型的上下文信息,以确保智能体能够准确理解用户意图并有效执行任务。

  • 上下文来源与组成:
    • 前导指令 (Preamble): 通常放在系统消息中,定义智能体的角色、行为准则、可用工具(工具定义通常由 API 隐式加入系统消息 ),以及必要的 Few-Shot 示例。  
    • 先前对话 (Prior Conversation): 包含到当前用户输入之前的所有历史消息(用户、助手、工具调用、工具响应)。这为当前交互提供了背景。
    • 当前交互 (Current Exchange):
      • 用户的最新消息。
      • 用户可能附加的上下文物件 (Artifacts):用户明确指向或应用自动附加的与当前请求相关的数据(如屏幕上高亮的文本、当前编辑的文件)。
      • 在处理用户请求过程中发生的工具调用和响应:智能体为了回答问题或执行任务而进行的内部工具交互记录。
    • 智能体响应 (Agent Response): 模型最终生成的、面向用户的回复。这条消息本身不包含在 当前 请求的 Prompt 中,但会成为 下一次 交互的“先前对话”的一部分。
  • 整合策略与挑战:
    • 工具选择: 是否需要根据对话阶段动态调整可用的工具集?移除当前不需要的工具可以减少干扰。
    • 上下文物件 (Artifacts) 管理:
      • 包含哪些? 全部包含可能导致信息过载 ;让模型自己选择需要额外的复杂性。  
      • 如何呈现? 可以将其内容嵌入用户/助手消息中(如使用 XML 标签 <artifact> 或 Markdown),或保留原始的工具调用/响应记录(这能提供更多工具使用示例)。  
      • 处理大型 Artifacts: 对于非常大的 Artifacts(如整本书),不能直接放入 Prompt。需要进行摘要,或者提供一个能让智能体按需查询 Artifact 细节的工具(例如,details(‘section 5’) 工具或 RAG 检索工具)。
    • 历史长度管理: 如何决定先前对话应该回溯多远?何时认为话题已经改变可以安全丢弃旧信息?这需要仔细权衡,过度保留和过度删减都有风险。

3. 设计有效的用户交互界面

用户与对话式智能体的交互通常发生在图形界面中,良好的 UI 设计对于提升用户体验和效率至关重要。

  • 基本要素:
    • 标准的聊天界面(用户和助手的消息交替出现)。
    • 明确的处理中状态指示(如旋转图标),告知用户智能体正在思考或执行操作。
  • 工具交互的可视化与控制:
    • 指示工具使用: 在助手消息中明确标示出正在进行的工具调用(例如,使用标签或图标),让用户了解后台活动。
    • 提供透明度: 允许用户查看工具调用的详细信息,包括调用的工具名称、传递的参数以及返回的结果。这有助于用户理解智能体的“思考过程”和行为依据。
    • 允许用户干预: 让用户能够修正工具调用的参数并重新提交,从而纠正智能体的错误理解或引导其走向更期望的方向。
  • 处理危险操作:
    • 对于会修改现实世界状态或产生重要后果(如购买、发送邮件)的工具调用,必须在执行前获得用户的显式授权。UI 需要设计清晰的授权请求流程。
  • 上下文物件 (Artifacts) 的呈现:
    • 如果交互围绕着某些核心对象(如代码文件、文档、设计图),应在 UI 中清晰地展示这些状态化的“讨论对象” (Stateful Objects of Discourse),而不仅仅是在对话流中反复提及。Anthropic 的 Artifacts 功能就是一个例子,它将主要对象展示在对话旁边的独立窗格中 。  
    • 允许用户查看和理解智能体当前关注的 Artifacts,甚至可能让用户编辑移除不相关的 Artifacts,有助于保持对话聚焦和高效。

通过精心管理对话状态、智能地整合各种上下文信息,并设计出透明、可控的用户界面,你可以构建出强大而用户友好的对话式智能体,有效地利用 LLM 的能力来完成复杂任务。


第十一章:自动化复杂任务——LLM 工作流(Workflows)

对话式智能体在有人类指导的情况下能完成许多任务,但对于需要多步骤、无需人工干预即可自主完成的复杂目标,其通用性和可靠性往往不足。为了更强大、更可靠地自动化复杂任务,我们需要引入 LLM 工作流 (Workflows)。工作流通过将宏大目标分解为一系列定义明确、可执行的子任务,并按照预定逻辑(或由 LLM 动态决定)连接这些任务,从而实现更强的任务完成能力,尽管可能牺牲一定的通用性。

1. 将复杂目标分解为任务

构建工作流的第一步是清晰地 定义最终目标,然后将其 分解为一系列更小的、可管理的子任务

  • 目标定义: 明确工作流旨在实现的最终输出或状态改变是什么?例如,在 Shopify 插件推广示例中,目标是为一系列商店生成并发送定制化的插件推广邮件。
  • 任务分解: 识别出达成目标所需的中间步骤。每个步骤应设计为一个相对独立的 任务 (Task)。例如,Shopify 示例可以分解为:1) 获取商店列表和网页;2) 分析商店特征;3) 生成插件概念;4) 生成营销邮件;5) 发送邮件。
  • 定义任务接口: 对每个任务,明确其:
    • 输入 (Input): 完成该任务需要哪些信息?
    • 输出 (Output): 该任务将产生什么结果,供后续任务使用?
    • 输入输出格式: 数据是结构化的(需要定义 Schema,如 JSON 或特定对象)还是自由文本? 例如,邮件生成任务的输入可能是包含插件名称、概念、原理和商店 ID 的结构化对象,输出可能是包含邮件主题和正文的对象 。  
    • 任务目标/逻辑: 任务内部大致如何工作?需要 LLM 吗?需要哪些能力(如分析、创意生成、格式转换)?这有助于确保任务划分的合理性。

良好的任务分解是构建健壮工作流的基础,它使得复杂问题模块化,易于实现、测试和维护。

2. 实现 LLM 驱动的任务

工作流中的任务不一定都需要 LLM。如果一个任务可以用传统软件(如网页抓取、数据库写入)或更简单的机器学习模型(如 BERT 分类器)高效完成,那么优先选择这些方法,因为它们通常更快、更便宜、更可靠。但对于许多涉及理解、生成、转换自然语言或进行复杂判断的任务,LLM 是核心。

  • 基于 Prompt 模板的方法:
    • 原理: 为每个 LLM 任务创建一个 Prompt 模板,该模板包含静态指令和用于填充动态输入数据的占位符。执行任务时,填充模板,调用 LLM,然后解析 Completion 以提取所需的输出。
    • 实现: 应用本书前面章节的所有 Prompt 工程技巧——收集上下文、排序、剪裁、组装成符合任务目标的文档结构(如报告、对话)。确保 Prompt 能引导模型生成易于解析的输出(例如,通过在模板前后添加固定文本界定符)。
  • 基于工具的方法(用于结构化输出):
    • 原理: 当任务目标是从输入(如网页 HTML)中提取结构化信息(如餐厅名称、地址、电话)时,可以利用 LLM 的工具调用能力。
    • 实现: 定义一个“工具”,该工具的参数就是你想要提取的结构化数据的 Schema。然后,在 Prompt 中指示 LLM 使用这个“工具”来“保存”从输入中提取的信息。模型为了“调用”该工具,会生成包含所需结构化数据的参数。可以使用 API 参数(如 OpenAI 的 tool_choice)强制模型必须调用此工具。
    • 优化: 如果提取困难,检查输入是否清晰,或者尝试将复杂的提取结构分解为多个更简单的提取任务。
  • 增加任务的复杂性和鲁棒性:
    • 引入推理 (CoT/ReAct): 如果任务需要更深的思考或规划,可以在 Prompt 中加入“让我们一步一步思考”或类似指令,或者设计成 ReAct 模式,让模型先思考再行动(或调用工具)。
    • 自我修正 (Self-Correction/Reflexion): 对于容易出错的任务(如代码生成),可以实现一个评估步骤(检查格式、运行测试、甚至让另一个 LLM 评审)。如果输出不符合要求,将评估报告连同原始请求和失败尝试一起反馈给模型,要求其修正错误并重试。
    • 多智能体协作: 对于开放式任务,可以设置一个“专家”智能体和一个“用户代理”智能体,让它们通过对话协作完成任务。框架如 AutoGen 支持这种模式。

3. 构建和优化工作流(Pipeline, DAG, Cyclic Graph)

实现了各个任务后,需要将它们连接起来形成完整的工作流。工作流的 拓扑结构 决定了任务间的执行顺序和数据流向。

  • 流水线 (Pipeline):
    • 结构: 任务按线性顺序连接,每个任务的输出最多流向下一个任务。
    • 优点: 结构最简单,易于理解和实现。
    • 缺点: 灵活性差,信息只能单向流动,可能导致后续任务缺乏早期任务产生的信息。
  • 有向无环图 (DAG – Directed Acyclic Graph):
    • 结构: 任务可以有多个输入和输出,但信息流没有闭环(不允许循环依赖)。一个任务只有在其所有上游任务都成功完成后才能执行。
    • 优点: 比流水线更灵活,允许信息分支和汇合,能模拟大多数实际工作流程,且易于管理(许多工作流自动化平台如 Airflow 基于 DAG)。
    • 示例: Shopify 示例可以改进为 DAG,让“分析商店特征”任务的输出同时流向“生成插件概念”和“生成邮件”任务 。  
  • 循环图 (Cyclic Graph):
    • 结构: 允许信息流形成闭环,即任务的输出可以流回其上游任务 。  
    • 优点: 可以实现反馈和迭代改进。例如,如果邮件质量检查失败,可以将失败信息反馈给“分析商店特征”任务,要求重新分析或调整。
    • 缺点: 显著增加复杂性。需要处理状态同步(如失败信息如何与原始数据关联)、防止无限循环(需要计数器或终止条件)、以及让所有任务都能处理可能的反馈输入。
    • 建议: 尽可能将循环逻辑(如 Reflexion)封装在单个任务内部,避免在工作流层面创建复杂的循环依赖 。  

工作流优化:

  • 任务优化: 持续优化每个任务的 Prompt、所用模型、或改用非 LLM 实现,以提高质量、降低成本和延迟。
  • 反馈整合: 在工作流中加入评估和反馈环节。可以在任务级别应用 Reflexion,或在工作流级别将失败项连同失败原因送回流程早期进行重试。
  • 数据驱动改进: 从工作流运行中收集输入输出数据,用于离线测试、评估不同 Prompt 或模型的效果(A/B 测试),甚至用于 Prompt 自动优化框架(如 DSPy, TextGrad)或模型 Fine-tuning。

4. 探索高级工作流模式

基础工作流(尤其是 DAG)结构固定,可靠性高,是大多数场景的首选。但对于需要更高灵活性的开放式问题,可以探索更高级的模式,尽管这通常意味着稳定性和可预测性的降低。

  • LLM 驱动路由/编排:
    • 原理: 让一个“工作流智能体”(LLM) 来决定工作项应该流向哪个(预定义的)任务,而不是固定的连接。这个智能体可以被赋予代表各项任务的“工具”。
    • 进阶: 工作流智能体甚至可以动态地为特定子目标即时生成任务描述和配置(从预定义工具库中选择工具)。工作流智能体还可以维护一个待办任务列表,并根据优先级动态调度执行。
  • 状态化任务智能体:
    • 原理: 每个核心“工作项”(如一个代码文件、一个用户案例)都有一个长期存在的、负责其状态的“任务智能体”。当外部事件(如用户请求、依赖项变更)发生时,相关智能体被激活以更新其负责的工作项,并可能通知其他依赖它的智能体。
    • 交互: 用户可以直接与负责特定工作项的智能体对话,进行修改或查询。
  • 基于角色的协作 (Roles and Delegation):
    • 原理: 定义一组具有特定角色、目标、背景和工具的智能体。一个“管理者”智能体(或预定流程)将任务分配给合适的角色去执行。框架如 AutoGen(用户代理+助手模式,群聊管理器)和 CrewAI 支持这种模式。

结论:

LLM 工作流提供了一种强大的方法来自动化原本需要大量人工参与的复杂任务。通过将目标分解为任务、精心实现每个任务(结合 LLM 和传统技术),并合理地组织任务间的流程(从简单的 Pipeline 到灵活的 DAG,乃至高级的智能体编排模式),你可以构建出既强大又具有一定可靠性的自动化系统。始终牢记,在简单可靠与灵活强大之间需要权衡,优先选用最简单能满足需求的方案,并持续评估和优化你的工作流。


第四部分:质量保证——评估与迭代

第十二章:评估你的 Prompt 和应用

构建 LLM 应用和工作流是一个持续迭代的过程。为了确保你的 Prompt、模型选择和整体应用设计是有效的,并且任何改动都在朝着正确的方向前进,建立一套评估体系至关重要。本章将介绍两种主要的评估方法——Offline 评估和 Online 评估——以及如何选择和解读关键的评估指标。

1. 实施 Offline 评估

Offline 评估是在没有真实用户参与的情况下,使用预先准备的示例对你的应用(或其部分)进行测试。它通常是项目早期的主要评估手段,因为它不依赖于上线部署。

  • 评估对象: 你可以评估整个应用流程(端到端测试/回归测试),也可以评估与 LLM 的单次交互(单元测试),或者评估特定的模型或 Prompt 策略。对于复杂的、多步骤的应用,同时进行端到端测试和关键步骤的单元测试是理想的。
  • 获取测试样本:
    • 现有记录: 利用已有的、与你的应用场景相似的数据。例如,如果你的应用是自动填写表单摘要,可以使用历史上用户手动填写的表单作为样本。
    • 相似问题语料库: 寻找公开的、与你的任务相似的数据集或语料库。例如,GitHub Copilot 使用公开的 GitHub 代码库来模拟代码补全场景。
    • 合成数据: 使用 LLM 生成测试样本。可以先生成主题或场景组合,再为每个组合生成具体案例。注意避免使用生成样本的同一个 LLM 来评估,以防偏见 。  
  • 评估方法:
    • 示例套件 (Example Suites):
      • 方法: 准备一小组(5-20个)有代表性的输入示例,运行你的应用生成输出,然后人工检查(”eyeball”)输出的变化,判断改进或退步。
      • 优点: 简单易行,项目初期即可使用,有助于深入理解应用行为和典型缺陷。
      • 缺点: 规模受限,依赖人工判断,不适用于检测细微变化。
    • 功能测试 (Functional Testing):
      • 方法: 检查生成的输出是否满足某些客观的功能性要求,而不需要“正确答案” 。例如:生成的代码是否能编译通过?生成的 API 调用是否符合语法?生成的文本是否能被下游系统成功解析? Copilot 使用单元测试来检查生成的代码是否正确。  
      • 优点: 客观、可自动化。
      • 缺点: 通常只能检查部分正确性(代码能编译不代表逻辑对),适用场景有限。
    • LLM 评估 (LLM-as-Judge):
      • 方法: 使用另一个(或同一个,但要小心)LLM 来评估目标 LLM 的输出质量。
      • 关键技巧(SOMA 评估)
        • 具体问题 (Specific Questions): 不要问笼统的“这个好吗?”,而是针对特定方面提问(如“这个回答是否解决了用户的具体问题?”“格式是否正确?”)。
        • 有序量表答案 (Ordinal Scaled Answers): 使用评分制(如 1-5 分),并为每个分数提供清晰的含义描述,而不是简单的“是/否”,以提高一致性和细致度。
        • 多维度覆盖 (Multi-aspect Coverage): 同时评估多个重要维度(如相关性、准确性、简洁性、安全性),而不是依赖单一指标。例如,RTC(相关性-真实性-完整性)框架 。  
      • 注意事项: 让 LLM 扮演第三方评估者的角色,而不是评估自己。评估结果主要是相对的(版本 A 比版本 B 好),而非绝对准确率。最好用小规模人工评估来校准 LLM 评估的可靠性。

2. 开展 Online 评估(A/B 测试)

Online 评估是将你的应用的不同版本部署给真实用户,并根据用户行为数据来判断优劣。这是验证应用在现实世界中表现的最终方法。

  • A/B 测试:
    • 方法: 最常用的 Online 评估方法。将用户随机分成组,一组使用现有版本(A),另一组使用新版本(B),运行一段时间后比较关键指标。
    • 准备: 通常需要先通过 Offline 评估筛选掉明显差的版本 。需要有机制(如 Optimizely 等 A/B 测试平台)来管理用户分组和数据收集。如果应用运行在客户端,可能需要先将新版本部署给大部分用户 。  
    • 关键: 预先定义 你要优化的核心指标(目标指标)和需要监控的底线指标(护栏指标)。

3. 选择和解读评估指标

无论是 Offline 还是 Online 评估,选择正确的 指标 (Metrics) 至关重要。指标是你判断“好坏”的依据。

  • 常见指标类型:
    • 直接反馈 (Direct Feedback): 用户明确表达的满意度。
      • 例子: 点赞/点踩按钮 ,评分,明确的对比选择(“哪个更好?”)。  
      • 优缺点: 信号直接,质量高(可用于 Fine-tuning);但可能有偏见(如只有不满意的用户才反馈),反馈率低,可能打扰用户。
    • 功能正确性 (Functional Correctness): 输出是否满足客观标准。
      • 例子: 代码是否编译通过 ,API 调用是否成功,生成的链接是否可访问。  
      • 优缺点: 客观,易于自动化;但通常只能衡量部分正确性。
    • 用户接受度 (User Acceptance): 用户是否采纳了 LLM 的建议。
      • 例子: GitHub Copilot 的代码接受率 ,建议链接的点击率 (CTR),用户是否继续执行了建议的操作(如预订推荐的航班)。  
      • 优缺点: 反映了建议的初步吸引力,通常与用户价值相关;但接受不等于最终满意或有效。
    • 达成的影响 (Achieved Impact): LLM 的贡献最终带来了多大的价值。
      • 例子: 最终邮件中由 LLM 生成的内容占比,用户根据建议完成购买的转化率,任务完成时间缩短的比例。
      • 优缺点: 最接近真实价值;但往往难以衡量,可能受多种因素影响。
    • 附带指标 (Incidental Metrics): 与核心目标不直接相关但值得关注的指标。
      • 例子: 延迟(Latency) ,错误率,对话时长 ,Token 消耗量。  
      • 优缺点: 提供辅助信息,有助于发现潜在问题或成本变化;但本身不能完全代表好坏(如对话短可能是高效也可能是用户放弃)。
  • 选择策略:
    • 优先寻找能反映用户核心价值接受度影响指标 。  
    • 如果没有可靠的此类指标,考虑使用直接反馈,但要注意其局限性。
    • 同时监控功能正确性附带指标(尤其是延迟和错误率)作为护栏 (Guardrails),确保优化核心指标的同时不会损害基本的用户体验或增加过多成本 。  

结论:

评估是 LLM 应用开发迭代中不可或缺的一环。无论是早期的 Offline 评估(使用示例套件、功能测试或 LLM 评估),还是上线后的 Online A/B 测试,都需要精心设计测试方案、选择合适的样本来源和评估方法,并明确你要衡量和优化的核心指标。持续、有效地评估将指引你不断改进 Prompt、优化模型选择和完善应用架构,最终打造出真正满足用户需求的高质量 LLM 应用。



评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注