Chapter 02

Chat Models 与 Prompts

ChatOpenAI / Anthropic 接入、PromptTemplate 模板系统、MessagePlaceholder 对话历史与 Few-shot 提示工程

Chat Model 的本质

在 LangChain 的语境中,Chat Model 是与 LLM 交互的标准接口。与早期的文本补全接口(输入一段文字、输出续写文字)不同,Chat Model 基于 对话消息列表(Message List)进行交互:输入是一组带角色标签的消息,输出是模型的回复消息。

这种设计更贴近现代 LLM API(如 OpenAI Chat Completions、Anthropic Messages API),并且天然支持多轮对话上下文的传递。

SystemMessage
系统指令消息,告诉模型它的角色、行为准则、回复格式等。对应 OpenAI API 中 role="system" 的消息。
HumanMessage
用户发送的消息。对应 role="user"。
AIMessage
模型的回复消息。对应 role="assistant"。在多轮对话中,历史的 AIMessage 作为上下文传递给下一轮。
ToolMessage
工具调用结果消息。当 Agent 调用工具并获得结果后,将结果包装为 ToolMessage 传回给模型。

接入主流 Chat Models

OpenAI GPT 系列

from langchain_openai import ChatOpenAI

# 基本配置
model = ChatOpenAI(
    model="gpt-4o",          # 或 "gpt-4o-mini"
    temperature=0.7,          # 创造性,0~2,建议 0~1
    max_tokens=1024,          # 最大输出 token 数
    timeout=60,               # 请求超时(秒)
    max_retries=3,            # 自动重试次数
)

# 直接调用(传入消息列表)
from langchain_core.messages import HumanMessage, SystemMessage

response = model.invoke([
    SystemMessage(content="你是一位专业的 Python 导师,用简洁的语言解答问题。"),
    HumanMessage(content="解释什么是生成器(generator)"),
])
print(response.content)       # AIMessage.content
print(response.usage_metadata) # token 用量统计

Anthropic Claude 系列

from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(
    model="claude-opus-4-5",  # 或 "claude-haiku-3-5"
    temperature=0,
    max_tokens=2048,
)

response = model.invoke([
    HumanMessage(content="用中文解释 LangChain 的 LCEL 是什么")
])
print(response.content)

模型参数调优技巧

temperature 参数

  • 0:确定性输出,适合代码生成、数学计算
  • 0.3~0.7:平衡创意与准确性,适合问答
  • 0.8~1.0:更有创意,适合文案创作
  • 大于 1.0 较少使用,输出可能不稳定

streaming 流式输出

  • 设置 streaming=True 或调用 .stream()
  • 逐 token 返回,改善用户体验
  • 适合聊天界面、长文本生成
  • 配合 LCEL 管道自动传播流式

PromptTemplate:模板化提示词

PromptTemplate 是将字符串模板与变量结合的工具。它让提示词可以复用,通过占位符动态插入不同的输入内容。

ChatPromptTemplate(最常用)

from langchain_core.prompts import ChatPromptTemplate

# 方式一:from_messages(推荐)
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一位专业的{language}开发者,用简洁的中文回答问题。"),
    ("human",  "帮我解释:{question}"),
])

# 格式化:传入变量
messages = prompt.format_messages(
    language="Python",
    question="什么是装饰器?"
)
print(messages)
# [SystemMessage(content='你是一位专业的Python开发者...'),
#  HumanMessage(content='帮我解释:什么是装饰器?')]

# 或者直接在 LCEL 管道中使用(推荐)
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

model = ChatOpenAI(model="gpt-4o-mini")
chain = prompt | model | StrOutputParser()

result = chain.invoke({
    "language": "Python",
    "question": "什么是装饰器?"
})
print(result)  # 直接得到字符串

PromptTemplate(纯文本)

from langchain_core.prompts import PromptTemplate

# 用于需要纯字符串格式的场景
template = PromptTemplate.from_template(
    "将以下内容翻译成{language}:\n\n{text}"
)

formatted = template.format(language="日语", text="Hello World")
print(formatted)  # 将以下内容翻译成日语:\n\nHello World

MessagePlaceholder:注入历史消息

