Prompt 组织管理

Prompt 是 Agent 的任务说明书。它负责把模型需要遵守的规则、当前用户输入、上下文资料和输出要求组织清楚。

这一篇主要看这些内容:

txt 复制代码
PromptTemplate
  -> 模块化拆分
  -> partial 预填变量
  -> Message / ChatPromptTemplate
  -> MessagesPlaceholder
  -> FewShotPromptTemplate
  -> ExampleSelector

先记住一句话:PromptTemplate 管字符串,ChatPromptTemplate 管消息数组。

Message 是什么

调用聊天模型时,通常不是只传一段孤立文本,而是传一组带角色的消息。

角色的意义是让模型分清楚:哪些是长期规则,哪些是本轮用户问题,哪些是历史回答,哪些是工具结果。

常见角色:

  • system:长期规则,比如模型身份、回答边界、输出风格。
  • human:当前用户问题,或者本轮任务需要处理的输入。
  • ai:模型之前的回复,常用于多轮对话历史。
  • tool:工具执行结果,后面 Tool 章节再展开。

所以 Message 可以理解成 Agent 的上下文格式。PromptTemplate 先把变量整理成内容,ChatPromptTemplate 再把这些内容分配到不同角色里。

PromptTemplate

PromptTemplate{变量名} 占位,再用 format() 填充变量。

下面用一个很轻的 NBA 案例:给詹姆斯写一段球员观察稿

python 复制代码
from langchain_core.prompts import PromptTemplate


observation_template = PromptTemplate.from_template(
    """
你是一位{role},回答风格:{tone}。
面向读者:{audience}。

请写一段 NBA 球员观察:{player}

观察主题:
{topic}

比赛背景:
{game_context}

分析角度:
{angle}

输出要求:
1. 先给出一句核心判断。
2. 再展开 2~3 个观察点。
3. 不要编造具体数据。
4. 控制在 {word_count_hint} 字左右。
""".strip()
)

prompt = observation_template.format(
    role="懂 NBA 的内容作者",
    tone="清楚、克制、有球迷视角",
    audience="喜欢詹姆斯的普通读者",
    player="勒布朗·詹姆斯",
    topic="老将核心如何影响比赛节奏",
    game_context="不引用实时比赛数据,只基于常见比赛观察来写。",
    angle="组织进攻、转换节奏、关键回合选择",
    word_count_hint="250",
)

print(prompt)

这段代码里有三个关键点:

  • PromptTemplate:表示一个字符串模板对象。
  • from_template():从一段模板字符串创建 PromptTemplate
  • format():把变量填进模板,得到最终要发给模型的 Prompt 字符串。

这样比手写字符串拼接清楚:

  • 模板负责结构。
  • 变量负责动态内容。
  • 缺哪个字段更容易发现。

这里的重点不是语法,而是思路:把会变化的内容变成参数,把不变的任务结构留在模板里。

模块化拆分

复杂 Prompt 不要一直往一个大字符串里塞,应该拆成多个小模块,再汇总成最终 Prompt。

在 Python 版里,可以直接用多个 PromptTemplate 手动组合。重点是掌握这个思路:拆模块,再汇总

python 复制代码
from langchain_core.prompts import PromptTemplate


# 模型身份和回答风格
persona_prompt = PromptTemplate.from_template(
    """你是一位{role},回答风格:{tone}。
面向读者:{audience}。"""
)

# 本次 NBA 观察稿的资料
context_prompt = PromptTemplate.from_template(
    """【观察资料】
球员:{player}
主题:{topic}
比赛背景:{game_context}
分析角度:{angle}"""
)

# 具体写作任务
task_prompt = PromptTemplate.from_template(
    """请写一段 NBA 球员观察稿:
1. 先给出核心判断。
2. 再展开 2~3 个观察点。
3. 不要编造具体数据。"""
)

