LCEL基本使用和高级使用

LCEL(LangChain Expression Language)是在runnalbe上封装的,如:RunnableSequence,RunnablePararllel

前三个是等价的,都是串行执行

python 复制代码
chain = runnalbe1 | runnable2
chain = runnable1.pipe(runnable2) #例子:chain = (prompt.pipe(llm).pipe(StrOutputParser()))
chain = RunnableSequence([runnable1, runnable2])

RunnableSequence是串行的,也可以并行使用RunnablePararllel,除了这两个Runnable还有很多方法,可以在这里查到:https://reference.langchain.com/python/langchain_core/

LCEL满足大部分需求,如果是复杂的,比如分支,循环,多个智能体等,推荐使用LangGraph。

事件流

  • 这是一个测试事件
  • 可以将流的过程进行分解,从而事件更细颗粒度的控制
  • langchain-core >= 0.2
  • 事件流的颗粒度:
python 复制代码
# 注意对于版本langchain-core<0.3.37,需要显式地指定事件流版本
events = []
async for event in llm.astream_events("hello",version="v2"):
    events.append(event)

事件过滤方式

1.按name来过滤

python 复制代码
chain = llm.with_config({"run_name": "model"}) | JsonOutputParser().with_config(
    {"run_name": "my_parser"}
)

max_events = 0
async for event in chain.astream_events(
    "output a list of the countries france, spain and japan and their populations in JSON format. "
    'Use a dict with an outer key of "countries" which contains a list of countries. '
    "Each country should have the key `name` and `population`",
    include_names=["my_parser"],version="v2"
):
    print(event)
    max_events += 1
    if max_events > 10:
        # Truncate output
        print("...")
        break

2.按tags来过滤

python 复制代码
chain = (llm | JsonOutputParser()).with_config({"tags": ["my_chain"]})

max_events = 0
async for event in chain.astream_events(
    'output a list of the countries france, spain and japan and their populations in JSON format. Use a dict with an outer key of "countries" which contains a list of countries. Each country should have the key `name` and `population`',
    include_tags=["my_chain"],version="v2"
):
    print(event)
    max_events += 1
    if max_events > 10:
        # Truncate output
        print("...")
        break

3.按事件阶段来过滤

python 复制代码
num_events = 0

async for event in chain.astream_events(
    "output a list of the countries france, spain and japan and their populations in JSON format. "
    'Use a dict with an outer key of "countries" which contains a list of countries. '
    "Each country should have the key `name` and `population`",version="v2"
):
    kind = event["event"]
    if kind == "on_chat_model_stream":
        print(
            f"Chat model chunk: {repr(event['data']['chunk'].content)}",
            flush=True,
        )
    if kind == "on_parser_stream":
        print(f"Parser chunk: {event['data']['chunk']}", flush=True)
    num_events += 1
    if num_events > 30:
        # Truncate the output
        print("...")
        break

链的并行使用

python 复制代码
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel

joke_chain = ChatPromptTemplate.from_template("给我讲一个关于{topic}的笑话") | llm
poem_chain = (
    ChatPromptTemplate.from_template("给我写一首关于{topic}的绝句") | llm
)

map_chain = RunnableParallel(joke=joke_chain, poem=poem_chain)

map_chain.invoke({"topic": "程序员"})

使用以下方式可以把链的graph打印出来

pip install grandalf

python 复制代码
map_chain.get_graph().print_ascii()
#以下是用节点的方式表示,不是画图了
map_chain.get_graph()

链的高级使用

1.使用@chain自定义Runnable函数

python 复制代码
# 导入DeepSeek聊天模型
from langchain_deepseek import ChatDeepSeek
# 导入字符串输出解析器
from langchain_core.output_parsers import StrOutputParser
# 导入chain装饰器,用于创建自定义链
from langchain_core.runnables import chain
from langchain_core.prompts import ChatPromptTemplate

# 初始化DeepSeek大语言模型
llm = ChatDeepSeek(
    model="Pro/deepseek-ai/DeepSeek-V3",  # 使用DeepSeek模型
    temperature=0,  # 设置温度为0,使输出更确定性
    api_key=os.environ.get("DEEPSEEK_API_KEY"),  # 从环境变量获取API密钥
    api_base=os.environ.get("DEEPSEEK_API_BASE"),  # 从环境变量获取API基础URL
)

# 创建第一个提示模板:请求关于特定主题的笑话
prompt1 = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
# 创建第二个提示模板:询问笑话的主题是什么
prompt2 = ChatPromptTemplate.from_template("What is the subject of this joke: {joke}")


