AI Agent白手起家41: LangChain 链的高级应用:函数、记忆、路由与容错

内容纲要

  • 在链中使用函数
    • @chain 装饰器将普通函数转换为 Runnable
    • RunnableLambdalambda 函数在链中的集成
    • 自定义支持流式输出的函数(yield 实现生成器)
  • 值的透传:RunnablePassthrough
  • 运行时动态配置
    • 动态调整模型温度(configurable_fields
    • 动态切换提示词模板
  • 为链增加记忆能力
    • 短时记忆:InMemoryHistory 实现多轮对话
    • 长期记忆:使用 Redis 持久化聊天记录
  • 自定义路由链:基于 LLM 分类的智能分发
  • 回退机制:with_fallbacks 在主模型失败时切换备用模型
  • 完整可运行代码(使用模拟模型,无需 API Key)

引言

在掌握了 LCEL(LangChain Expression Language)的基本链式调用后,面对复杂的业务场景,往往需要在链中嵌入自定义函数、添加记忆、实现动态路由以及错误容错。本文将深入讲解这些高级技巧,从函数集成、记忆机制到路由与回退,每个知识点都配有可运行的代码示例,帮助你将链的开发能力提升到生产级水平。

在链中使用函数

使用 @chain 装饰器快速生成链

通过 @chain 装饰器,可以将任意 Python 函数转换为 Runnable 对象,无缝融入 LCEL 管道。

python 复制代码
from langchain_core.runnables import chain
from langchain_community.chat_models.fake import FakeListChatModel
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

model = FakeListChatModel(responses=["This is a joke about dogs."])
prompt = ChatPromptTemplate.from_template("Tell a joke about {topic}")

@chain
def custom_chain(topic: str) -> str:
    # 函数内部可以自由组合组件
    chain = prompt | model | StrOutputParser()
    return chain.invoke({"topic": topic})

# 现在 custom_chain 就是一个 Runnable
print(custom_chain.invoke("dogs"))

使用 RunnableLambda 嵌入 lambda 函数

python 复制代码
from langchain_core.runnables import RunnableLambda

# 定义一个计算长度的函数
length_func = RunnableLambda(lambda x: len(x))
chain = (
    ChatPromptTemplate.from_template("Just say: {text}")
    | model
    | StrOutputParser()
    | length_func
)
print(chain.invoke({"text": "Hello"}))  # 输出数字

自定义支持流式输出的函数

若要在链的末端添加一个处理逻辑并保持流式输出,必须用 yield 实现生成器,避免使用 return(会阻塞直到全部完成)。

python 复制代码
from typing import Iterator

def stream_splitter(input_stream: Iterator[str]) -> Iterator[str]:
    buffer = ""
    for chunk in input_stream:
        buffer += chunk
        while "," in buffer:
            idx = buffer.index(",")
            yield buffer[:idx+1]
            buffer = buffer[idx+1:]
    if buffer:
        yield buffer

# 模拟流式输入
mock_stream = iter(["Cat,", "Dog", ",Bird"])
for item in stream_splitter(mock_stream):
    print(item)

实际在链中使用时,可将该生成器函数封装为 RunnableLambda 并正确设置 afunc 等,以兼容流式调用。

值的透传:RunnablePassthrough

当需要将原始输入原封不动地传递给下游时,使用 RunnablePassthrough,常见于与并行分支配合。

python 复制代码
from langchain_core.runnables import RunnableParallel, RunnablePassthrough

parallel = RunnableParallel(
    unchanged=RunnablePassthrough(),
    doubled=RunnableLambda(lambda x: x * 2)
)
print(parallel.invoke(5))  # {'unchanged': 5, 'doubled': 10}

运行时动态配置

利用 configurable_fields 可在运行时调整模型的参数或切换提示词。

动态调节温度

python 复制代码
from langchain_openai import ChatOpenAI

# 假设使用真实模型,此处用 FakeListChatModel 模拟
model = FakeListChatModel(responses=["42"])
# 为模型添加可配置字段
config_model = model.configurable_fields(
    temperature=lambda: None  # 实际可设 temperature
)
# 运行时覆盖
result = config_model.with_config(configurable={"temperature": 0.9}).invoke("")
print(result.content)

动态切换提示词

python 复制代码
from langchain_core.prompts import PromptTemplate

prompt_a = PromptTemplate.from_template("Hello {name}")
prompt_b = PromptTemplate.from_template("Hi {name} from template B")
config_prompt = prompt_a.configurable_fields()
new_prompt = config_prompt.with_config(configurable={"prompt": prompt_b})
print(new_prompt.invoke({"name": "Alice"}).text)

为链增加记忆能力

短时记忆:InMemoryHistory

使用 InMemoryHistory 存储会话中的对话历史,配合 RunnableWithMessageHistory 实现多轮对话。

python 复制代码
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.messages import HumanMessage

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."),
    ("placeholder", "{history}"),
    ("human", "{input}")
])
model = FakeListChatModel(responses=["I remember you said: 'Hello'."])
chain = prompt | model | StrOutputParser()
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="input",
    history_messages_key="history",
)