# 输出格式
format_prompt = PromptTemplate.from_template(
    """输出要求:
- 篇幅:约 {word_count_hint} 字。
- 语气:{tone_constraint}。
- 直接输出正文,不要列太多术语。"""
)

# 最终汇总
final_prompt = PromptTemplate.from_template(
    """{persona_block}

{context_block}

{task_block}

{format_block}"""
)


def format_player_observation(data: dict[str, str]) -> str:
    """把拆开的 Prompt 模块合并成最终提示词。"""

    return final_prompt.format(
        persona_block=persona_prompt.format(**data),
        context_block=context_prompt.format(**data),
        task_block=task_prompt.format(),
        format_block=format_prompt.format(**data),
    )

这段代码里有几个名字容易混:

  • persona_promptcontext_prompttask_promptformat_prompt:每一个都是一个小模板。
  • final_prompt:负责把几个小模板的结果汇总起来。
  • format_player_observation():普通 Python 函数,用来串起整个格式化过程。
  • **data:把字典里的字段展开成模板需要的变量。

重点不是一定要用某个类,而是 Prompt 设计上不要把所有内容堆在一个大字符串里。

partial 预填变量

有些变量每次都一样,比如作者身份、读者身份、输出字数、语气要求。每次 format() 都传一遍会很重复,多余。

partial() 可以先固定一部分变量。

python 复制代码
persona_prompt_with_defaults = persona_prompt.partial(
    role="懂 NBA 的内容作者",
    tone="清楚、克制、有球迷视角",
    audience="喜欢詹姆斯的普通读者",
)

format_prompt_with_defaults = format_prompt.partial(
    word_count_hint="250",
    tone_constraint="像赛后短评,不像数据报告",
)


def format_player_observation_with_defaults(player_data: dict[str, str]) -> str:
    """只传本次变化的球员观察资料,稳定规则交给 partial 预填。"""

    return final_prompt.format(
        persona_block=persona_prompt_with_defaults.format(),
        context_block=context_prompt.format(**player_data),
        task_block=task_prompt.format(),
        format_block=format_prompt_with_defaults.format(),
    )


james_prompt = format_player_observation_with_defaults(
    {
        "player": "勒布朗·詹姆斯",
        "topic": "老将核心如何影响比赛节奏",
        "game_context": "不引用实时比赛数据,只基于常见比赛观察来写。",
        "angle": "组织进攻、转换节奏、关键回合选择",
    }
)

这段代码要看懂两步:

  • partial() 不会立刻生成最终 Prompt,它只是先固定一部分变量。
  • 后面的 format_player_observation_with_defaults() 只需要再接收本次变化的变量。

partial() 的价值不是"少写几行代码",而是把稳定规则和动态输入分开:

txt 复制代码
稳定规则:角色、语气、读者、输出格式
动态输入:球员、主题、比赛背景、分析角度

ChatPromptTemplate

PromptTemplate 生成字符串,但聊天模型更适合接收消息数组。

ChatPromptTemplate 用来给 Prompt 分配消息角色。

python 复制代码
from langchain_core.prompts import ChatPromptTemplate


chat_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "{persona_block}"),
        (
            "human",
            """观察资料:
{context_block}

任务:
{task_block}

输出格式:
{format_block}""",
        ),
    ]
)

messages = chat_prompt.format_messages(
    persona_block="你是一位懂 NBA 的内容作者,回答要克制、准确。",
    context_block="球员:勒布朗·詹姆斯\n主题:老将核心如何影响比赛节奏\n背景:不引用实时比赛数据。",
    task_block="写一段 NBA 球员观察稿,重点看组织进攻、转换节奏、关键回合选择。",
    format_block="输出 250 字以内中文段落。",
)

print(messages)

这段代码里:

  • ChatPromptTemplate:表示"消息数组模板"。
  • from_messages():创建一组带角色的消息模板。
  • format_messages():填充变量后,得到真正要传给聊天模型的消息数组。