# 使用@chain装饰器定义一个自定义链
@chain
def custom_chain(text):
    # 步骤1: 将输入文本填充到第一个提示模板中
    prompt_val1 = prompt1.invoke({"topic": text})
    # 步骤2: 使用DS模型生成关于指定主题的笑话
    output1 = llm.invoke(prompt_val1)
    # 步骤3: 将模型输出解析为字符串
    parsed_output1 = StrOutputParser().invoke(output1)
    
    # 步骤4: 创建第二个处理链,用于分析笑话主题
    # 这个链将提示模板、DS模型和字符串解析器串联起来
    chain2 = prompt2 | llm | StrOutputParser()
    
    # 步骤5: 将第一步生成的笑话作为输入,让第二个链分析其主题
    return chain2.invoke({"joke": parsed_output1})


# 调用自定义链,输入主题"bears"(熊)
# 整个过程:
# 1. 先生成一个关于熊的笑话
# 2. 然后分析这个笑话的主题是什么
# 3. 返回分析结果
custom_chain.invoke("bears")

2.RunnableLambda

在链中使用函数

python 复制代码
from operator import itemgetter  # 导入itemgetter函数,用于从字典中提取值

from langchain_core.prompts import ChatPromptTemplate  # 导入聊天提示模板
from langchain_core.runnables import RunnableLambda  # 导入可运行的Lambda函数包装器
from langchain_deepseek import ChatDeepSeek  # 导入DeepSeek聊天模型
import os  # 导入os模块,用于从环境变量获取API密钥

# 初始化DeepSeek大语言模型
model = ChatDeepSeek(
    model="Pro/deepseek-ai/DeepSeek-R1",  # 使用DeepSeek-R1模型
    temperature=0,  # 设置温度为0,使输出更确定性
    api_key=os.environ.get("DEEPSEEK_API_KEY"),  # 从环境变量获取API密钥
    api_base=os.environ.get("DEEPSEEK_API_BASE"),  # 从环境变量获取API基础URL
)


def length_function(text):
    return len(text)


def _multiple_length_function(text1, text2):
    return len(text1) * len(text2)


def multiple_length_function(_dict):
    return _multiple_length_function(_dict["text1"], _dict["text2"])



# 创建一个简单的聊天提示模板,询问a和b的和
prompt = ChatPromptTemplate.from_template("what is {a} + {b}")

# 构建一个复杂的处理链
chain = (
    {
        # 处理"a"参数:
        # 1. 从输入字典中提取"foo"键的值
        # 2. 将提取的值传递给length_function函数(假设这个函数计算字符串长度)
        "a": itemgetter("foo") | RunnableLambda(length_function),
        
        # 处理"b"参数:
        # 1. 创建一个包含两个键值对的字典:
        #    - "text1": 从输入字典中提取"foo"键的值
        #    - "text2": 从输入字典中提取"bar"键的值
        # 2. 将这个字典传递给multiple_length_function函数
        #    (假设这个函数计算两个文本的总长度)
        "b": {"text1": itemgetter("foo"), "text2": itemgetter("bar")}
        | RunnableLambda(multiple_length_function),
    }
    | prompt  # 将处理后的"a"和"b"值填入提示模板
    | model  # 将填充后的提示发送给DeepSeek模型生成回答
)

# 调用链处理流程,输入一个包含"foo"和"bar"键的字典
# 整个过程:
# 1. 计算"bar"字符串的长度作为a的值
# 2. 计算"bar"和"gah"字符串的总长度作为b的值
# 3. 将这些值填入提示"what is {a} + {b}"
# 4. 让DeepSeek模型回答这个问题
chain.invoke({"foo": "bar", "bar": "gah"})

#输出