# 第一轮对话
response1 = chain_with_history.invoke(
    {"input": "Hello, my name is Alice"},
    config={"configurable": {"session_id": "user123"}}
)
print(response1)

# 第二轮对话(自动携带历史)
response2 = chain_with_history.invoke(
    {"input": "What is my name?"},
    config={"configurable": {"session_id": "user123"}}
)
print(response2)

长期记忆:使用 Redis 持久化

安装 redislangchain-community 后,可用 RedisChatMessageHistory 持久保存历史记录,重启后依然存在。

python 复制代码
# 请确保本地 Redis 服务已启动
from langchain_community.chat_message_histories import RedisChatMessageHistory

history = RedisChatMessageHistory(session_id="user_001", url="redis://localhost:6379")
history.clear()
history.add_user_message("Hi")
history.add_ai_message("Hello! How can I help?")
print(history.messages)  # 重启后仍可获取

自定义路由链

借助 LLM 对用户输入进行分类,然后根据分类结果路由到不同的专业链。

python 复制代码
from langchain_core.runnables import RunnableBranch

# 分类链:返回 "math" 或 "general"
classify_prompt = ChatPromptTemplate.from_template(
    "Classify the following question into 'math' or 'general'. Only return one word.\nQuestion: {question}"
)
classify_chain = classify_prompt | FakeListChatModel(responses=["math"]) | StrOutputParser()

# 专业链
math_chain = (
    ChatPromptTemplate.from_template("Answer the math question: {question}")
    | FakeListChatModel(responses=["2 + 2 = 4"])
    | StrOutputParser()
)
general_chain = (
    ChatPromptTemplate.from_template("Answer the general question: {question}")
    | FakeListChatModel(responses=["This is a general answer."])
    | StrOutputParser()
)

# 路由函数
def route(info):
    if "math" in info["topic"]:
        return math_chain
    else:
        return general_chain

full_chain = (
    classify_chain
    | (lambda topic: {"topic": topic, "question": lambda d: d["question"]})  # 简化处理
    | RunnableLambda(route)
)

# 实际调用
print(full_chain.invoke({"question": "What is 2+2?"}))

回退机制

使用 .with_fallbacks() 设置备用模型,当主模型因速率限制等原因失败时,自动切换到备选模型。

python 复制代码
primary_model = FakeListChatModel(responses=["Primary response"])
# 模拟主模型失败:可以让它抛出异常,这里用模拟方式跳过
fallback_model = FakeListChatModel(responses=["Fallback response"])

# 构建链,设置回退(实际主模型失败时会自动切换)
chain = (ChatPromptTemplate.from_template("Say something") 
         | primary_model 
         | StrOutputParser())
chain_with_fallback = chain.with_fallbacks([fallback_model])
# 此处因 FakeListChatModel 不会抛异常,真实场景如 API 限制会触发
print(chain_with_fallback.invoke({}))

完整可运行代码(整合示例)

以下代码整合了上述关键技巧,全部使用模拟模型,无需任何 API Key 即可运行。

python 复制代码
# 安装依赖:pip install langchain langchain-core langchain-community
from langchain_core.runnables import (
    chain, RunnableLambda, RunnablePassthrough, RunnableParallel, RunnableBranch
)
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage
from langchain_community.chat_models.fake import FakeListChatModel
from typing import Iterator

# ---------- 1. @chain 装饰器 ----------
model = FakeListChatModel(responses=["A joke about cats."])
@chain
def quick_chain(topic: str) -> str:
    prompt = ChatPromptTemplate.from_template("Tell a joke about {topic}")
    return (prompt | model | StrOutputParser()).invoke({"topic": topic})
print("@chain 输出:", quick_chain.invoke("cats"))