这里的重点是:systemhuman 不要混在一起。

  • system 放长期规则。
  • human 放当前任务和输入。

消息角色类

除了 ("system", "...") 这种 tuple 写法,也可以用角色模板类。

python 复制代码
from langchain_core.prompts import (
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
    SystemMessagePromptTemplate,
)


persona_prompt = SystemMessagePromptTemplate.from_template(
    "你是一位{role},回答风格:{tone}。"
)

context_prompt = HumanMessagePromptTemplate.from_template(
    """【观察资料】
球员:{player}
主题:{topic}
比赛背景:{game_context}
分析角度:{angle}"""
)

task_prompt = HumanMessagePromptTemplate.from_template(
    "请基于资料写一段 NBA 球员观察稿,注意不要编造具体数据。"
)

intro_chat_prompt = ChatPromptTemplate.from_messages(
    [persona_prompt, context_prompt, task_prompt]
)

messages = intro_chat_prompt.format_messages(
    role="懂 NBA 的内容作者",
    tone="清楚、克制、有球迷视角",
    player="勒布朗·詹姆斯",
    topic="阅读比赛的经验价值",
    game_context="不引用实时比赛数据。",
    angle="防守选择、传球路线、关键回合判断",
)

这段代码和 tuple 写法做的是同一件事,只是拆得更明确:

  • SystemMessagePromptTemplate:专门生成 system 消息。
  • HumanMessagePromptTemplate:专门生成 human 消息。
  • ChatPromptTemplate.from_messages():把这些消息模板组合起来。

适合这样理解:

txt 复制代码
PromptTemplate:生成字符串
SystemMessagePromptTemplate:生成 system 消息
HumanMessagePromptTemplate:生成 human 消息
ChatPromptTemplate:把多条消息模板组装起来

MessagesPlaceholder

MessagesPlaceholder 是"消息插槽"。

它不是普通字符串占位符,而是给一组历史消息留位置。

python 复制代码
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder


prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "你是一名懂 NBA 的内容作者,回答要结合历史对话,避免重复。",
        ),
        MessagesPlaceholder("history"),
        ("human", "这是用户本轮的新问题:{current_input}"),
    ]
)

messages = prompt.format_messages(
    history=[
        HumanMessage(content="这篇观察稿不要编造实时数据。"),
        AIMessage(content="明白,只基于常见比赛观察来写。"),
    ],
    current_input="写一段詹姆斯如何影响比赛节奏的观察。",
)

这段代码里最重要的是 MessagesPlaceholder("history")

  • history 是插槽名字。
  • format_messages() 时,必须给它传一组真实消息。
  • 它适合放多轮对话历史,而不是放普通字符串。

注意:

  • history 必须是一组消息。
  • 它适合接 Memory 保存的历史对话。
  • 不要把历史对话拼成一个巨大字符串再塞进去。

FewShotPromptTemplate

Few-shot 的意思是"给模型几个例子,让它照着格式回答"。

python 复制代码
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate


examples = [
    {
        "player": "勒布朗·詹姆斯",
        "answer": "詹姆斯的价值不只在得分,还在于他能用阅读比赛的能力影响队友站位和进攻节奏。",
    },
    {
        "player": "斯蒂芬·库里",
        "answer": "库里的威胁不只来自持球投篮,他的无球跑动也会持续拉扯防守阵型。",
    },
]

example_prompt = PromptTemplate.from_template(
    "球员:{player}\n解释:{answer}"
)

prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    prefix="请参考下面的例子,用同样风格写 NBA 球员观察。",
    suffix="球员:{input}\n解释:",
    input_variables=["input"],
)

formatted = prompt.format(input="凯文·杜兰特")
print(formatted)

这段代码里:

  • examples:准备好的示例数据。
  • example_prompt:每个示例应该被格式化成什么样。
  • FewShotPromptTemplate:把多个示例和当前输入合成一个完整 Prompt。
  • prefix / suffix:分别控制示例前后的说明文字。

