上下文工程与提示词管理

Context Engineering and prompt management

传统软件可以通过代码直接规定行为;LLM 则需要通过提示词引导。Prompt Engineering 已从简单技巧发展为具有系统策略和工具的方法,也是 Context Engineering 的重要组成部分。本课介绍 zero-shot、few-shot、Chain-of-Thought、自一致性和提示词链等方法,帮助 LLM 推理并有效利用知识;随后介绍扩大应用规模所需的提示词管理技术:

  • 使用 Jinja2 模板引擎
  • 使用 YAML 配置提示词
  • 使用 Pydantic 数据模型进行验证和版本管理

LLM 提示词工程策略

Zero-shot 与 few-shot

Zero-shot prompting 只提供指令或查询,不提供示例,依赖模型已学到的知识执行任务。这是最简单的方式:直接提出问题或命令。它适合直接明了的任务,或需要简短提示词的情况;但只要要求略有歧义,模型便可能误解。

Few-shot prompting 在提示词中加入少量任务示例。通过具有代表性的输入—输出对,让模型了解预期模式,再针对新输入延续该模式。这利用了模型的 in-context learning 能力:推理时依据示例调整输出,模型权重保持不变,示例也不会因此永久保存。对于复杂或不清楚的任务,few-shot 往往优于 zero-shot,因为示例减少了歧义。设计时需要注意:

  • 示例相关性:覆盖关心的输入范围与边界情况。
  • 简洁性:受到 token 上限约束,示例数量以足够引导模型为宜。
  • 顺序影响:模型容易受较近示例影响,最后几个示例可能产生较大作用。可以随机排列示例;评估稳健性时,也可以用不同顺序多次请求。

对于简单任务,常见做法是从 zero-shot 开始,根据错误分析结果,必要时采用 one-shot 或 few-shot。

提示:聊天场景中,few-shot 示例通常作为若干 user/assistant 消息对,放在真实用户查询之前。单提示词的 completion API 中,可以将示例连接到提示词文本里。无论采用哪种形式,都应通过引号、分隔符或 Markdown 清楚区分示例,并让示例严格体现期望输出格式。

Chain-of-Thought prompting

任务更复杂时,要求模型按步骤处理可能有帮助。Chain-of-Thought(CoT)通过提示词,让模型先产生中间推理步骤,再给出最终答案。

例如,询问“12 乘以 13,再加 14 等于多少?”时,加入“请分步计算”,模型可能回答:“12 乘以 13 等于 156,再加 14 得到 170,因此答案是 170。”可以通过“逐步思考”等短语采用 zero-shot CoT,也可以提供包含详细解答的 few-shot 示例。早期 CoT 研究中,约 10B 参数的较小模型获益较少,约 100B 及以上的大模型提升较明显;具体效果仍取决于模型和任务。

CoT 可以用于数学以外、通常需要多个步骤的任务,例如逻辑推理、复杂问答,以及先说明解题方式再生成代码的编程任务。

注意:CoT 可能使响应更长。部分应用无需向最终用户展示全部中间文字,可以在系统内部处理后,按需要整理答案和简明依据。

推理中的自一致性

单条推理路径可能出错。Self-Consistency 通过采样多条不同路径,再对最终答案投票,提高可靠性。具体来说,以相同 CoT 提示词多次请求模型,每次取得最终答案,然后选择出现最多的答案,或使用排序规则。即使单次推理可能出错,只要模型多次尝试时总体更倾向于正确结果,多次独立尝试中的多数答案便可能更可靠。它通过多个采样结果减少单次轨迹造成的结果波动。

实现时,例如并行运行 5 次或 10 次提示词请求,再在代码中多数投票;具体接口支持时,也可利用 n、best-of 等采样设置。计算开销较高,需要在额外 API 调用与更高答案置信度之间取舍。

我曾在实践中尝试这种方法,但更长的响应延迟和计算成本,使它在商业场景中较少可行。

提示词链:多步骤工作流

Prompt Chaining 将多个提示词—模型调用按顺序连接,前一步输出成为下一步输入。每次调用承担一个子任务,共同完成复杂目标。任务能够自然分解,或一个模型的结果需要下一次提示词继续处理时,这种方法很有用。例如,长文档问答可以先提取或概括相关事实,再基于这些事实形成最终答案。客服机器人可以采用以下步骤:

  1. 分类用户问题,即执行意图分类。
  2. 根据分类选择或生成回答模板。
  3. 填入具体信息,并完善答案。