python 复制代码
AIMessage(content='The sum of 3 and 9 is calculated as follows:\n\n3 + 9 = 12\n\n**Answer:** 12', additional_kwargs={'refusal': None, 'reasoning_content': "Okay, so I need to figure out what 3 plus 9 is. Let me start by recalling how addition works. Addition is combining two numbers to get their total sum. So if I have 3 of something and then add 9 more, how many do I have in total?\n\nLet me count on my fingers. Starting with 3, if I add 9 more, I can count up 9 numbers from 3. So 3... then 4, 5, 6, 7, 8, 9, 10, 11, 12. That's 9 numbers added, so 3 + 9 should be 12. Wait, let me check that again to make sure I didn't skip a number. Starting at 3, adding 1 gets me to 4 (that's 1), then 5 (2), 6 (3), 7 (4), 8 (5), 9 (6), 10 (7), 11 (8), 12 (9). Yep, that's 9 steps, so 3 + 9 equals 12.\n\nAlternatively, I know that 9 is one less than 10. So maybe breaking it down: 3 + 9 is the same as 3 + (10 - 1). That would be 3 + 10 = 13, then subtract 1 to get 12. That also gives me 12. Hmm, same answer.\n\nOr maybe using the commutative property, since addition is commutative. So 3 + 9 is the same as 9 + 3. If I have 9 and add 3, that's easier for some people. Starting at 9, add 1 to get 10, then 2 more to get 12. So 9 + 3 = 12. Yep, still 12.\n\nAnother way: using number bonds. If I know that 9 and 1 make 10, then maybe take 1 from the 3 to make 10. So 3 is 2 + 1. Then 9 + 1 = 10, and 10 + 2 = 12. That's another method called making a ten, which is helpful for mental math. So again, 12.\n\nWait, maybe I can visualize it with objects. Imagine 3 apples and someone gives me 9 more apples. How many apples do I have? Let's count: 3 apples plus 9 apples. If I line them up, 3 + 9 would be 12 apples in total. That makes sense.\n\nI could also use a number line. Starting at 3, move 9 units to the right. Each jump is 1 unit. So from 3, jumping to 4 (1), 5 (2), 6 (3), 7 (4), 8 (5), 9 (6), 10 (7), 11 (8), 12 (9). Land on 12. So that's 3 + 9 = 12.\n\nAlternatively, using basic addition facts. If I memorize that 3 + 9 is 12, but since I might not have that memorized, I can check with subtraction. If 12 - 9 = 3, then that confirms that 3 + 9 = 12. Similarly, 12 - 3 = 9, which also checks out.\n\nI think all these methods point to the same answer. Let me just make sure I didn't make a mistake in any of the steps. For example, when I broke down 3 into 2 + 1 to add to 9, that's correct because 9 + 1 = 10, and 10 + 2 = 12. No errors there. When I used the number line, each step was counted correctly. Starting at 3 and moving 9 steps lands on 12. The commutative property also holds, so 9 + 3 is indeed the same as 3 + 9. All the different approaches confirm that the sum is 12. I don't see any mistakes in my reasoning, so I'm confident the answer is 12."}, response_metadata={'token_usage': {'completion_tokens': 901, 'prompt_tokens': 12, 'total_tokens': 913, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'Pro/deepseek-ai/DeepSeek-R1', 'system_fingerprint': '', 'finish_reason': 'stop', 'logprobs': None}, id='run-81da5dd1-267d-4aba-8d2a-026d61620ab2-0', usage_metadata={'input_tokens': 12, 'output_tokens': 901, 'total_tokens': 913, 'input_token_details': {}, 'output_token_details': {}})

定义支持流式输出的函数

python 复制代码
# 这是一个自定义解析器,将LLM输出的标记迭代器
# 按逗号分隔转换为字符串列表
def split_into_list(input: Iterator[str]) -> Iterator[List[str]]:
    # 保存部分输入直到遇到逗号
    buffer = ""
    for chunk in input:
        # 将当前块添加到缓冲区
        buffer += chunk
        # 当缓冲区中有逗号时
        while "," in buffer:
            # 在逗号处分割缓冲区
            comma_index = buffer.index(",")
            # 输出逗号之前的所有内容
            yield [buffer[:comma_index].strip()]
            # 保存剩余部分用于下一次迭代
            buffer = buffer[comma_index + 1 :]
    # 输出最后一块
    yield [buffer.strip()]


list_chain = str_chain | split_into_list

for chunk in list_chain.stream({"animal": "熊"}):
    print(chunk, flush=True)

在LCEL中,如果是一个函数,比如str_chain | split_into_list,会自动这样处理:str_chain | RunnableLambda(split_into_list)

使用RunnablePassthrough来传递值

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

# 创建一个可并行运行的处理流程
runnable = RunnableParallel(
    passed=RunnablePassthrough(),  # 第一个处理器:直接传递输入,不做修改
    modified=lambda x: x["num"] + 1,  # 第二个处理器:取出输入中的"num"值并加1
)

# 执行这个处理流程,输入是一个包含"num"字段的字典
runnable.invoke({"num": 1})
# 运行结果:{'passed': {'num': 1}, 'modified': 2}

LCEl支持在运行时候对链进行配置

动态改写模型的温度

动态切换提示词

python 复制代码
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import ConfigurableField
from langchain_openai import ChatOpenAI
import os


llm = ChatOpenAI(
    model="gpt-4",
    temperature=0,
    api_key=os.environ.get("OPENAI_API_KEY"),
    base_url=os.environ.get("OPENAI_API_BASE"),
    ).configurable_fields(
    temperature=ConfigurableField(
        id="llm_temperature",
        name="LLM Temperature",
        description="The temperature of the LLM",
    )
)

llm.invoke("随意挑选一个随机数,输出为一个整数")
为链增加记忆能力

InMemoryHistory

根据session_id来标记为同一个人,来记住它的对话。

python 复制代码
from typing import List  # 导入List类型提示
from pydantic import BaseModel, Field  # 导入Pydantic的BaseModel和Field
from langchain_core.chat_history import BaseChatMessageHistory  # 导入聊天历史基类

from langchain_core.messages import BaseMessage, AIMessage  # 导入消息基类和AI消息类



class InMemoryHistory(BaseChatMessageHistory, BaseModel):
    """内存中实现的聊天消息历史记录。"""

    messages: List[BaseMessage] = Field(default_factory=list)  # 使用空列表作为默认值存储消息

    def add_messages(self, messages: List[BaseMessage]) -> None:
        """添加一组消息到存储中"""
        self.messages.extend(messages)

    def clear(self) -> None:
        """清空所有消息"""
        self.messages = []

# 这里我们使用全局变量来存储聊天消息历史。
# 这样可以更容易地检查它以查看底层结果。
store = {}  # 创建空字典用于存储不同会话的历史记录

def get_by_session_id(session_id: str) -> BaseChatMessageHistory:
    """根据会话ID获取历史记录,如果不存在则创建新的"""
    if session_id not in store:
        store[session_id] = InMemoryHistory()  # 为新会话创建新的历史记录对象
    return store[session_id]


# 获取会话ID为"1"的历史记录
history = get_by_session_id("1")
# 添加一条AI消息到历史记录
history.add_message(AIMessage(content="你好"))  # 修改为中文消息
# 打印存储的所有历史记录
print(store)  # 将输出包含会话"1"的历史记录,其中有一条"你好"的AI消息

输出:

python 复制代码
{'1': InMemoryHistory(messages=[AIMessage(content='你好', additional_kwargs={}, response_metadata={})])}

在链中增加短时记忆

python 复制代码
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder  # 导入聊天提示模板和消息占位符
from langchain_core.runnables.history import RunnableWithMessageHistory  # 导入带历史记录的可运行组件
from langchain_deepseek import ChatDeepSeek
import os

llm = ChatDeepSeek(
    model="Pro/deepseek-ai/DeepSeek-V3",
    temperature=0,
    api_key=os.environ.get("DEEPSEEK_API_KEY"),
    api_base=os.environ.get("DEEPSEEK_API_BASE"),
)

# 创建聊天提示模板,包含系统提示、历史记录和用户问题
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个擅长{ability}的助手"),  # 系统角色提示,使用ability变量定义助手专长
    MessagesPlaceholder(variable_name="history"),  # 放置历史消息的占位符
    ("human", "{question}"),  # 用户问题的占位符
])