Few-shot 适合用在:

  • 想固定输出风格。
  • 想让模型照着格式生成。
  • 规则很难说清,但例子很直观。

ExampleSelector

如果例子很多,不一定每次都全部塞给模型。

ExampleSelector 的作用是:根据当前输入,挑几个最合适的例子放进 Prompt。

这里先把名字分清楚:

  • FewShotPromptTemplate 负责"把例子放进 Prompt"。
  • ExampleSelector 负责"从很多例子里挑哪些例子"。
  • 两者经常一起用,但不是一回事。

LengthBasedExampleSelector

按长度控制例子数量,避免 Prompt 太长。

python 复制代码
from langchain_core.example_selectors import LengthBasedExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate


examples = [
    {"player": "勒布朗·詹姆斯", "answer": "重点看阅读比赛、组织进攻和关键回合选择。"},
    {"player": "斯蒂芬·库里", "answer": "重点看无球牵制、投篮威胁和空间影响。"},
    {"player": "尼古拉·约基奇", "answer": "重点看高位策应、传球视野和进攻中轴价值。"},
    {"player": "扬尼斯·阿德托昆博", "answer": "重点看冲击篮筐、转换进攻和防守覆盖。"},
]

example_prompt = PromptTemplate.from_template(
    "球员:{player}\n解释:{answer}"
)

example_selector = LengthBasedExampleSelector(
    examples=examples,
    example_prompt=example_prompt,
    max_length=120,
)

prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="请用同样风格写 NBA 球员观察。",
    suffix="球员:{input}\n解释:",
    input_variables=["input"],
)

这个选择器只关心长度,不关心语义相似度。

SemanticSimilarityExampleSelector

语义选择器会根据当前输入,挑语义最接近的例子。

这一块需要向量模型和向量库,依赖比前面更重,先了解用途即可:

txt 复制代码
用户输入:Agent 怎么查外部资料?
选择器可能挑中:RAG、Retriever、VectorStore 相关例子

适合用在:

  • few-shot 例子很多。
  • 不同问题需要不同示例。
  • 你希望 Prompt 自动挑最相关的例子。

小结

这一篇要记住几个核心点:

  1. PromptTemplate 负责字符串模板。
  2. 模块化拆分能让 Prompt 更容易维护。
  3. partial() 可以预填稳定变量。
  4. ChatPromptTemplate 负责生成带角色的消息数组。
  5. MessagesPlaceholder 给历史消息留位置。
  6. FewShotPromptTemplate 让模型照着例子回答。
  7. ExampleSelector 负责从很多例子里挑合适的例子。
相关推荐
shimly1234562 小时前
python3 uvicorn 是啥?
python
CTA量化套保3 小时前
期货量化程序 time.sleep 卡死:天勤单线程与 deadline 替代
python·区块链
摇滚侠3 小时前
SpringMVC 入门到实战 文件上传 75-77
java·后端·spring·maven·intellij-idea
GIS数据转换器3 小时前
城市排水生命线安全运行监测平台深度解析
java·运维·人工智能·python·安全·数据挖掘·无人机
贤哥哥yyds4 小时前
GBK转UTF\-8编码自动转换工具 使用文档
python
数量技术宅4 小时前
2026量化前沿:从Reddit热帖到Python实战,如何用赫斯特指数(Hurst)狙击虚假突破?
开发语言·python
华如锦4 小时前
面了很多 Java转AI Agent方向,一些面试题总结
java·开发语言·人工智能·python·ai
戴西软件4 小时前
戴西 DLM 许可授权管理系统:破解无网络环境下工业软件授权难题,助力制造企业降本增效
网络·人工智能·python·深度学习·程序人生·算法·制造
Dxy12393102165 小时前
Python线程锁:为什么多线程会“打架“,以及怎么解决
开发语言·前端·python