LangChain LCEL 与聊天机器人教程详解

本文档整理自 LangChain 中文网两篇教程:

适合初学者按顺序阅读。你可以把它理解成一条学习路线:

  1. 先学会调用聊天模型。
  2. 再学会把模型输出变成普通字符串。
  3. 再学会用提示词模板组织输入。
  4. 再用 LCEL 把多个组件串起来。
  5. 最后给聊天机器人加上聊天历史、会话隔离、历史裁剪和流式输出。

一、先建立整体认识

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=modeltoken 计算器用模型估算消息 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. SystemMessageHumanMessage 有什么区别

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. 流式输出是不是必须

不是语法上必须,但聊天产品体验上几乎是必需。长回答如果不流式输出,用户会感觉等待很久。

十、推荐学习顺序

  1. 先跑通单独的 model.invoke()
  2. 再加入 StrOutputParser,理解模型输出和字符串输出的区别。
  3. 再加入 ChatPromptTemplate,学会把变量填进提示词。
  4. 再用 prompt | model | parser 组成完整 LCEL 链。
  5. 再尝试 LangServe,把链变成 API。
  6. 再学习聊天历史,理解模型为什么默认无状态。
  7. 再使用 RunnableWithMessageHistory 管理多轮对话。
  8. 再加入 MessagesPlaceholder 和多变量提示词。
  9. 再学习 trim_messages 管理长对话。
  10. 最后加入 .stream() 做流式体验。

十一、最重要的心智模型

1. LLM 应用是数据流

不要只把 LangChain 理解成“调用模型的库”。更准确地说,它帮你组织数据流:

输入 -> 转换 -> 模型 -> 转换 -> 输出

2. 聊天机器人的记忆来自上下文

模型不是天然记得你是谁。它能记住,是因为你的程序把历史消息放进了这次请求。

3. 提示词模板是输入工程

模板不是为了好看,而是为了稳定地把业务变量转换成模型能理解的消息结构。

4. LCEL 是组合工具

LCEL 的核心价值是组合。你可以把提示词、模型、解析器、裁剪器、历史包装器等组合成更复杂但仍然清晰的应用。

5. 调试和可观测性很重要

LLM 应用的错误可能来自提示词、历史、模型、解析器、检索结果或工具调用。LangSmith 这类追踪工具能帮你看到中间过程。

十二、从这两篇教程能延伸到什么

学完这两篇后,你已经具备继续学习以下内容的基础:

  • 对话式 RAG:让聊天机器人基于外部文档回答。
  • Agent:让聊天机器人可以调用工具、执行动作。
  • 工具调用:让模型决定什么时候调用函数。
  • 输出结构化:让模型输出 JSON、Pydantic 对象等。
  • 长期记忆:把用户偏好或长期信息保存到数据库。
  • 生产部署:把链部署成服务并接入前端。

十三、一句话总结

第一篇教程教你搭建最小 LLM 应用:

PromptTemplate -> ChatModel -> OutputParser

第二篇教程教你把它扩展成聊天机器人:

当前消息 + 历史消息 + 系统提示 -> ChatModel -> 保存新历史 -> 流式返回

如果你能真正理解这两条数据流,后面学习 RAG、Agent、工具调用都会轻松很多。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