每一步都可以是独立的 LLM 提示词调用,步骤之间也可以加入其他逻辑。

另一种实际模式是 ReAct(Reason + Act):连接 LLM 推理与工具操作。模型先确定下一步行动,系统执行搜索等工具操作,再把搜索结果交回模型继续处理,循环直到产生最终答案。

链式处理便于管理中间结果与错误。例如,某一步解析失败时,可以重新请求或执行处理逻辑。代价是多次调用增加延迟和成本,以及需要管理步骤间的状态。但在许多实际应用,尤其是 RAG 中,链式处理十分重要。Embedding → Retrieval → Generation 本身便是一条链。任务分解也有助于分别评估系统各部分。

随着模型能力增强,指令遵循和结果准确性通常也会改善,使提示词设计更容易,部分改进无需额外工作便可获得。

适合规模扩展的提示词管理

复杂 LLM 应用会积累大量提示词,包括长指令、few-shot 示例和 system 消息。直接在代码中管理会越来越复杂。提示词管理使其模块化、易于维护,并具有版本控制。这里讨论三种方法:

  • 使用模板引擎
  • 将提示词存入 YAML 配置文件
  • 使用提示词注册中心

常见做法是将提示词内容与应用逻辑分开,让提示词能够持续改进,同时提高代码可读性。

提示词模板与 Jinja2

提示词直接写在代码中,即使采用 Python f-string,遇到较长内容和大量动态部分时也容易混乱。提示词模板是一种可复用格式,运行时将实际值填入占位符。LangChain 的 PromptTemplate 可以定义 Tell me a joke about {topic},再调用 prompt.format(topic="elephants") 生成最终文本,本质上是字符串格式化。

更复杂的提示词可能包含条件逻辑或重复片段,这时适合使用 Jinja2。这个 Python 模板库支持循环、if/else 等控制结构,也支持模板继承和组合。用于 LLM 时,可以根据输入动态调整提示词,或在多个提示词之间复用共同部分。

Jinja2 的主要优势包括:

  • 职责分离:提示词文本位于模板文件,可由提示词工程师或领域专家编写和迭代;Python 代码只负责加载和渲染。这样,大段自然语言指令与业务逻辑各自清楚,代码更易读,提示词更易编辑。
  • 规模与组织:多个任务或角色可能产生几十种提示词变体。独立模板目录便于浏览与更新。
  • 复用:多个提示词可能共享格式要求或免责声明。Jinja2 的继承和 include 可复用基础模板或片段,例如 {% include 'polite_style.txt' %},减少复制并保持一致。
  • 模板逻辑:{% if %} 可以根据条件加入或省略内容。例如,针对技术用户和非技术用户调整措辞,将不同表达统一保存在一个模板中。

例如,聊天机器人需要根据用户信息调整表达,其 system 提示词模板可以如下:

- When interacting with the user, respond in their language.
- The language of the user is {{ user_language }}.

{% if buying_intent == "True" %}
- List the products in a bullet list with detailed descriptions.
{% else %}
- Explain availability of products in a paragraph.
{% endif %}

使用 user_language="German" 和 buying_intent="True" 渲染后,得到面向德语用户、并针对明确购买意图的提示词。Python 用法如下:

from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("templates/"))
template = env.get_template("prompt_template.txt")
prompt_text = template.render(user_language="German", buying_intent="True")

渲染后,占位符被替换,合适的条件分支被保留。还可以把生成的字符串交给 LangChain PromptTemplate 或其他 LLM 编排框架;也可以编写包装函数,读取 Jinja2 文件并生成 LangChain PromptTemplate,用于链式工作流。

模板系统使提示词模块化。一位工程师可以专注模板措辞和快速测试,另一位则专注周围的程序逻辑。

基于 YAML 的提示词配置

提示词数量增加后,将其视为配置数据很有帮助。YAML 是便于人类阅读的数据序列化格式,许多团队和框架用它保存配置。可以用 YAML 结构化保存提示词模板、变量,甚至整个多步骤流程。

  • 版本控制与协作:仓库中的 .yaml 或 .yml 文件可以像代码一样比较差异、接受评审;同事可以通过 pull request 提议修改。
  • 动态加载:应用可以按名称或环境加载提示词。例如,配置文件包含 dev、prod 变体或多个版本,由程序选择合适内容,便于在无需重新部署代码时更新提示词。
  • 结构化说明:除文本模板外,还可添加 description、author、created_at 等元数据。大型组织提示词较多时,这些背景信息很有价值。
  • 声明式复杂流水线:高级用法可以定义提示词与工具工作流。例如,Azure Prompt Flow 中使用的 .prompty 将 YAML 配置与 Jinja2 模板结合,描述 LLM 调用;Prompt Declaration Language(PDL)则是基于 YAML 的 DSL,可声明多步骤对话、工具调用及输入输出 schema。目标是把提示词逻辑从命令式代码移入声明式说明。我个人并不特别喜欢完全通过配置管理 LLM 工作流,但在技术能力不同的团队中,它可能有用。这类定义通常比 LangFlow 等无代码工具更接近代码,也更适合生产场景。

