外观
03. Prompt 与 Message
学习目标
完成本章后,你应该能够:
- 说明 message 为什么是 Agent 的上下文载体。
- 区分 System、Human、AI 和 Tool message 的职责。
- 使用
ChatPromptTemplate渲染带变量的聊天提示词。 - 理解 Agent prompt 应该负责什么,不应该负责什么。
- 判断哪些问题不能只靠改 prompt 解决。
Message 是 Agent 的上下文载体
聊天模型看到的不是“一个神秘的提示词字符串”,而是一组有角色的 message。Agent 的上下文、用户问题、模型中间回复、工具调用结果,最终都会被组织成 message 列表交给模型。
常见 message 类型包括:
- System message:定义模型的角色、边界、输出格式和重要规则。它通常放在最前面,用来影响整轮任务的行为。
- Human message:用户输入的问题、指令或补充信息。
- AI message:模型已经生成过的回复,也可能包含工具调用请求。
- Tool message:工具执行后的结果。它告诉模型某次工具调用返回了什么。
Agent 能不能稳定运行,很大程度取决于这些 message 是否清晰、顺序是否正确、工具结果是否可读。Prompt 不是孤立文本,而是 message 结构的一部分。
PromptTemplate
PromptTemplate 用来把变量渲染进提示词。对于聊天模型,更常用的是 ChatPromptTemplate,因为它可以直接构造多条带角色的 message。
运行本章示例:
bash
uv run python examples/02_prompt_cli.py示例会把用户问题渲染到 human message 中,并先打印渲染后的 messages,再调用模型:
python
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一名严谨的 LangChain 教练。请用中文回答,并把回复分成:核心概念、最小示例、常见误区。"),
("human", "{question}"),
]
)
rendered_prompt = prompt.invoke({"question": question})
messages = rendered_prompt.to_messages()
response = model.invoke(messages)这里使用 tuple message templates,而不是把 {question} 写进普通 HumanMessage 对象。普通 message 对象通常代表已经确定的内容,不一定会参与模板渲染;tuple 模板能明确告诉 LangChain 哪些字段需要替换。
Agent Prompt 的边界
Agent prompt 应该帮助模型理解任务边界和协作方式,例如:
- 当前 Agent 的角色是什么。
- 可以使用哪些信息源和工具。
- 什么时候应该调用工具,什么时候可以直接回答。
- 输出格式应该如何组织。
- 不确定时应该承认不知道,还是继续检索或询问用户。
但是 Agent prompt 不应该承担所有系统责任。Prompt 可以约束模型行为,却不能替代工具实现、数据质量、状态管理和可观测性。
一个实用边界是:prompt 负责“告诉模型应该怎样思考和表达”,代码负责“保证工具、上下文、检索、权限和错误处理按预期工作”。
常见误区
误区一:把所有问题都归因于 prompt。
如果工具 schema 不清楚、参数类型不合理、工具返回值难以理解,模型可能无法稳定调用工具。继续堆提示词通常只会让上下文更长,问题并不会消失。
误区二:认为 prompt 可以修复模型能力不足。
某些本地模型能聊天,但不擅长严格 JSON、工具调用或长上下文推理。Prompt 可以改善表现,但不能把不可靠能力变成可靠系统。需要通过模型选择、工具设计和测试共同解决。
误区三:认为 prompt 可以修复 RAG 数据问题。
如果检索没有找回正确资料,或者切片把关键上下文拆散,模型拿不到事实依据。此时应该检查数据清洗、chunk 策略、embedding、检索参数和重排逻辑,而不是只改系统提示词。
误区四:忽略历史消息和工具消息。
Agent prompt 只是上下文的一部分。真实 Agent 运行时,历史 AI message、tool call 和 tool result 都会影响下一步决策。调试时要打印完整 messages,而不是只看最初的 system prompt。
代码阅读重点
阅读 examples/02_prompt_cli.py 时,重点看 ChatPromptTemplate.from_messages()。
这个模板把消息分成两层:
- system 消息:稳定规则,告诉模型应该扮演什么角色,回答格式是什么。
- human 消息:用户问题,通过
{question}注入。
运行脚本时会先打印渲染后的 messages,再打印模型回答。这个设计是故意的:调试 Prompt 时,不要只看模板源码,要看最终传给模型的消息。
Prompt 调试方法
当回答不符合预期时,可以按下面顺序排查:
- 最终 messages 是否包含你以为传进去的变量?
- system 消息是否太长,导致核心规则不突出?
- 用户问题是否和系统规则冲突?
- 输出格式是否要求过细,超出了本地模型的稳定能力?
- 是否应该把格式要求拆成更简单的步骤?
Prompt 调试不是不断堆规则。规则越多,模型越可能忽略某些约束。更稳的做法是把关键规则写清楚,把可验证的事情交给程序检查。
实验练习
- 把系统消息中的“三部分回答”改成“两部分回答”,观察输出变化。
- 删除系统消息,只保留 human 消息,比较回答是否更发散。
- 把默认问题改成一个非 LangChain 问题,观察系统角色对回答的约束有多强。
自测问题
- System Message 和 Human Message 的职责有什么不同?
- PromptTemplate 的价值只是字符串拼接吗?
- 为什么调试 Agent 前要先看渲染后的 messages?
完整示例
python
from __future__ import annotations
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).resolve().parents[1]))
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import ChatOllama
from examples.common.config import load_config, ollama_client_kwargs
from examples.common.ollama_check import assert_model_available, assert_ollama_running
def main() -> None:
config = load_config()
try:
assert_ollama_running(config.ollama_base_url)
assert_model_available(config.ollama_base_url, config.ollama_model)
except RuntimeError as exc:
print(f"错误:{exc}")
return
model = ChatOllama(
model=config.ollama_model,
base_url=config.ollama_base_url,
client_kwargs=ollama_client_kwargs(),
temperature=0,
)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一名严谨的 LangChain 教练。请用中文回答,并把回复分成:"
"核心概念、最小示例、常见误区。",
),
("human", "{question}"),
]
)
default_question = "LangChain 的 Agent 是什么?"
question = input(f"问题(默认:{default_question}):").strip() or default_question
rendered_prompt = prompt.invoke({"question": question})
messages = rendered_prompt.to_messages()
print("\n渲染后的 messages:")
for message in messages:
print(f"- {message.type}: {message.content}")
response = model.invoke(messages)
print(f"\nAI: {response.content}")
if __name__ == "__main__":
main()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56