MessagePlaceholder 是 ChatPromptTemplate 中的特殊占位符,用于在模板中插入一段动态的消息列表,最常见的用途是注入多轮对话历史。

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一位 AI 助手,用友好的语气回答用户问题。"),
    MessagesPlaceholder(variable_name="history"),  # 注入对话历史
    ("human", "{input}"),
])

# 模拟带历史的调用
history = [
    HumanMessage(content="我叫小明"),
    AIMessage(content="你好,小明!很高兴认识你。"),
]

messages = prompt.format_messages(
    history=history,
    input="你还记得我叫什么名字吗?"
)

model = ChatOpenAI(model="gpt-4o-mini")
response = model.invoke(messages)
print(response.content)  # 模型会记住 "小明"
MessagesPlaceholder vs 手动插入历史

MessagesPlaceholder 的优势在于:它可以接受任意长度的消息列表,并且与 LCEL 管道无缝集成。当与 RunnableWithMessageHistory(第5章)结合时,历史消息会自动从存储中加载并注入到这个占位符中。

Few-shot 提示工程

Few-shot Prompting(少样本提示)是指在提示词中给模型提供少量示例,让模型学习输出格式和风格,而不需要微调模型。这是提升模型输出质量最有效的技巧之一。

from langchain_core.prompts import (
    ChatPromptTemplate, FewShotChatMessagePromptTemplate
)

# 定义示例
examples = [
    {
        "input": "2 + 2",
        "output": "计算结果:4\n运算过程:直接加法"
    },
    {
        "input": "15 * 4",
        "output": "计算结果:60\n运算过程:15 乘以 4"
    },
]

# 定义每个示例的格式
example_prompt = ChatPromptTemplate.from_messages([
    ("human", "{input}"),
    ("ai",    "{output}"),
])

# Few-shot 提示模板
few_shot_prompt = FewShotChatMessagePromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
)

# 最终完整提示
final_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个数学计算助手,按照示例的格式回答。"),
    few_shot_prompt,
    ("human", "{input}"),
])

chain = final_prompt | model | StrOutputParser()
print(chain.invoke({"input": "7 * 8"}))

Few-shot 的选择策略

当示例数量较多时,可以使用 SemanticSimilarityExampleSelector 根据输入的语义相似性动态选择最相关的示例:

from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

selector = SemanticSimilarityExampleSelector.from_examples(
    examples=examples,           # 全部示例
    embeddings=OpenAIEmbeddings(),
    vectorstore_cls=Chroma,
    k=3,                        # 每次选最相关的 3 个
)

# 根据当前输入动态选择示例
selected = selector.select_examples({"input": "如何用 Python 排序列表?"})
print(selected)

partial_variables:预填充模板变量

有时候某些变量在运行时是固定的(比如当前日期),可以使用 partial 预填充,避免每次调用都传入:

from datetime import datetime
from langchain_core.prompts import ChatPromptTemplate

def get_current_date():
    return datetime.now().strftime("%Y年%m月%d日")

prompt = ChatPromptTemplate.from_messages([
    ("system", "今天是{date},你是一位时事分析师。"),
    ("human", "{question}"),
]).partial(date=get_current_date)  # 预填充 date 变量

# 调用时只需传入 question
chain = prompt | model | StrOutputParser()
result = chain.invoke({"question": "今日有什么重要新闻?"})

Prompt 调试技巧

在开发阶段,经常需要查看实际发送给模型的完整消息。几个实用技巧:

# 技巧1:直接调用 format_messages 查看渲染结果
messages = prompt.format_messages(language="Python", question="装饰器")
for m in messages:
    print(type(m).__name__, ":", m.content[:100])

# 技巧2:在 LCEL 管道中插入调试步骤
from langchain_core.runnables import RunnableLambda

def debug_print(x):
    print("[DEBUG]", x)
    return x

chain = prompt | RunnableLambda(debug_print) | model | StrOutputParser()

# 技巧3:开启 LangSmith 追踪(最全面,第9章详述)
# LANGCHAIN_TRACING_V2=true 环境变量开启后,所有调用自动追踪
最佳实践:始终用 ChatPromptTemplate,而非手动拼字符串

直接用 f-string 拼接提示词虽然快速,但无法与 LCEL 管道集成,不支持流式输出,也无法在 LangSmith 中正确追踪变量注入情况。养成使用 ChatPromptTemplate 的习惯,会带来长期的可维护性收益。

本章小结