传统软件通过确定性代码直接规定程序行为,而大语言模型(LLM)则需要通过提示词(Prompt)进行引导。随着应用复杂度提升,提示词工程(Prompt Engineering)已从早期的零散调优技巧演进为系统的上下文工程(Context Engineering)方法。本课前半部分介绍帮助 LLM 提升推理质量与知识利用率的提示词策略(包括 Zero-shot、Few-shot、思维链 Chain-of-Thought、自一致性 Self-Consistency 与提示词链 Prompt Chaining);后半部分介绍支撑应用规模化落地的提示词管理技术:
- 使用 Jinja2 模板引擎动态组装提示词
- 使用 YAML 配置文件管理提示词
- 使用 Pydantic 数据模型与提示词注册中心进行验证和版本管理
LLM 提示词工程策略
1. Zero-shot 与 Few-shot
Zero-shot prompting(零样本提示):仅提供任务指令或查询,不附带任何示例,完全依赖模型预训练习得的知识执行任务。这种方式最为简短直接,适合指令清晰、无歧义的常规任务;但只要任务描述稍有含糊,模型就可能出现理解偏差。
Few-shot prompting(少样本提示):在提示词中附带少量具有代表性的“输入—输出”示例对,利用模型的上下文学习(In-Context Learning)能力,引导模型在推理时参照示例模式处理新输入(模型权重本身保持不变,示例也不会被永久记忆)。面对复杂或格式要求精细的任务,Few-shot 往往比 Zero-shot 更稳定。设计示例时需注意:
- 示例相关性:覆盖核心输入分布与典型边界情况。
- 简洁性:受上下文窗口 token 限制,示例数量应以足以明确模式为准,避免冗余。
- 排列顺序影响:模型容易受位置较近的末尾示例影响(Recency Bias)。在评测提示词稳健性时,可尝试打乱示例顺序多次请求以观察输出波动。
工程上通常先从简单的 Zero-shot 起步,结合错误分析结果,在必要时逐步引入 One-shot 或 Few-shot 示例。
提示:在多轮对话(Chat)接口中,Few-shot 示例通常组织为真实用户提问之前的若干组 user / assistant 历史消息对;在单文本补全(Completion)接口中,则直接拼接在提示词正文内。无论采用哪种形式,都应使用引号、分隔符或 Markdown 清晰界定示例边界,并让示例严格遵循期望的输出格式。
2. Chain-of-Thought(CoT,思维链)提示
当任务复杂度较高时,引导模型分步推演往往能显著改善结果。Chain-of-Thought(CoT)通过提示词让模型先输出中间推理步骤,再得出最终结论。
例如,提问“12 乘以 13,再加 14 等于多少?”并附上“请分步计算”,模型会依次输出:“12 乘以 13 等于 156,再加 14 得到 170,因此答案是 170。”开发者既可以通过添加“让我们逐步思考”等简短指令启用 Zero-shot CoT,也可以配合包含详细解题步骤的示例构建 Few-shot CoT。早期 CoT 研究表明,约 10B 参数规模的较小模型从中获益有限,而 100B 及以上规模的大模型提升更为明显(具体效果仍取决于所选模型与任务类型)。除算术计算外,CoT 同样适用于逻辑推理、复杂多跳问答,以及“先阐述实现思路再编写代码”的编程任务。
CoT 可以用于数学以外、通常需要多个步骤的任务,例如逻辑推理、复杂问答,以及先说明解题方式再生成代码的编程任务。
注意:启用 CoT 会拉长模型生成的文本长度。若前端无需向最终用户展示完整的中间推演过程,可在系统内部解析提取最终结论与简明依据后再返回。
3. 推理中的自一致性(Self-Consistency)
单条思维链推理路径仍有可能在中途出错。自一致性(Self-Consistency)通过对同一问题采样多条不同的推理路径,并对各路径得出的最终答案进行多数投票(或按规则排序),来提升结果的可靠性。具体做法是:使用相同的 CoT 提示词向模型发起多次独立请求(例如并行调用 5 次或 10 次,或在接口支持时利用 n、best-of 等多样本采样参数),收集每次生成的最终答案并选取出现频次最高的结果。只要模型在多次尝试中整体倾向于正确方向,多路径投票就能有效平滑单次采样的随机波动。
实现时,例如并行运行 5 次或 10 次提示词请求,再在代码中多数投票;具体接口支持时,也可利用 n、best-of 等采样设置。计算开销较高,需要在额外 API 调用与更高答案置信度之间取舍。
不过,该方法需要在更高的答案置信度与成倍增加的 API 调用开销之间权衡。在实际工程中,由于多路采样会带来更长的响应延迟与更高的计算成本,在商业生产场景中较少直接采用。
4. 提示词链(Prompt Chaining):多步骤工作流
提示词链将多个“提示词—模型调用”按先后顺序串联起来,把前一步的输出作为后一步的输入,让每次调用专注于一个明确的子任务,协同完成复杂目标。当任务可以自然拆解,或前序生成结果需要经过下一步提示词二次加工时,这种模式尤为适用。例如,长文档问答可先由第一步提取或概括相关事实,再由第二步基于提取出的事实组织最终回答;智能客服机器人则可拆解为以下三步链条:
- 对用户提问执行意图分类。
- 根据意图类别选取或生成对应的回答模板。
- 填入具体业务数据并润色最终回答。
每一步都可以是独立的 LLM 提示词调用,步骤之间也可以加入其他逻辑。
每个步骤既可以是独立的 LLM 调用,也可以在步骤之间穿插传统代码逻辑。另一种常见的链式扩展模式是 ReAct(Reason + Act):将 LLM 推理与外部工具调用交替衔接——模型先推理决定下一步动作,系统执行搜索等外部工具调用后将结果传回模型继续推理,循环往复直至得出最终答案。
链式拆解便于对中间结果做校验与局部容错(例如某一步解析失败时只针对该步重试),也有助于对各子环节分别开展独立评估。事实上,RAG 系统的 Embedding → Retrieval → Generation 本身就是一条典型的处理链。其代价则是多次串行调用会增加端到端延迟与成本,且需要维护跨步骤的状态传递。随着基础模型自身指令遵循与推理能力的增强,部分原本需要拆分为多步的简单子任务也可以适当合并。
随着模型能力增强,指令遵循和结果准确性通常也会改善,使提示词设计更容易,部分改进无需额外工作便可获得。
适合规模扩展的提示词管理
当 LLM 应用规模扩大后,系统中会积累大量包含长指令、Few-shot 示例与系统消息的提示词变体。若继续将提示词硬编码在业务代码中,维护难度会急剧上升。工程上通常将提示词内容与应用程序逻辑解耦,通过以下三种方式实现模块化维护与版本控制:
- 使用模板引擎(Jinja2)
- 将提示词存入 YAML 配置文件
- 使用提示词注册中心(Prompt Registry)
常见做法是将提示词内容与应用逻辑分开,让提示词能够持续改进,同时提高代码可读性。
1. 提示词模板与 Jinja2
使用 Python 原生 f-string 或 LangChain 的基础 PromptTemplate(如定义 Tell me a joke about {topic} 后调用 prompt.format(topic="elephants"))本质上都是简单的字符串替换;一旦提示词包含复杂的条件分支或循环列表,直接写在代码里就会变得难以阅读。
Jinja2 是成熟的 Python 模板引擎,支持 for 循环、if/else 条件分支以及模板继承与片段包含(include),非常适合动态组装复杂提示词:
Jinja2 的主要优势包括:
- 职责分离:将大段自然语言指令存放在独立的模板文件中,交由提示词工程师或领域专家编辑迭代,Python 代码仅负责加载文件与传入渲染变量。
- 便于组织与扩展:面对数十种任务或角色提示词变体时,可通过独立的模板目录清晰归档。
- 复用公共片段:多个提示词共享的格式要求或免责声明,可通过 Jinja2 的继承或
{% 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 %}在 Python 中传入 user_language="German" 和 buying_intent="True" 进行渲染:
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 接入现有工作流。
模板系统使提示词模块化。一位工程师可以专注模板措辞和快速测试,另一位则专注周围的程序逻辑。
2. 基于 YAML 的提示词配置
提示词数量增加后,将其视为配置数据很有帮助。YAML 是便于人类阅读的数据序列化格式,许多团队和框架用它保存配置。可以用 YAML 结构化保存提示词模板、变量,甚至整个多步骤流程。
- 版本控制与协作评审:代码仓库中的
.yaml/.yml文件可像普通源码一样通过 Git 对比差异(Diff),方便团队成员通过 Pull Request 发起修改评审。 - 按环境动态加载:支持在配置文件中维护
dev、prod等不同环境变体或多个版本,由程序按需加载,无需改动业务代码即可切换提示词。 - 附带结构化元数据:除模板正文外,还可记录
description、author、created_at等背景字段,便于大型团队协作检索。 - 声明式定义流水线:进阶用法可将提示词与工具调用流程一并声明在配置中(例如 Azure Prompt Flow 采用结合了 YAML 与 Jinja2 的
.prompty格式;Prompt Declaration Language, PDL 则提供基于 YAML 的领域特定语言来声明多步对话、工具调用与输入输出 schema)。完全通过配置文件管理 LLM 工作流未必适合所有开发习惯,但在技术背景多元的团队中,这类声明式配置往往比纯无代码拖拽工具(如 LangFlow)更贴近代码工程规范,也更适合生产部署。
以下是一个不绑定特定框架的 YAML + Jinja2 提示词配置示例:
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 的 | 语法保留多行换行格式,明确列出所需变量 variables,并在第二个模板内嵌了遍历 docs 的 Jinja2 循环。这种将 YAML 结构化配置与 Jinja2 动态模板相结合的方式在实际工程系统中较为常见。
3. 使用提示词注册中心(Langfuse)
课程采用可自托管的 Langfuse 进行集中式提示词管理。其官方接口通过“提示词名称 + 整数版本号(version)+ 可移动标签(label)”进行管理:同名提示词重复调用 create_prompt 时会自动递增生成新版本;模板内部使用 {{question}} 语法声明变量,调用 compile 方法即可渲染生成最终的消息列表。
def create_prompt():
langfuse = get_client()
return langfuse.create_prompt(name='course-feishu-qa', type='chat', labels=['staging'], prompt=[
{'role': 'system', 'content': '仅依据授权资料回答;依据不足时说明缺少信息。'},
{'role': 'user', 'content': '问题:{{question}}\n资料:{{context}}'}])
def run_versioned_prompt(question, contexts):
langfuse = get_client()
prompt = langfuse.get_prompt('course-feishu-qa', type='chat', version=int(os.environ['PROMPT_VERSION']))
messages = prompt.compile(question=question, context=json.dumps(contexts, ensure_ascii=False))
with langfuse.start_as_current_observation(as_type='span', name='course-feishu-request', input={'question': question}) as span:
with langfuse.start_as_current_observation(as_type='generation', name='qa-generation', model=os.environ['CHAT_MODEL'], prompt=prompt) as generation:
completion = configured_client('CHAT').chat.completions.create(model=os.environ['CHAT_MODEL'], messages=messages)
answer = completion.choices[0].message.content
generation.update(output=answer, usage_details={
'input': completion.usage.prompt_tokens, 'output': completion.usage.completion_tokens})
span.update(output=answer, metadata={'prompt_version': prompt.version})
langfuse.flush()
return answer在验证环境中可通过 staging 标签试用新提示词;正式上线与复现实验时,则应明确记录已通过评估的整数版本号(production 标签便于日常运维切换,但实验归档仍以具体整数版本为准)。向平台上传提示词与追踪数据前需核查数据使用权限,并将 LANGFUSE_BASE_URL 指向自托管实例。
def experiment(index, principal):
langfuse = get_client()
version = datetime.fromisoformat(os.environ['DATASET_VERSION'].replace('Z', '+00:00'))
dataset = langfuse.get_dataset('course-feishu-qa', version=version)
def task(*, item, **kwargs):
contexts = index.retrieve(principal, item.input['question'])
answer = run_versioned_prompt(item.input['question'], contexts)
return {'answer': answer, 'source_ids': list({c['source_id'] for c in contexts})}
return dataset.run_experiment(name='course-feishu-schema-v1', task=task,
metadata={'prompt_version': int(os.environ['PROMPT_VERSION']), 'corpus_version': 'demo-v1'})当外部管理平台暂时不可用时,已固化在本地的提示词文件与 JSONL 评估集仍可继续支撑离线复现;若在生产环境启用本地提示词缓存,则需明确规定缓存过期时间与审计策略。
结合使用与工程实践
提示词工程解决“如何设计有效指令”,提示词管理解决“如何在规模化后可靠地维护和迭代指令”:Zero-shot 与 Few-shot 便于快速验证模型能力;CoT 与自一致性有助于提升复杂推理准确率;提示词链与 RAG 将单次模型调用融入多步业务流;而 Jinja2 模板、YAML 配置与提示词注册中心则使提示词成为系统中受版本控制、可追踪、可回滚的一等工程资产。
项目中的提示词调整应小步进行并能够追踪。通过版本号或清楚名称管理每次修改,需要时能够恢复先前版本。持续监控生产提示词:模型输出追踪日志可以暴露行为变化或意外理解,提示你调整指令。提示词工程需要持续迭代,这些工具有助于长期改进。
提示词版本练习
针对飞书保修问答场景创建两个不同的提示词整数版本,在同一份 JSONL 评估集与同一语料 hash 下运行对比实验,完整记录召回率、引用正确率、拒答质量与响应耗时,根据评测结果指定正式启用的版本号——切勿将“提示词已成功保存到注册中心”等同于完成质量验收。