内容纲要
- 在链中使用函数
@chain装饰器将普通函数转换为RunnableRunnableLambda与lambda函数在链中的集成- 自定义支持流式输出的函数(
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 持久化
安装 redis 和 langchain-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 应用的核心基础。