以下是一个不绑定特定库的 YAML 配置示例:

prompts:
  qa_system:
    description: System prompt for shopping assistant.
    template: |
      You are a shopping assistant that can answer questions about the products in stock.
      Instructions:
      """{{instructions}}"""
    variables: ["instructions"]
  qa_with_inventory:
    description: Answer a question using provided documents.
    template: |
      You are a shopping assistant that can answer questions about the products in stock.

      {% for doc in docs %}
      Document {{ loop.index }}: {{ doc }}
      {% endfor %}

      Question: {{ question }}

      Answer in a concise manner, referencing the documents.
    variables: ["question", "docs"]

这里定义了 qa_system 和 qa_with_inventory 两个模板。每个模板使用 YAML 的 | 保存保留换行的多行字符串,并列出预期变量。第二个模板包含 Jinja2 循环,用于枚举 docs。应用加载 YAML 后,将实际数据填入变量并渲染,结合了 YAML 结构化配置与 Jinja2 动态模板的优势。我见过并构建过许多采用这种组合的系统。

使用提示词注册中心

复杂 LLM 应用可以通过 prompt registry,系统化存储、管理提示词与版本,支持团队协作。注册中心提供集中管理位置。LangSmith 是具有此能力的平台之一;虽然示例采用 LangSmith,大多数可观测性与评估平台也支持相近功能。

LangSmith Prompt Hub 将提示词注册为命名资源,再不断完善并保存版本。创建或保存提示词,会在历史中形成一个新版本。每个版本不可变,以唯一 commit hash 标识,可以精确引用。例如,名为 ecommerce-rag 的提示词可以通过 client.pull_prompt("ecommerce-rag:123456ee78") 取得指定提交,也可以引用标签。生产应用因此能够固定使用经过验证的版本。

Commit tag 进一步简化版本管理。dev、v2 等易读标签指向某个提交,也可以按需要移动。代码引用标签时,只需重新指定标签,就能切换生产环境使用的提示词版本。常见做法是为经过测试的稳定版本使用 v1、v2,并使用 staging、production 等环境标签。

LangSmith 的提交历史界面展示各版本的变化与修改者,支持审计与协作。每个提示词还可带有描述、使用说明,甚至 README,让协作者了解背景。

LangSmith 提示词管理也可独立于 LangChain 使用。通过 Python、TypeScript SDK 或 REST API,可以访问注册中心。例如,Python langsmith client 可以上传提示词对象,或带变量的原始模板字符串;SDK 负责向服务上传文本、输入变量和格式设置。因此,自定义程序或直接 API 调用的应用,也能按名称从 LangSmith 获取提示词。

提示词平台的一项重要优势是系统化测试与审计。LangSmith 的 Playground 和 Prompt Canvas 支持快速人工迭代,可以在部署前尝试不同模型、示例输入并观察效果。团队成员,包括非工程人员,可以在界面调整提示词并提出建议,实现实时协作。Prompt Canvas 支持并排编辑与比较提示词变体,加快讨论和修改。实验可以保存为新版本,也可以放弃。

结合使用

提示词工程关注如何设计有效指令,提示词管理关注如何在规模扩大后可靠使用这些指令。LLM 应用从实验进入生产时,两者都更加重要。Zero-shot 和 few-shot 便于快速验证能力;CoT 和自一致性可以帮助提高推理准确率;提示词链与 RAG 将 LLM 接入更广的工作流,处理复杂、知识密集的任务。模板、配置文件和数据模型让提示词成为系统中可维护的一等资源。

项目中的提示词调整应小步进行并能够追踪。通过版本号或清楚名称管理每次修改,需要时能够恢复先前版本。持续监控生产提示词:模型输出追踪日志可以暴露行为变化或意外理解,提示你调整指令。提示词工程需要持续迭代,这些工具有助于长期改进。

搜索课程

正在加载课程目录…