# 将提示模板与DS模型连接成一个链
chain = prompt | llm

# 创建带有消息历史功能的可运行链
chain_with_history = RunnableWithMessageHistory(
    chain,  # 基础链
    # 使用上一个示例中定义的get_by_session_id函数获取历史记录
    get_by_session_id,
    input_messages_key="question",  # 指定输入消息的键名
    history_messages_key="history",  # 指定历史消息的键名
)

# 首次调用链,询问余弦的含义
print(chain_with_history.invoke(  # noqa: T201
    {"ability": "math", "question": "余弦函数是什么意思?"},  # 输入参数
    config={"configurable": {"session_id": "foo"}}  # 配置会话ID为"foo"
))

# 打印存储中的历史记录
# 此时应包含第一次对话的问题和回答
print(store)  

输出:

python 复制代码
content='余弦函数(通常记作 \\(\\cos\\))是三角函数的一种,用于描述直角三角形中一个锐角的邻边与斜边的比值,或者在单位圆中表示横坐标与半径的关系。以下是余弦函数的详细解释:\n\n### 1. **直角三角形中的定义**\n在直角三角形中,余弦值定义为:\n\\[\n\\cos \\theta = \\frac{\\text{邻边}}{\\text{斜边}}\n\\]\n其中:\n- \\(\\theta\\) 是一个锐角,\n- **邻边** 是该角相邻的直角边,\n- **斜边** 是直角三角形的斜边(最长边)。\n\n**例子**:若一个角 \\(\\theta\\) 的邻边长为 3,斜边为 5,则 \\(\\cos \\theta = \\frac{3}{5}\\)。\n\n---\n\n### 2. **单位圆中的定义**\n在直角坐标系中,以原点为中心、半径为 1 的单位圆上,余弦值等于角度 \\(\\theta\\) 的终边与圆交点的 **横坐标(x 坐标)**:\n\\[\n\\cos \\theta = x\n\\]\n- 当 \\(\\theta\\) 从 \\(0\\) 增加到 \\(2\\pi\\)(360°)时,余弦值从 1 递减到 -1,再回到 1,呈现周期性变化。\n\n---\n\n### 3. **余弦函数的性质**\n- **周期性**:余弦函数的周期为 \\(2\\pi\\)(或 360°),即 \\(\\cos(\\theta + 2\\pi) = \\cos \\theta\\)。\n- **取值范围**:值域为 \\([-1, 1]\\)。\n- **偶函数**:满足 \\(\\cos(-\\theta) = \\cos \\theta\\),图像关于 y 轴对称。\n- **与正弦函数的关系**:\\(\\cos \\theta = \\sin\\left(\\theta + \\frac{\\pi}{2}\\right)\\)。\n\n---\n\n### 4. **图像特征**\n余弦函数的图像(波形图)特点:\n- **起点**:\\(\\cos 0 = 1\\)。\n- **零点**:在 \\(\\theta = \\frac{\\pi}{2}, \\frac{3\\pi}{2}, \\ldots\\) 处值为 0。\n- **极值点**:在 \\(\\theta = 0, \\pi, 2\\pi, \\ldots\\) 处取得最大值 1 或最小值 -1。\n\n---\n\n### 5. **应用场景**\n- **几何**:计算角度或边长。\n- **物理**:描述简谐振动(如弹簧运动、交流电)。\n- **工程**:信号处理、傅里叶分析等。\n\n---\n\n### 示例计算\n**问题**:求 \\(\\cos 60^\\circ\\) 的值。  \n**解**:  \n在单位圆中,\\(60^\\circ\\) 对应的坐标为 \\(\\left(\\frac{1}{2}, \\frac{\\sqrt{3}}{2}\\right)\\),因此:\n\\[\n\\cos 60^\\circ = \\frac{1}{2}\n\\]\n\n---\n\n通过上述定义和性质,余弦函数成为数学和科学中分析周期性现象的重要工具。如果需要进一步探讨其公式(如余弦定理)或其他扩展内容,可以继续提问!' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 651, 'prompt_tokens': 13, 'total_tokens': 664, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'Pro/deepseek-ai/DeepSeek-V3', 'system_fingerprint': '', 'id': '0195d2a9a43046e1ecabf1b587b24599', 'finish_reason': 'stop', 'logprobs': None} id='run-d92a68be-61cf-4619-8fa3-dd7ff8285b31-0' usage_metadata={'input_tokens': 13, 'output_tokens': 651, 'total_tokens': 664, 'input_token_details': {}, 'output_token_details': {}}
{'1': InMemoryHistory(messages=[AIMessage(content='你好', additional_kwargs={}, response_metadata={})]), 'foo': InMemoryHistory(messages=[HumanMessage(content='余弦函数是什么意思?', additional_kwargs={}, response_metadata={}), AIMessage(content='余弦函数(通常记作 \\(\\cos\\))是三角函数的一种,用于描述直角三角形中一个锐角的邻边与斜边的比值,或者在单位圆中表示横坐标与半径的关系。以下是余弦函数的详细解释:\n\n### 1. **直角三角形中的定义**\n在直角三角形中,余弦值定义为:\n\\[\n\\cos \\theta = \\frac{\\text{邻边}}{\\text{斜边}}\n\\]\n其中:\n- \\(\\theta\\) 是一个锐角,\n- **邻边** 是该角相邻的直角边,\n- **斜边** 是直角三角形的斜边(最长边)。\n\n**例子**:若一个角 \\(\\theta\\) 的邻边长为 3,斜边为 5,则 \\(\\cos \\theta = \\frac{3}{5}\\)。\n\n---\n\n### 2. **单位圆中的定义**\n在直角坐标系中,以原点为中心、半径为 1 的单位圆上,余弦值等于角度 \\(\\theta\\) 的终边与圆交点的 **横坐标(x 坐标)**:\n\\[\n\\cos \\theta = x\n\\]\n- 当 \\(\\theta\\) 从 \\(0\\) 增加到 \\(2\\pi\\)(360°)时,余弦值从 1 递减到 -1,再回到 1,呈现周期性变化。\n\n---\n\n### 3. **余弦函数的性质**\n- **周期性**:余弦函数的周期为 \\(2\\pi\\)(或 360°),即 \\(\\cos(\\theta + 2\\pi) = \\cos \\theta\\)。\n- **取值范围**:值域为 \\([-1, 1]\\)。\n- **偶函数**:满足 \\(\\cos(-\\theta) = \\cos \\theta\\),图像关于 y 轴对称。\n- **与正弦函数的关系**:\\(\\cos \\theta = \\sin\\left(\\theta + \\frac{\\pi}{2}\\right)\\)。\n\n---\n\n### 4. **图像特征**\n余弦函数的图像(波形图)特点:\n- **起点**:\\(\\cos 0 = 1\\)。\n- **零点**:在 \\(\\theta = \\frac{\\pi}{2}, \\frac{3\\pi}{2}, \\ldots\\) 处值为 0。\n- **极值点**:在 \\(\\theta = 0, \\pi, 2\\pi, \\ldots\\) 处取得最大值 1 或最小值 -1。\n\n---\n\n### 5. **应用场景**\n- **几何**:计算角度或边长。\n- **物理**:描述简谐振动(如弹簧运动、交流电)。\n- **工程**:信号处理、傅里叶分析等。\n\n---\n\n### 示例计算\n**问题**:求 \\(\\cos 60^\\circ\\) 的值。  \n**解**:  \n在单位圆中,\\(60^\\circ\\) 对应的坐标为 \\(\\left(\\frac{1}{2}, \\frac{\\sqrt{3}}{2}\\right)\\),因此:\n\\[\n\\cos 60^\\circ = \\frac{1}{2}\n\\]\n\n---\n\n通过上述定义和性质,余弦函数成为数学和科学中分析周期性现象的重要工具。如果需要进一步探讨其公式(如余弦定理)或其他扩展内容,可以继续提问!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 651, 'prompt_tokens': 13, 'total_tokens': 664, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'Pro/deepseek-ai/DeepSeek-V3', 'system_fingerprint': '', 'id': '0195d2a9a43046e1ecabf1b587b24599', 'finish_reason': 'stop', 'logprobs': None}, id='run-d92a68be-61cf-4619-8fa3-dd7ff8285b31-0', usage_metadata={'input_tokens': 13, 'output_tokens': 651, 'total_tokens': 664, 'input_token_details': {}, 'output_token_details': {}})])}

第二次调用链,询问余弦的反函数

python 复制代码
# 第二次调用链,询问余弦的反函数
# 由于使用相同的会话ID,模型可以参考前一次对话的上下文
print(chain_with_history.invoke(  
    {"ability": "math", "question": "它的反函数是什么?"},  # 输入参数
    config={"configurable": {"session_id": "foo"}}  # 使用相同的会话ID
))
# 再次打印存储中的历史记录
# 此时应包含两次对话的完整历史
print(store) 

增加用户与对话ID,精准控制记忆

python 复制代码
from langchain_deepseek import ChatDeepSeek
from langchain_core.runnables import (
    ConfigurableFieldSpec,
)
import os

llm = ChatDeepSeek(
    model="Pro/deepseek-ai/DeepSeek-V3",
    temperature=0,
    api_key=os.environ.get("DEEPSEEK_API_KEY"),
    api_base=os.environ.get("DEEPSEEK_API_BASE"),
)

store = {}  # 创建空字典用于存储不同用户和对话的历史记录

def get_session_history(
    user_id: str, conversation_id: str
) -> BaseChatMessageHistory:
    """
    根据用户ID和对话ID获取聊天历史记录
    如果不存在则创建新的历史记录对象
    
    参数:
        user_id: 用户的唯一标识符
        conversation_id: 对话的唯一标识符
    
    返回:
        对应的聊天历史记录对象
    """
    if (user_id, conversation_id) not in store:
        store[(user_id, conversation_id)] = InMemoryHistory()
    return store[(user_id, conversation_id)]

# 创建聊天提示模板,包含系统提示、历史记录和用户问题
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个擅长{ability}的助手"),  # 系统角色提示
    MessagesPlaceholder(variable_name="history"),  # 历史消息占位符
    ("human", "{question}"),  # 用户问题占位符
])

# 将提示模板与DS模型连接成一个链
chain = prompt | llm

# 创建带有消息历史功能的可运行链,支持用户ID和对话ID配置
with_message_history = RunnableWithMessageHistory(
    chain,  # 基础链
    get_session_history=get_session_history,  # 获取历史记录的函数
    input_messages_key="question",  # 输入消息的键名
    history_messages_key="history",  # 历史消息的键名
    history_factory_config=[  # 历史记录工厂配置
        ConfigurableFieldSpec(
            id="user_id",  # 配置字段ID
            annotation=str,  # 类型注解
            name="用户ID",  # 字段名称
            description="用户的唯一标识符",  # 字段描述
            default="",  # 默认值
            is_shared=True,  # 是否在多个调用间共享
        ),
        ConfigurableFieldSpec(
            id="conversation_id",  # 配置字段ID
            annotation=str,  # 类型注解
            name="对话ID",  # 字段名称
            description="对话的唯一标识符",  # 字段描述
            default="",  # 默认值
            is_shared=True,  # 是否在多个调用间共享
        ),
    ],
)

# 调用链,询问余弦的含义
# 指定用户ID为"123",对话ID为"1"
with_message_history.invoke(
    {"ability": "数学", "question": "李白的老婆叫什么?"},  # 输入参数
    config={"configurable": {"user_id": "123", "conversation_id": "2"}}  # 配置参数
)

长时记忆

! pip install -qU langchain-redis langchain-openai redis

python 复制代码
import os

# Use the environment variable if set, otherwise default to localhost
REDIS_URL = "redis://redis:6379"
print(f"Connecting to Redis at: {REDIS_URL}")
python 复制代码
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_redis import RedisChatMessageHistory
from langchain_deepseek import ChatDeepSeek
import os


llm = ChatDeepSeek(
    model="Pro/deepseek-ai/DeepSeek-V3",
    temperature=0,
    api_key=os.environ.get("DEEPSEEK_API_KEY"),
    api_base=os.environ.get("DEEPSEEK_API_BASE"),
)


# 初始化 Redis 聊天消息历史记录
# 使用 Redis 存储聊天历史,需要提供会话 ID 和 Redis 连接 URL
#它会默认链接6379端口
history = RedisChatMessageHistory(session_id="user_123", redis_url=REDIS_URL)
#history.clear()  # 首先清空历史记录
# 向历史记录中添加消息
history.add_user_message("你好,AI助手!2222")  # 添加用户消息
history.add_ai_message("你好!我今天能为你提供什么帮助?222")  # 添加AI回复消息

# 检索并显示历史消息
print("聊天历史:")
for message in history.messages:
    # 打印每条消息的类型和内容
    print(f"{type(message).__name__}: {message.content}")

输出

python 复制代码
聊天历史:
HumanMessage: 你好,AI助手!2222
AIMessage: 你好!我今天能为你提供什么帮助?222

LCEL自定义路由

python 复制代码
# 导入必要的库
from langchain_core.output_parsers import StrOutputParser  # 导入字符串输出解析器
from langchain_core.prompts import PromptTemplate  # 导入提示模板
import os  # 导入os库
from langchain_deepseek import ChatDeepSeek
# 导入RunnableLambda用于创建可运行的函数链
from langchain_core.runnables import RunnableLambda


claudeLLM = ChatDeepSeek(
    model="Pro/deepseek-ai/DeepSeek-V3",
    temperature=0,
    api_key=os.environ.get("DEEPSEEK_API_KEY"),
    api_base=os.environ.get("DEEPSEEK_API_BASE"),
)

# 创建分类链 - 用于确定问题类型
chain = (
    # 创建提示模板,要求模型将问题分类为LangChain、Anthropic或Other
    PromptTemplate.from_template(
        """根据下面的用户问题,将其分类为 `LangChain`、`Anthropic` 或 `Other`。

请只回复一个词作为答案。

<question>
{question}
</question>

分类结果:"""
    )
    | claudeLLM  # 将提示发送给Claude模型
    | StrOutputParser()  # 解析模型的输出为纯文本
)

# 创建LangChain专家链 - 模拟Harrison Chase(LangChain创始人)的回答风格
langchain_chain = PromptTemplate.from_template(
    """你将扮演一位LangChain专家。请以他的视角回答问题。 \
你的回答必须以"正如Harrison Chase告诉我的"开头,否则你会受到惩罚。 \
请回答以下问题:

问题: {question}
回答:"""
) | claudeLLM  # 将提示发送给



# 创建Anthropic专家链 - 模拟Dario Amodei(Anthropic创始人)的回答风格
anthropic_chain = PromptTemplate.from_template(
    """你将扮演一位一位Anthropic专家。请以他的视角回答问题。 \
你的回答必须以"正如Dario Amodei告诉我的"开头,否则你会受到惩罚。 \
请回答以下问题:

问题: {question}
回答:"""
) | claudeLLM  

# 创建通用回答链 - 用于处理其他类型的问题
general_chain = PromptTemplate.from_template(
    """请回答以下问题:

问题: {question}
回答:"""
) | claudeLLM 

# 自定义路由函数 - 根据问题分类结果选择合适的回答链
def route(info):
    print(info)   # 打印分类结果
    # 根据分类结果选择相应的专家链
    if "anthropic" in info["topic"].lower():  # 如果问题与Anthropic相关
        print("claude")
        return anthropic_chain  # 使用Anthropic专家链
    elif "langchain" in info["topic"].lower():  # 如果问题与LangChain相关
        print("langchain")
        return langchain_chain  # 使用LangChain专家链
    else:  # 其他类型的问题
        print("general")
        return general_chain # 使用通用回答链



# 创建完整的处理链
# 1. 首先将问题分类并保留原始问题
# 2. 然后根据分类结果路由到相应的专家链处理
full_chain = {"topic": chain, "question": lambda x: x["question"]} | RunnableLambda(route)

# 调用完整链处理用户问题
# 这个问题会被分类为Anthropic相关,然后由anthropic_chain处理
full_chain.invoke({"question": "我该如何使用langchain?"})

回退机制

当调用失败时切换大模型

python 复制代码
# 导入必要的库
from unittest.mock import patch  # 导入mock库,用于模拟函数行为
from langchain_anthropic import ChatAnthropic  # 导入Anthropic的语言模型接口
from langchain_openai import ChatOpenAI  # 导入OpenAI的语言模型接口
import httpx  # HTTP客户端库
from openai import RateLimitError  # OpenAI的速率限制错误类

# 创建模拟HTTP请求和响应对象,用于构造模拟的API错误
request = httpx.Request("GET", "/")  # 创建一个GET请求
response = httpx.Response(200, request=request)  # 创建一个状态码为200的响应
# 创建一个OpenAI速率限制错误对象,用于模拟API调用超出速率限制的情况
error = RateLimitError("rate limit", response=response, body="")

# 初始化OpenAI模型
# 注意:设置max_retries = 0是为了避免在遇到速率限制等错误时自动重试
openai_llm = ChatOpenAI(
    model="gpt-4",  # 使用GPT-4模型
    temperature=0,  # 设置温度为0,使输出更确定性
    api_key=os.environ.get("OPENAI_API_KEY"),  # 从环境变量获取API密钥
    base_url=os.environ.get("OPENAI_API_BASE"),  # 从环境变量获取基础URL
)

# 初始化Anthropic模型作为备用选项
anthropic_llm = ChatAnthropic(
    model='claude-3-5-sonnet-latest',  # 使用Claude 3.5 Sonnet模型
    api_key=os.environ.get("ANTHROPIC_API_KEY"),  # 从环境变量获取API密钥
    base_url=os.environ.get("ANTHROPIC_BASE_URL"),  # 从环境变量获取基础URL
)

# 创建带有备用选项的语言模型
# 如果主模型(OpenAI)失败,将自动尝试使用备用模型(Anthropic)
llm = openai_llm.with_fallbacks([anthropic_llm]) #这里甚至可以设置多个大模型

# 如果不设置回退机制,当API调用超量就会直接报错
# with patch("openai.resources.chat.completions.Completions.create", side_effect=error):
#    try:
#        print(llm.invoke("Why did the chicken cross the road?"))
#    except RateLimitError:
#        print("Hit error")

# 测试备用机制 - 使用中文问题
# 使用patch模拟OpenAI API调用失败(抛出速率限制错误)
with patch("openai.resources.chat.completions.Completions.create", side_effect=error):
    try:
        # 尝试调用语言模型回答中文问题
        # 由于OpenAI被模拟为失败,应该自动切换到Anthropic模型
        print(llm.invoke("为什么程序员需要学会python?"))
    except RateLimitError:
        # 如果仍然遇到错误(备用机制失败),则打印错误信息
        print("Hit error")
相关推荐
末日汐21 小时前
linux--进程学习
linux·运维·服务器·学习
海盗儿21 小时前
(一)TensorRT-LLM 初探(version: 1.0.0)
linux·运维·windows
lwprain21 小时前
mongdb、flume1.11.0使用初探
windows·mongdb
我真不会起名字啊21 小时前
Windows消息机制
windows
阿拉伯柠檬21 小时前
传输层协议TCP(二)
linux·服务器·网络·网络协议·tcp/ip·面试
Android-Flutter21 小时前
android compose CheckBox, RadioGroup 使用
android·kotlin
智航GIS21 小时前
8.11 sys 模块
数据库·windows·microsoft
ljt272496066121 小时前
Compose笔记(六十六)--ModalNavigationDrawer
android·笔记·android jetpack
独自破碎E1 天前
整理一些Linux的常用命令
linux·运维·服务器