# ---------- 2. 流式处理函数 ----------
def aggregate_commas(stream: Iterator[str]) -> Iterator[str]:
    buf = ""
    for chunk in stream:
        buf += chunk
        while "," in buf:
            idx = buf.index(",")
            yield buf[:idx+1]
            buf = buf[idx+1:]
    if buf:
        yield buf
mock_stream = iter(["A,B", ",C"])
print("流式分割:", list(aggregate_commas(mock_stream)))

# ---------- 3. RunnablePassthrough ----------
res = RunnableParallel(orig=RunnablePassthrough(), mod=RunnableLambda(lambda x: x*2)).invoke(10)
print("Passthrough:", res)

# ---------- 4. 动态配置(模拟) ----------
base_model = FakeListChatModel(responses=["42"])
configurable_model = base_model.configurable_fields()
modified_model = configurable_model.with_config(configurable={"temperature": 0.9})
print("动态温度:", modified_model.invoke("").content)

# ---------- 5. 记忆示例 ----------
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."),
    ("placeholder", "{history}"),
    ("human", "{input}")
])
memory_model = FakeListChatModel(responses=["Yes, your name is Alice."])
memory_chain = prompt | memory_model | StrOutputParser()
with_memory = RunnableWithMessageHistory(
    memory_chain, get_session_history, "input", "history"
)
print("第一轮:", with_memory.invoke({"input": "My name is Alice"}, config={"configurable": {"session_id": "1"}}))
print("第二轮:", with_memory.invoke({"input": "What is my name?"}, config={"configurable": {"session_id": "1"}}))

# ---------- 6. 路由链 ----------
classify_responses = ["math", "general"]
classify_model = FakeListChatModel(responses=classify_responses)
classify_prompt = ChatPromptTemplate.from_template(
    "Classify the question into 'math' or 'general'. Only one word.\nQuestion: {question}"
)
classify_chain = classify_prompt | classify_model | StrOutputParser()
math_chain = (ChatPromptTemplate.from_template("Math answer: {question}") 
              | FakeListChatModel(responses=["2+2=4"]) | StrOutputParser())
general_chain = (ChatPromptTemplate.from_template("General answer: {question}") 
                 | FakeListChatModel(responses=["General reply"]) | StrOutputParser())

def route(info):
    return math_chain if "math" in info["topic"] else general_chain

# 由于分类模型返回次序问题,这里简化演示:直接使用分类结果
routing_chain = (
    {"topic": classify_chain, "question": lambda x: x["question"]}
    | RunnableLambda(route)
)
print("路由结果:", routing_chain.invoke({"question": "What is 2+2?"}))

# ---------- 7. 回退机制 ----------
primary = FakeListChatModel(responses=["Primary"])
fallback = FakeListChatModel(responses=["Fallback"])
fallback_chain = (ChatPromptTemplate.from_template("say") | primary | StrOutputParser()).with_fallbacks([fallback])
print("回退输出:", fallback_chain.invoke({}))

总结

通过本文的实战示例,我们掌握了在 LangChain 链中嵌入函数、配置动态参数、赋予链记忆能力、构建智能路由以及设置容错回退等高级技巧。

这些能力是构建健壮、灵活且具备生产级品质的 AI Agent 应用的核心基础。

相关推荐
Wang's Blog3 小时前
AI Agent白手起家37: LangChain 输出解析器实战:文本、JSON、XML 与 Pydantic
xml·langchain·json
梦想三三4 小时前
Qwen Function Calling实战:重构电商客服AI Agent
人工智能·python·langchain·大模型·rag
hboot14 小时前
AI工程师第六课 - RAG检索增强生成
后端·langchain·llm
用户31268748772020 小时前
AI Agent 开发实战(十一):Multi-Agent 协作编排
langchain·ai编程
(轻舟已过万重山)1 天前
第27章 框架实操:用 LangChain/LlamaIndex 搭建完整 RAG 系统
人工智能·ai·langchain
aGdF8E3gQ1 天前
16. LangChain ChatPromptTemplate多模态应用实战
windows·langchain
JaydenAI1 天前
[AG-UI详解-08]AG-UI客户端工具 V.S. LangChain的Headless工具
ai·langchain·agent·ag-ui·maf
香菜TTT1 天前
LangChain框架_学习笔记
笔记·学习·langchain
吃饱了得干活1 天前
向量数据库 Milvus:从零搭建 RAG 向量数据库实战
数据库·langchain·agent