01LangChain LCEL 与聊天机器人教程详解
LangChain LCEL 与聊天机器人教程详解
本文档整理自 LangChain 中文网两篇教程:
适合初学者按顺序阅读。你可以把它理解成一条学习路线:
- 先学会调用聊天模型。
- 再学会把模型输出变成普通字符串。
- 再学会用提示词模板组织输入。
- 再用 LCEL 把多个组件串起来。
- 最后给聊天机器人加上聊天历史、会话隔离、历史裁剪和流式输出。
一、先建立整体认识
1. LangChain 是什么
是什么:
LangChain 是一个用于开发大语言模型应用的框架。它把常见的大模型应用能力封装成组件,比如模型调用、提示词模板、输出解析、聊天历史、检索、工具调用、部署等。
解决什么问题:
直接调用大模型 API 可以完成简单问答,但当应用变复杂时,你会遇到这些问题:
- 提示词越来越难维护。
- 不同模型的调用方式不统一。
- 模型返回值包含很多元数据,不方便直接使用。
- 多步骤流程很难组织。
- 聊天机器人需要记住上下文。
- 调试时不知道中间哪一步出错。
- 想把链部署成接口时还要自己写很多胶水代码。
LangChain 的目标就是把这些常见问题模块化,让你像搭积木一样构建 LLM 应用。
为什么要学:
因为大多数 LLM 应用不是“调用一次模型就结束”,而是由多个步骤组成。例如:
用户输入 -> 提示词模板 -> 聊天模型 -> 输出解析 -> 返回结果
或者聊天机器人:
用户输入 -> 读取历史消息 -> 组装提示词 -> 调用模型 -> 保存新消息 -> 返回响应
LangChain 提供了一套统一写法,让这些流程更清晰。
二、构建最小 LLM 应用
这部分对应第一篇教程,目标是构建一个“把英文翻译成指定语言”的简单应用。
2. ChatModel 聊天模型
是什么:
ChatModel 是 LangChain 中对聊天模型的统一抽象。比如 OpenAI 的 ChatOpenAI、Anthropic 的 ChatAnthropic、Google 的 ChatVertexAI 都属于聊天模型。
解决什么问题:
不同供应商的 API 参数和返回格式不同。LangChain 用统一接口包装它们,让你可以用类似方式调用不同模型。
为什么叫 ChatModel:
因为现在主流大模型通常不是只接收一个字符串,而是接收一组“消息”。消息里可以区分系统指令、用户输入、AI 回复等角色。
基础代码:
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4")
调用模型:
from langchain_core.messages import HumanMessage, SystemMessage
messages = [
SystemMessage(content="Translate the following from English into Italian"),
HumanMessage(content="hi!"),
]
result = model.invoke(messages)
print(result)
初学者理解:
model.invoke(messages) 可以理解为:
把这一组消息发送给模型,然后等待模型返回回答。
3. Message 消息对象
是什么:
消息对象是传给聊天模型的基本数据单位。常见类型有:
SystemMessage:系统消息,用来告诉模型整体规则。HumanMessage:用户消息,表示用户说的话。AIMessage:AI 消息,表示模型曾经回复过的话。
解决什么问题:
聊天模型需要知道不同内容的角色。比如“你是一个翻译助手”应该是系统指令,而“hi!”才是用户输入。
为什么不能只传字符串:
只传字符串时,模型很难区分规则、用户问题、历史回复。消息结构能让上下文更清晰。
示例:
messages = [
SystemMessage(content="你是一个专业翻译助手。"),
HumanMessage(content="请把 hello 翻译成中文。"),
]
角色区别:
| 消息类型 | 表示什么 | 常见用途 |
|---|---|---|
SystemMessage | 系统规则 | 设定身份、语气、任务边界 |
HumanMessage | 用户输入 | 用户当前问题 |
AIMessage | 模型回复 | 保存历史对话 |
4. invoke 方法
是什么:
invoke 是 LangChain Runnable 接口中的同步调用方法。
解决什么问题:
它提供了一种统一调用方式。无论你调用的是模型、提示词模板、输出解析器,还是一整条链,都可以使用 .invoke()。
为什么重要:
LangChain 里很多组件都实现了 Runnable 接口。统一接口让组件之间更容易组合。
示例:
response = model.invoke(messages)
后面你会看到:
prompt_value = prompt_template.invoke({"language": "italian", "text": "hi"})
text = parser.invoke(response)
chain_result = chain.invoke({"language": "italian", "text": "hi"})
这些都是同一种调用思想。
5. AIMessage 返回值
是什么:
模型返回的通常不是单纯字符串,而是一个 AIMessage 对象。
解决什么问题:
除了回答文本,模型返回中还可能包含 token 用量、模型名称、停止原因、运行 ID 等元数据。
为什么不直接返回字符串:
真实应用里经常需要这些元数据,比如统计成本、排查模型是否被截断、记录调用链路。
示意:
AIMessage(
content="Ciao!",
response_metadata={...}
)
如果你只想要文本,就需要输出解析器。
6. 输出解析器 StrOutputParser
是什么:
StrOutputParser 是一个输出解析器,用来把模型返回的 AIMessage 转成普通字符串。
解决什么问题:
很多时候我们只关心模型回答文本,不想处理完整消息对象和元数据。
为什么需要它:
如果不解析,你后续代码可能要写:
response.content
当链条变长时,手动处理会变乱。输出解析器可以作为链的一部分自动执行。
单独使用:
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
result = model.invoke(messages)
text = parser.invoke(result)
print(text)
放进链里:
chain = model | parser
text = chain.invoke(messages)
初学者理解:
模型输出 AIMessage -> 解析器提取 content -> 得到字符串
7. LCEL 管道符 |
是什么:
LCEL 是 LangChain Expression Language,中文可以理解为“LangChain 表达式语言”。它允许你用 | 把多个组件串起来。
解决什么问题:
LLM 应用通常是多步骤流程。LCEL 让你用简洁方式描述这些步骤的顺序。
为什么用 |:
因为它像 Unix 管道一样,左边的输出会传给右边作为输入。
示例:
chain = model | parser
含义是:
输入消息 -> 调用模型 -> 把模型结果交给 parser -> 输出字符串
更完整的链:
chain = prompt_template | model | parser
含义是:
输入字典 -> 生成消息 -> 调用模型 -> 解析为字符串
为什么 LCEL 很重要:
- 写法清晰。
- 支持追踪。
- 支持流式处理。
- 支持批量调用。
- 支持组合更复杂的链。
8. PromptTemplate 提示词模板
是什么:
提示词模板是一种把原始输入转换成模型消息的组件。
解决什么问题:
如果每次都手写消息列表,代码会重复且不易维护。模板可以把固定部分和变量部分分开。
为什么需要模板:
比如翻译应用中,系统规则基本固定,变化的是目标语言和待翻译文本。
示例:
from langchain_core.prompts import ChatPromptTemplate
system_template = "Translate the following into {language}:"
prompt_template = ChatPromptTemplate.from_messages(
[
("system", system_template),
("user", "{text}"),
]
)
这里有两个变量:
language:目标语言。text:待翻译文本。
调用模板:
result = prompt_template.invoke({
"language": "italian",
"text": "hi",
})
得到的是类似这样的消息:
SystemMessage: Translate the following into italian:
HumanMessage: hi
初学者理解:
提示词模板就是“带变量的消息生成器”。
9. 最小翻译链
是什么:
一个由提示词模板、聊天模型、输出解析器组成的完整链。
解决什么问题:
它把用户输入的字典转换成最终字符串结果,中间不需要手动处理每一步。
完整代码:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
model = ChatOpenAI(model="gpt-4")
prompt_template = ChatPromptTemplate.from_messages(
[
("system", "Translate the following into {language}:"),
("user", "{text}"),
]
)
parser = StrOutputParser()
chain = prompt_template | model | parser
result = chain.invoke({
"language": "italian",
"text": "hi",
})
print(result)
流程图:
{"language": "italian", "text": "hi"}
|
v
ChatPromptTemplate 生成消息列表
|
v
ChatModel 调用大模型
|
v
StrOutputParser 提取字符串
|
v
"ciao"
三、调试与部署
10. LangSmith
是什么:
LangSmith 是 LangChain 生态中的调试、追踪、可观测性平台。
解决什么问题:
当链条变复杂后,你需要知道:
- 每一步输入是什么。
- 每一步输出是什么。
- 哪次模型调用失败了。
- token 用了多少。
- 延迟在哪里。
- 模型为什么给出某个结果。
为什么重要:
LLM 应用不像普通函数那样完全可预测。追踪中间过程能帮助你快速定位问题。
初学者建议:
刚开始可以不强制使用 LangSmith,但当你开始写多步骤链、RAG、Agent 时,它会非常有用。
11. LangServe
是什么:
LangServe 可以把 LangChain 的链部署成 REST API。
解决什么问题:
你写好的链如果只在本地脚本里运行,前端或其他服务没法方便调用。LangServe 可以快速把链变成 HTTP 服务。
为什么有用:
当你想做一个 Web 应用、聊天页面、后端接口时,链需要作为服务暴露出去。
服务端示例:
#!/usr/bin/env python
from fastapi import FastAPI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langserve import add_routes
prompt_template = ChatPromptTemplate.from_messages(
[
("system", "Translate the following into {language}:"),
("user", "{text}"),
]
)
model = ChatOpenAI()
parser = StrOutputParser()
chain = prompt_template | model | parser
app = FastAPI(
title="LangChain Server",
version="1.0",
description="A simple API server using LangChain's Runnable interfaces",
)
add_routes(app, chain, path="/chain")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="localhost", port=8000)
启动:
python serve.py
访问:
http://localhost:8000/chain/playground/
12. RemoteRunnable
是什么:
RemoteRunnable 是 LangServe 的客户端工具,可以像调用本地链一样调用远程链。
解决什么问题:
服务端链已经部署成接口后,客户端不需要自己拼 HTTP 请求,可以继续用 .invoke() 风格调用。
示例:
from langserve import RemoteRunnable
remote_chain = RemoteRunnable("http://localhost:8000/chain/")
result = remote_chain.invoke({
"language": "italian",
"text": "hi",
})
print(result)
初学者理解:
本地 chain.invoke(...) -> 直接运行本地链
remote_chain.invoke(...) -> 通过 HTTP 调用远程链
四、构建聊天机器人
这部分对应第二篇教程。第一篇的翻译应用只调用一次模型,而聊天机器人需要多轮对话能力。
13. 聊天机器人与普通 LLM 调用的区别
是什么:
普通 LLM 调用只处理当前输入;聊天机器人需要处理当前输入加历史上下文。
解决什么问题:
用户通常会连续提问。例如:
用户:Hi! I'm Bob.
AI:Hello Bob!
用户:What's my name?
如果模型看不到第一轮对话,它就不知道用户叫 Bob。
为什么模型不会自动记住:
大模型 API 本身通常是无状态的。每一次请求都是独立的。它不会自动知道你上一次说了什么,除非你把历史消息再次传给它。
错误示例:
model.invoke([HumanMessage(content="Hi! I'm Bob")])
model.invoke([HumanMessage(content="What's my name?")])
第二次调用只传了当前问题,没有传之前的“I’m Bob”,所以模型无法回答。
14. 聊天历史 Chat History
是什么:
聊天历史是一组按时间顺序排列的消息,包括用户消息和 AI 消息。
解决什么问题:
让模型在当前问题之外,还能看到之前对话内容。
为什么重要:
聊天机器人的“记忆”本质上不是模型真的记住了,而是你把历史对话作为上下文重新发给模型。
示例:
from langchain_core.messages import HumanMessage, AIMessage
messages = [
HumanMessage(content="Hi! I'm Bob"),
AIMessage(content="Hello Bob! How can I assist you today?"),
HumanMessage(content="What's my name?"),
]
response = model.invoke(messages)
print(response.content)
这次模型有机会知道用户叫 Bob,因为历史消息被一起传入。
15. BaseChatMessageHistory
是什么:
BaseChatMessageHistory 是 LangChain 对聊天历史存储的抽象。
解决什么问题:
不同应用可能把聊天历史存在不同地方:
- 内存。
- 文件。
- Redis。
- 数据库。
- 远程服务。
用统一抽象后,链可以不关心底层历史到底存在哪里。
为什么需要抽象:
学习阶段可以用内存保存,生产环境可能要换成 Redis 或数据库。如果接口统一,更换存储方式时业务代码改动更小。
16. InMemoryChatMessageHistory
是什么:
InMemoryChatMessageHistory 是把聊天历史存在 Python 内存里的实现。
解决什么问题:
它适合教程、实验、临时 demo,不需要额外数据库。
为什么只适合学习或简单 demo:
程序重启后,内存数据会丢失。多个服务实例之间也无法共享内存历史。
示例:
from langchain_core.chat_history import InMemoryChatMessageHistory
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
这里的 store 是一个字典:
- key 是
session_id。 - value 是这个会话对应的消息历史。
17. session_id 会话 ID
是什么:
session_id 是区分不同用户或不同聊天窗口的标识。
解决什么问题:
如果没有会话 ID,所有用户的聊天历史可能混在一起。
为什么重要:
假设两个用户同时聊天:
用户 A:我叫 Bob
用户 B:我叫 Alice
如果没有隔离历史,用户 B 可能看到用户 A 的上下文,这是严重问题。
示例:
config = {
"configurable": {
"session_id": "abc123"
}
}
RunnableWithMessageHistory 会根据这个 ID 找到对应的历史记录。
18. RunnableWithMessageHistory
是什么:
RunnableWithMessageHistory 是一个包装器,用来给已有链自动添加消息历史能力。
解决什么问题:
你不用每次手动读取历史、拼接历史、调用模型、保存新消息。它帮你把这些流程封装起来。
为什么重要:
聊天机器人的核心流程是固定的:
读取当前会话历史
合并当前用户输入
调用模型
把用户输入和模型回复保存回历史
手写容易重复且出错,包装器能减少样板代码。
示例:
from langchain_core.runnables.history import RunnableWithMessageHistory
with_message_history = RunnableWithMessageHistory(
model,
get_session_history,
)
config = {"configurable": {"session_id": "abc123"}}
response = with_message_history.invoke(
[HumanMessage(content="Hi! I'm Bob")],
config=config,
)
response = with_message_history.invoke(
[HumanMessage(content="What's my name?")],
config=config,
)
print(response.content)
初学者理解:
model 是原始模型
with_message_history 是带记忆能力的模型包装版
19. 给聊天机器人加提示词模板
是什么:
聊天机器人不只是简单传历史消息,还可以加系统指令,比如“你是一个乐于助人的助手”。
解决什么问题:
控制机器人角色、语气、回答语言、行为边界。
为什么需要 MessagesPlaceholder:
聊天历史不是一个固定字符串,而是一组消息。MessagesPlaceholder 表示“这里插入一组历史消息”。
示例:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful assistant. Answer all questions to the best of your ability."),
MessagesPlaceholder(variable_name="messages"),
]
)
chain = prompt | model
流程:
输入 messages
|
v
系统消息 + 历史消息
|
v
模型生成回复
20. input_messages_key
是什么:
当链的输入是字典时,input_messages_key 告诉 RunnableWithMessageHistory 哪个字段是用户消息。
解决什么问题:
聊天链可能不只接收消息,还接收其他变量,比如回答语言 language。
为什么需要它:
如果输入是:
{
"messages": [HumanMessage(content="Hi")],
"language": "Chinese"
}
包装器需要知道哪个字段应该被当成聊天消息保存到历史。
示例:
with_message_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="messages",
)
21. 多变量提示词
是什么:
提示词模板可以接收多个变量,比如消息历史和回答语言。
解决什么问题:
真实聊天应用不只需要用户问题,还可能需要用户偏好、语言、角色、场景等变量。
示例:
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful assistant. Answer all questions in {language}.",
),
MessagesPlaceholder(variable_name="messages"),
]
)
chain = prompt | model
调用:
response = with_message_history.invoke(
{
"messages": [HumanMessage(content="hi! I'm Bob")],
"language": "Chinese",
},
config={"configurable": {"session_id": "abc456"}},
)
为什么好用:
你可以把“稳定规则”和“动态参数”分开管理。
五、管理对话历史
22. 为什么要裁剪聊天历史
是什么:
裁剪聊天历史就是只保留一部分消息传给模型。
解决什么问题:
大模型有上下文窗口限制,不能无限接收历史消息。历史太长还会增加成本和延迟。
为什么重要:
聊天越久,消息越多。如果每次都把全部历史传给模型,会遇到:
- 超出模型上下文长度。
- token 成本持续增加。
- 响应变慢。
- 旧信息干扰新问题。
23. trim_messages
是什么:
trim_messages 是 LangChain 用来裁剪消息列表的工具。
解决什么问题:
按 token 数或策略保留重要消息,避免上下文过长。
常见参数:
from langchain_core.messages import trim_messages
trimmer = trim_messages(
max_tokens=65,
strategy="last",
token_counter=model,
include_system=True,
allow_partial=False,
start_on="human",
)
参数解释:
| 参数 | 是什么 | 作用 |
|---|---|---|
max_tokens | 最大 token 数 | 限制最终保留的历史长度 |
strategy="last" | 保留策略 | 保留最近的消息 |
token_counter=model | token 计算器 | 用模型估算消息 token 数 |
include_system=True | 是否保留系统消息 | 通常应保留系统指令 |
allow_partial=False | 是否允许截断单条消息 | False 表示不切开一条消息 |
start_on="human" | 从用户消息开始 | 保持对话结构自然 |
为什么保留最近消息:
大多数聊天场景中,最近上下文通常最相关。
风险:
如果重要信息在很早之前出现,裁剪后模型会忘记它。例如用户一开始说“我叫 Bob”,后面历史过长被裁剪掉,模型就回答不了“我叫什么名字”。
24. RunnablePassthrough.assign
是什么:
RunnablePassthrough.assign 可以在不丢失原始输入的情况下,给输入字典新增或替换字段。
解决什么问题:
我们希望在传给 prompt 之前,把 messages 字段裁剪一下,同时保留其他字段,比如 language。
为什么需要它:
链的输入可能是:
{
"messages": [...],
"language": "English"
}
我们只想处理 messages,不想影响 language。
示例:
from operator import itemgetter
from langchain_core.runnables import RunnablePassthrough
chain = (
RunnablePassthrough.assign(
messages=itemgetter("messages") | trimmer
)
| prompt
| model
)
流程:
输入字典
|
v
取出 messages -> trimmer 裁剪 -> 放回 messages 字段
|
v
prompt 组装消息
|
v
model 生成回复
25. itemgetter
是什么:
itemgetter("messages") 是 Python 标准库 operator 里的工具,用来从字典中取出指定字段。
解决什么问题:
在 LCEL 链里,我们需要从输入字典中取出 messages 传给 trimmer。
示例:
from operator import itemgetter
get_messages = itemgetter("messages")
messages = get_messages({
"messages": ["hello"],
"language": "English",
})
结果:
["hello"]
在链中的作用:
itemgetter("messages") | trimmer
表示:
从输入字典取 messages -> 裁剪 messages
六、流式输出
26. stream 方法
是什么:
.stream() 是 LangChain Runnable 接口提供的流式调用方法。
解决什么问题:
模型生成较长回答时,如果等完整结果出来再显示,用户会觉得卡顿。流式输出可以边生成边展示。
为什么聊天应用必须重视流式:
聊天机器人的体验很依赖响应速度。即使总耗时一样,逐字或逐 token 输出也会让用户感觉系统在工作。
示例:
config = {"configurable": {"session_id": "abc15"}}
for chunk in with_message_history.stream(
{
"messages": [HumanMessage(content="hi! I'm todd. tell me a joke")],
"language": "English",
},
config=config,
):
print(chunk.content, end="")
初学者理解:
invoke: 等模型完整回答后一次性返回
stream: 模型每生成一小段就返回一小段
七、完整聊天机器人代码结构
下面是一份把主要知识点合在一起的参考结构。
from operator import itemgetter
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, trim_messages
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnablePassthrough
from langchain_core.runnables.history import RunnableWithMessageHistory
model = ChatOpenAI(model="gpt-4o-mini")
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful assistant. Answer all questions in {language}.",
),
MessagesPlaceholder(variable_name="messages"),
]
)
trimmer = trim_messages(
max_tokens=1000,
strategy="last",
token_counter=model,
include_system=True,
allow_partial=False,
start_on="human",
)
chain = (
RunnablePassthrough.assign(
messages=itemgetter("messages") | trimmer
)
| prompt
| model
)
with_message_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="messages",
)
config = {"configurable": {"session_id": "user-001"}}
response = with_message_history.invoke(
{
"messages": [HumanMessage(content="你好,我叫小明。")],
"language": "中文",
},
config=config,
)
print(response.content)
response = with_message_history.invoke(
{
"messages": [HumanMessage(content="我叫什么名字?")],
"language": "中文",
},
config=config,
)
print(response.content)
八、两篇教程的知识点关系图
基础 LLM 应用
├── ChatModel
├── Message
├── invoke
├── AIMessage
├── StrOutputParser
├── PromptTemplate
└── LCEL chain
部署与调试
├── LangSmith
├── LangServe
└── RemoteRunnable
聊天机器人
├── 聊天历史
├── session_id
├── InMemoryChatMessageHistory
├── RunnableWithMessageHistory
├── MessagesPlaceholder
├── input_messages_key
├── trim_messages
├── RunnablePassthrough.assign
└── stream
九、初学者常见问题
1. 为什么模型记不住上一轮对话
因为模型 API 本身通常无状态。每次请求都要把必要上下文重新传进去。
2. 聊天历史是不是越多越好
不是。历史太多会超出上下文窗口、增加成本、拖慢速度,还可能引入无关信息。实际项目需要裁剪或摘要。
3. SystemMessage 和 HumanMessage 有什么区别
SystemMessage 是给模型的全局规则,HumanMessage 是用户当前输入。系统消息通常优先级更高,适合放角色设定和任务规则。
4. 为什么要用 StrOutputParser
因为模型返回的是 AIMessage,里面有文本和元数据。如果只想要字符串,用 StrOutputParser 会让链更干净。
5. 为什么要用提示词模板
模板能把固定规则和动态输入分开,避免到处拼字符串,也让提示词更容易维护。
6. LCEL 的 | 到底在做什么
它把左边组件的输出传给右边组件。比如:
prompt | model | parser
表示:
先生成提示词,再调用模型,再解析输出。
7. RunnableWithMessageHistory 会自动保存什么
它会根据配置中的 session_id 找到历史,把当前用户消息和模型回复加入对应历史中。
8. session_id 应该怎么设计
实际项目中通常用用户 ID、会话 ID、聊天窗口 ID 或它们的组合。关键是不同用户、不同会话不要混在一起。
9. 内存历史能不能用于生产
一般不建议。内存历史会在程序重启后丢失,也不适合多实例部署。生产环境更常用 Redis、数据库或专门的会话存储。
10. 流式输出是不是必须
不是语法上必须,但聊天产品体验上几乎是必需。长回答如果不流式输出,用户会感觉等待很久。
十、推荐学习顺序
- 先跑通单独的
model.invoke()。 - 再加入
StrOutputParser,理解模型输出和字符串输出的区别。 - 再加入
ChatPromptTemplate,学会把变量填进提示词。 - 再用
prompt | model | parser组成完整 LCEL 链。 - 再尝试 LangServe,把链变成 API。
- 再学习聊天历史,理解模型为什么默认无状态。
- 再使用
RunnableWithMessageHistory管理多轮对话。 - 再加入
MessagesPlaceholder和多变量提示词。 - 再学习
trim_messages管理长对话。 - 最后加入
.stream()做流式体验。
十一、最重要的心智模型
1. LLM 应用是数据流
不要只把 LangChain 理解成“调用模型的库”。更准确地说,它帮你组织数据流:
输入 -> 转换 -> 模型 -> 转换 -> 输出
2. 聊天机器人的记忆来自上下文
模型不是天然记得你是谁。它能记住,是因为你的程序把历史消息放进了这次请求。
3. 提示词模板是输入工程
模板不是为了好看,而是为了稳定地把业务变量转换成模型能理解的消息结构。
4. LCEL 是组合工具
LCEL 的核心价值是组合。你可以把提示词、模型、解析器、裁剪器、历史包装器等组合成更复杂但仍然清晰的应用。
5. 调试和可观测性很重要
LLM 应用的错误可能来自提示词、历史、模型、解析器、检索结果或工具调用。LangSmith 这类追踪工具能帮你看到中间过程。
十二、从这两篇教程能延伸到什么
学完这两篇后,你已经具备继续学习以下内容的基础:
- 对话式 RAG:让聊天机器人基于外部文档回答。
- Agent:让聊天机器人可以调用工具、执行动作。
- 工具调用:让模型决定什么时候调用函数。
- 输出结构化:让模型输出 JSON、Pydantic 对象等。
- 长期记忆:把用户偏好或长期信息保存到数据库。
- 生产部署:把链部署成服务并接入前端。
十三、一句话总结
第一篇教程教你搭建最小 LLM 应用:
PromptTemplate -> ChatModel -> OutputParser
第二篇教程教你把它扩展成聊天机器人:
当前消息 + 历史消息 + 系统提示 -> ChatModel -> 保存新历史 -> 流式返回
如果你能真正理解这两条数据流,后面学习 RAG、Agent、工具调用都会轻松很多。
更多推荐




所有评论(0)