LangChain表达式LCEL(五)

LCEL进阶

Manipulating inputs & output

RunnableParallel 对于操作一个 Runnable 的输出以匹配序列中下一个 Runnable 的输入格式非常有用。

这里提示的输入应该是一个带有“context”和“question”键的地图。用户输入只是问题。因此,我们需要使用检索器获取上下文,并通过“question”键下的用户输入。

from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

vectorstore = FAISS.from_texts(
    ["harrison worked at kensho"], embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever()
template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()

retrieval_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

retrieval_chain.invoke("where did harrison work?")

请注意,当将 RunnableParallel 与另一个 Runnable 组合时,我们甚至不需要将字典包装在 RunnableParallel 类中 - 类型转换已为我们处理。在链的上下文中,这些是等效的:

{"context": retriever, "question": RunnablePassthrough()}

RunnableParallel({"context": retriever, "question": RunnablePassthrough()})

RunnableParallel(context=retriever, question=RunnablePassthrough())
Using itemgetter as shorthand

注意,与 RunnableParallel 结合时,可以使用 Python 的 itemgetter 从map中提取数据。

from operator import itemgetter

from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

vectorstore = FAISS.from_texts(
    ["harrison worked at kensho"], embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever()

template = """Answer the question based only on the following context:
{context}

Question: {question}

Answer in the following language: {language}
"""
prompt = ChatPromptTemplate.from_template(template)

chain = (
    {
        "context": itemgetter("question") | retriever,
        "question": itemgetter("question"),
        "language": itemgetter("language"),
    }
    | prompt
    | model
    | StrOutputParser()
)

chain.invoke({"question": "where did harrison work", "language": "italian"})
Parallelize steps

RunnableParallel(又名 RunnableMap)可以轻松并行执行多个 Runnable,并将这些 Runnable 的输出作为映射返回。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel
from langchain_openai import ChatOpenAI

model = ChatOpenAI()
joke_chain = ChatPromptTemplate.from_template("tell me a joke about {topic}") | model
poem_chain = (
    ChatPromptTemplate.from_template("write a 2-line poem about {topic}") | model
)

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

map_chain.invoke({"topic": "bear"})
Parallelism

RunnableParallel 对于并行运行独立进程也很有用,因为映射中的每个 Runnable 都是并行执行的。

RunnablePassthrough: Passing data through

RunnablePassthrough 允许不改变或添加额外的键来传递输入,通常与 RunnableParallel 结合使用,将数据分配给映射中的新键。

RunnablePassthrough() 单独调用时,将简单地获取输入并将其传递。

使用assign参数调用RunnablePassthrough (RunnablePassthrough.assign(...)),将接收输入,并添加传递给assign函数的额外参数。

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

runnable = RunnableParallel(
    passed=RunnablePassthrough(),
    extra=RunnablePassthrough.assign(mult=lambda x: x["num"] * 3),
    modified=lambda x: x["num"] + 1,
)

runnable.invoke({"num": 1})
{'passed': {'num': 1}, 'extra': {'num': 1, 'mult': 3}, 'modified': 2}

如上所示,passed键调用RunnablePassthrough(),因此只是简单的传递了{'num': 1}

第二行中,我们使用了带有将数值乘以3的lambda的 RunnablePastshrough.assign。在这种情况下,extra 被设置为 {'num': 1, 'mult': 3},即原始值加上 mult 键。

最后,我们还使用lambda在映射中设置了第三个键 modified,将num加1,结果为 modified 键的值为 2

Retrieval Example
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

vectorstore = FAISS.from_texts(
    ["harrison worked at kensho"], embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever()
template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()

retrieval_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

retrieval_chain.invoke("where did harrison work?")

在这里,prompt的输入预期是一个带有 “context” 和 “question” 键的映射。用户输入只是问题。因此,我们需要使用我们的retriever获取上下文,并将用户输入传递到 “question” 键下。在这种情况下,RunnablePassthrough允许我们将用户的问题传递给prompt和model。

RunnableLambda: Run Custom Functions

可以在流水线中使用任意函数。

注意:这些函数的所有输入都需要是一个参数。如果有一个接受多个参数的函数,应该编写一个接受单个输入并将其解包为多个参数的包装器函数

from operator import itemgetter

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_openai import ChatOpenAI


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"])


prompt = ChatPromptTemplate.from_template("what is {a} + {b}")
model = ChatOpenAI()

chain1 = prompt | model

chain = (
    {
        "a": itemgetter("foo") | RunnableLambda(length_function),
        "b": {"text1": itemgetter("foo"), "text2": itemgetter("bar")}
        | RunnableLambda(multiple_length_function),
    }
    | prompt
    | model
)
chain.invoke({"foo": "bar", "bar": "gah"})
AIMessage(content='3 + 9 = 12', response_metadata={'finish_reason': 'stop', 'logprobs': None})
Accepting a Runnable Config

可运行的lambda函数可以选择接受一个RunnableConfig,它们可以使用该配置传递回调、标签和其他配置信息给嵌套运行。

from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableConfig

import json


def parse_or_fix(text: str, config: RunnableConfig):
    fixing_chain = (
        ChatPromptTemplate.from_template(
            "Fix the following text:\n\n```text\n{input}\n```\nError: {error}"
            " Don't narrate, just respond with the fixed data."
        )
        | ChatOpenAI()
        | StrOutputParser()
    )
    for _ in range(3):
        try:
            return json.loads(text)
        except Exception as e:
            text = fixing_chain.invoke({"input": text, "error": e}, config)
    return "Failed to parse"

from langchain.callbacks import get_openai_callback

with get_openai_callback() as cb:
    output = RunnableLambda(parse_or_fix).invoke(
        "{foo: bar}", {"tags": ["my-tag"], "callbacks": [cb]}
    )
    print(output)
    print(cb)

RunnableBranch: Dynamically route logic based on input

route允许创建非确定性链,其中前一步的输出定义了下一步。route有助于在与LLMs的交互中提供结构和一致性。

执行路由有两种方法:

  1. 使用RunnableBranch
  2. 编写自定义工厂函数,该函数接受前一步的输入并返回一个可运行的。重要的是,这应该返回一个可运行的,而不是实际执行。
Example Setup

首先,创建一个链,将传入的问题识别为有关 LangChain、Anthropic 或其他的问题:

from langchain_community.chat_models import ChatAnthropic
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate

chain = (
    PromptTemplate.from_template(
        """Given the user question below, classify it as either being about `LangChain`, `Anthropic`, or `Other`.

Do not respond with more than one word.

<question>
{question}
</question>

Classification:"""
    )
    | ChatAnthropic()
    | StrOutputParser()
)

chain.invoke({"question": "how do I call Anthropic?"})

接下来创建三个子链:

langchain_chain = (
    PromptTemplate.from_template(
        """You are an expert in langchain. \
Always answer questions starting with "As Harrison Chase told me". \
Respond to the following question:

Question: {question}
Answer:"""
    )
    | ChatAnthropic()
)

anthropic_chain = (
    PromptTemplate.from_template(
        """You are an expert in anthropic. \
Always answer questions starting with "As Dario Amodei told me". \
Respond to the following question:

Question: {question}
Answer:"""
    )
    | ChatAnthropic()
)

general_chain = (
    PromptTemplate.from_template(
        """Respond to the following question:

Question: {question}
Answer:"""
    )
    | ChatAnthropic()
)
Using a custom function (Recommended)

可以使用自定义函数在不同输出之间进行路由。

示例:

def route(info):
    if "anthropic" in info["topic"].lower():
        return anthropic_chain
    elif "langchain" in info["topic"].lower():
        return langchain_chain
    else:
        return general_chain
from langchain_core.runnables import RunnableLambda

full_chain = {"topic": chain, "question": lambda x: x["question"]}
			| RunnableLambda(route)
full_chain.invoke({"question": "how do I use Anthropic?"})
full_chain.invoke({"question": "how do I use LangChain?"})
full_chain.invoke({"question": "whats 2 + 2"})
Using a RunnableBranch

RunnableBranch 是一种特殊类型的可运行对象,它允许您定义一组条件和可运行对象以根据输入执行。它不提供上述自定义函数无法实现的任何功能,因此建议改用自定义函数。

RunnableBranch 使用一对(条件、可运行)和一个默认可运行的列表进行初始化。通过将每个条件传递给其调用的输入来选择哪个分支。它选择第一个计算结果为True的条件,并使用输入运行相应的可运行程序。

如果没有提供的条件匹配,将运行默认的可运行程序。

from langchain_core.runnables import RunnableBranch

branch = RunnableBranch(
    (lambda x: "anthropic" in x["topic"].lower(), anthropic_chain),
    (lambda x: "langchain" in x["topic"].lower(), langchain_chain),
    general_chain,
)
full_chain = {"topic": chain, "question": lambda x: x["question"]} | branch
full_chain.invoke({"question": "how do I use Anthropic?"})

Bind runtime args

如果想要在一个Runnable序列中调用一个Runnable,并传递一些常量参数,而这些参数不是前一个Runnable的输出的一部分,也不是用户输入的一部分,就可以使用Runnable.bind()来轻松地传递这些参数。

假设我们有一个简单的提示+模型序列:

from langchain.schema import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "Write out the following equation using algebraic symbols then solve it. Use the format\n\nEQUATION:...\nSOLUTION:...\n\n",
        ),
        ("human", "{equation_statement}"),
    ]
)
model = ChatOpenAI(temperature=0)
runnable = (
    {"equation_statement": RunnablePassthrough()} | prompt | model | StrOutputParser()
)

print(runnable.invoke("x raised to the third plus seven equals 12"))
EQUATION: x^3 + 7 = 12

SOLUTION:
Subtracting 7 from both sides of the equation, we get:
x^3 = 12 - 7
x^3 = 5

Taking the cube root of both sides, we get:
x = ∛5

Therefore, the solution to the equation x^3 + 7 = 12 is x = ∛5.

想要使用特定的stop词调用模型:

runnable = (
    {"equation_statement": RunnablePassthrough()}
    | prompt
    | model.bind(stop="SOLUTION")
    | StrOutputParser()
)
print(runnable.invoke("x raised to the third plus seven equals 12"))
EQUATION: x^3 + 7 = 12
Attaching OpenAI functions

绑定的一个特别有用的应用是将OpenAI函数附加到兼容的OpenAI模型上:

function = {
    "name": "solver",
    "description": "Formulates and solves an equation",
    "parameters": {
        "type": "object",
        "properties": {
            "equation": {
                "type": "string",
                "description": "The algebraic expression of the equation",
            },
            "solution": {
                "type": "string",
                "description": "The solution to the equation",
            },
        },
        "required": ["equation", "solution"],
    },
}
# Need gpt-4 to solve this one correctly
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "Write out the following equation using algebraic symbols then solve it.",
        ),
        ("human", "{equation_statement}"),
    ]
)
model = ChatOpenAI(model="gpt-4", temperature=0).bind(
    function_call={"name": "solver"}, functions=[function]
)
runnable = {"equation_statement": RunnablePassthrough()} | prompt | model
runnable.invoke("x raised to the third plus seven equals 12")
AIMessage(content='', additional_kwargs={'function_call': {'name': 'solver', 'arguments': '{\n"equation": "x^3 + 7 = 12",\n"solution": "x = ∛5"\n}'}}, example=False)
Attaching OpenAI tools
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "Get the current weather in a given location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    },
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                },
                "required": ["location"],
            },
        },
    }
]
model = ChatOpenAI(model="gpt-3.5-turbo-1106").bind(tools=tools)
model.invoke("What's the weather in SF, NYC and LA?")
AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_zHN0ZHwrxM7nZDdqTp6dkPko', 'function': {'arguments': '{"location": "San Francisco, CA", "unit": "celsius"}', 'name': 'get_current_weather'}, 'type': 'function'}, {'id': 'call_aqdMm9HBSlFW9c9rqxTa7eQv', 'function': {'arguments': '{"location": "New York, NY", "unit": "celsius"}', 'name': 'get_current_weather'}, 'type': 'function'}, {'id': 'call_cx8E567zcLzYV2WSWVgO63f1', 'function': {'arguments': '{"location": "Los Angeles, CA", "unit": "celsius"}', 'name': 'get_current_weather'}, 'type': 'function'}]})

Configure chain internals at runtime

  • configurable_fields:允许配置可运行的特定字段。
  • configurable_alternatives :可以列出在运行时设置的任何特定可运行对象的替代方案。
configure_fields
With LLMs

LLMs中可以配置temperature

from langchain.prompts import PromptTemplate
from langchain_core.runnables import ConfigurableField
from langchain_openai import ChatOpenAI

model = ChatOpenAI(temperature=0).configurable_fields(
    temperature=ConfigurableField(
        id="llm_temperature",
        name="LLM Temperature",
        description="The temperature of the LLM",
    )
)
model.invoke("pick a random number")
AIMessage(content='27')
model.with_config(configurable={"llm_temperature": 0.9}).invoke("pick a random number")
AIMessage(content='47')

还可以这么设置

prompt = PromptTemplate.from_template("Pick a random number above {x}")
chain = prompt | model
chain.invoke({"x": 0})
AIMessage(content='27')
chain.with_config(configurable={"llm_temperature": 0.9}).invoke({"x": 0})
AIMessage(content='37')
With HubRunnables

This is useful to allow for switching of prompts

from langchain.runnables.hub import HubRunnable
prompt = HubRunnable("rlm/rag-prompt").configurable_fields(
    owner_repo_commit=ConfigurableField(
        id="hub_commit",
        name="Hub Commit",
        description="The Hub commit to pull from",
    )
)
prompt.invoke({"question": "foo", "context": "bar"})
ChatPromptValue(messages=[HumanMessage(content="You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\nQuestion: foo \nContext: bar \nAnswer:")])
prompt.with_config(configurable={"hub_commit": "rlm/rag-prompt-llama"}).invoke(
    {"question": "foo", "context": "bar"}
)
ChatPromptValue(messages=[HumanMessage(content="[INST]<<SYS>> You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.<</SYS>> \nQuestion: foo \nContext: bar \nAnswer: [/INST]")])
configurable_alternatives
With LLMs
from langchain.prompts import PromptTemplate
from langchain_community.chat_models import ChatAnthropic
from langchain_core.runnables import ConfigurableField
from langchain_openai import ChatOpenAI

llm = ChatAnthropic(temperature=0).configurable_alternatives(
    # This gives this field an id
    # When configuring the end runnable, we can then use this id to configure this field
    ConfigurableField(id="llm"),
    # This sets a default_key.
    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used
    default_key="anthropic",
    # This adds a new option, with name `openai` that is equal to `ChatOpenAI()`
    openai=ChatOpenAI(),
    # This adds a new option, with name `gpt4` that is equal to `ChatOpenAI(model="gpt-4")`
    gpt4=ChatOpenAI(model="gpt-4"),
    # You can add more configuration options here
)
prompt = PromptTemplate.from_template("Tell me a joke about {topic}")
chain = prompt | llm
# By default it will call Anthropic
chain.invoke({"topic": "bears"})
# We can use `.with_config(configurable={"llm": "openai"})` to specify an llm to use
chain.with_config(configurable={"llm": "openai"}).invoke({"topic": "bears"})
# If we use the `default_key` then it uses the default
chain.with_config(configurable={"llm": "anthropic"}).invoke({"topic": "bears"})
With Prompts

在提示之间交替,可以做类似的事情

llm = ChatAnthropic(temperature=0)
prompt = PromptTemplate.from_template(
    "Tell me a joke about {topic}"
).configurable_alternatives(
    # This gives this field an id
    # When configuring the end runnable, we can then use this id to configure this field
    ConfigurableField(id="prompt"),
    # This sets a default_key.
    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used
    default_key="joke",
    # This adds a new option, with name `poem`
    poem=PromptTemplate.from_template("Write a short poem about {topic}"),
    # You can add more configuration options here
)
chain = prompt | llm
With Prompts and LLMs

配置多项内容

llm = ChatAnthropic(temperature=0).configurable_alternatives(
    # This gives this field an id
    # When configuring the end runnable, we can then use this id to configure this field
    ConfigurableField(id="llm"),
    # This sets a default_key.
    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used
    default_key="anthropic",
    # This adds a new option, with name `openai` that is equal to `ChatOpenAI()`
    openai=ChatOpenAI(),
    # This adds a new option, with name `gpt4` that is equal to `ChatOpenAI(model="gpt-4")`
    gpt4=ChatOpenAI(model="gpt-4"),
    # You can add more configuration options here
)
prompt = PromptTemplate.from_template(
    "Tell me a joke about {topic}"
).configurable_alternatives(
    # This gives this field an id
    # When configuring the end runnable, we can then use this id to configure this field
    ConfigurableField(id="prompt"),
    # This sets a default_key.
    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used
    default_key="joke",
    # This adds a new option, with name `poem`
    poem=PromptTemplate.from_template("Write a short poem about {topic}"),
    # You can add more configuration options here
)
chain = prompt | llm
# We can configure it write a poem with OpenAI
chain.with_config(configurable={"prompt": "poem", "llm": "openai"}).invoke(
    {"topic": "bears"}
)
# We can always just configure only one if we want
chain.with_config(configurable={"llm": "openai"}).invoke({"topic": "bears"})
Saving configurations

还可以轻松地将配置的链保存为自己的对象

openai_joke = chain.with_config(configurable={"llm": "openai"})
openai_joke.invoke({"topic": "bears"})

Create a runnable with the @chain decorator

使用“@chain”装饰器创建可运行程序

可以通过添加@chain装饰器将任意函数转换为链式函数。这在功能上等同于RunnableLambda中进行包装。这将通过正确跟踪链式函数来改善可观察性。在此函数内部对可运行对象的任何调用都将被跟踪为嵌套子项。还可以像使用其他可运行对象一样使用它,将其组合在链式函数中等等。

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import chain
from langchain_openai import ChatOpenAI

prompt1 = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
prompt2 = ChatPromptTemplate.from_template("What is the subject of this joke: {joke}")
model = ChatOpenAI()

@chain
def custom_chain(text):
    prompt_val1 = prompt1.invoke({"topic": text})
    output1 = model.invoke(prompt_val1)
    parsed_output1 = StrOutputParser().invoke(output1)
    chain2 = prompt2 | model | StrOutputParser()
    return chain2.invoke({"joke": parsed_output1})

custom_chain现在是一个可运行对象,这意味着您需要使用invoke来调用它。

custom_chain.invoke("bears")

Add fallbacks

添加备选模型

在LLM应用程序中,可能会出现许多故障点,无论是LLM API的问题、模型输出不佳、其他集成的问题等等。添加备选模型可以帮助您优雅地处理和隔离这些问题。

重要的是,fallbacks不仅可以应用在LLM级别上,还可以应用在整个可运行级别上。

Handing LLM API Errors

处理LLM API错误,这可能是。对 LLM API 的请求可能会因多种原因而失败 - API 可能会关闭、可能会达到速率限制等等。因此,使用备选模型可以帮助防止此类情况的发生。

重要提示:默认情况下,许多 LLM 包装器会捕获错误并重试。在使用备选时,您很可能希望将其关闭。否则第一个包装器将继续重试并且不会失败。

from langchain_community.chat_models import ChatAnthropic
from langchain_openai import ChatOpenAI

首先,模拟一下如果遇到 OpenAI 的 RateLimitError 会发生什么

from unittest.mock import patch

import httpx
from openai import RateLimitError

request = httpx.Request("GET", "/")
response = httpx.Response(200, request=request)
error = RateLimitError("rate limit", response=response, body="")
# 请注意,我们将max_retries = 0设置为避免在RateLimits等情况下重试
openai_llm = ChatOpenAI(max_retries=0)
anthropic_llm = ChatAnthropic()
llm = openai_llm.with_fallbacks([anthropic_llm])
# 让我们首先使用OpenAI LLm,以显示我们遇到了错误
with patch("openai.resources.chat.completions.Completions.create", side_effect=error):
    try:
        print(openai_llm.invoke("Why did the chicken cross the road?"))
    except RateLimitError:
        print("遇到错误")
# 现在让我们尝试使用Anthropic回退
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("遇到错误")

可以像使用普通LLM一样使用我们的“带有回退的LLM”。

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You're a nice assistant who always includes a compliment in your response",
        ),
        ("human", "Why did the {animal} cross the road"),
    ]
)
chain = prompt | llm
with patch("openai.resources.chat.completions.Completions.create", side_effect=error):
    try:
        print(chain.invoke({"animal": "kangaroo"}))
    except RateLimitError:
        print("遇到错误")
Specifying errors tohandle

指定要处理的错误

如果我们想更具体地指定回退被调用的时机,我们还可以指定要处理的错误:

llm = openai_llm.with_fallbacks(
    [anthropic_llm], exceptions_to_handle=(KeyboardInterrupt,)
)

chain = prompt | llm
with patch("openai.resources.chat.completions.Completions.create", side_effect=error):
    try:
        print(chain.invoke({"animal": "kangaroo"}))
    except RateLimitError:
        print("遇到错误")
Fallbacks for Sequences

还可以为序列创建回退,它们本身就是序列。在这里,我们使用了两个不同的模型:ChatOpenAI,然后是普通的OpenAI(不使用聊天模型)。因为OpenAI不是聊天模型,您可能希望使用不同的提示。

# 首先让我们创建一个带有ChatModel的链
# 我们在这里添加了一个字符串输出解析器,以便两者之间的输出类型相同
from langchain_core.output_parsers import StrOutputParser

chat_prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You're a nice assistant who always includes a compliment in your response",
        ),
        ("human", "Why did the {animal} cross the road"),
    ]
)
# 在这里,我们将使用一个错误的模型名称,以便轻松创建一个会出错的链
chat_model = ChatOpenAI(model_name="gpt-fake")
bad_chain = chat_prompt | chat_model | StrOutputParser()
# 现在让我们创建一个带有普通OpenAI模型的链
from langchain.prompts import PromptTemplate
from langchain_openai import OpenAI

prompt_template = """Instructions: You should always include a compliment in your response.

Question: Why did the {animal} cross the road?"""
prompt = PromptTemplate.from_template(prompt_template)
llm = OpenAI()
good_chain = prompt | llm
# 现在我们可以创建一个最终的链,将两者结合起来
chain = bad_chain.with_fallbacks([good_chain])
chain.invoke({"animal": "turtle"})

Stream custom generator functions

流式自定义生成器函数,可以在LCEL管道中使用生成器函数(即使用yield 关键字且行为类似于迭代器的函数)。

这些生成器的签名应该是 Iterator[Input] -> Iterator[Output]. 或者对于异步生成器: AsyncIterator[Input] -> AsyncIterator[Output].

这些对于以下情况非常有用:

  • 实现自定义输出解析器
  • 修改先前步骤的输出,同时保留流式处理能力

示例:为逗号分隔列表实现一个自定义输出解析器

Sync version

同步版本

from typing import Iterator, List

from langchain.prompts.chat import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template(
    "Write a comma-separated list of 5 animals similar to: {animal}"
)
model = ChatOpenAI(temperature=0.0)

str_chain = prompt | model | StrOutputParser()

# 流式输出
for chunk in str_chain.stream({"animal": "bear"}):
    print(chunk, end="", flush=True)
    
str_chain.invoke({"animal": "bear"})
# 这是一个自定义解析器,将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": "bear"}):
    print(chunk, flush=True)
['lion']
['tiger']
['wolf']
['gorilla']
['panda']
list_chain.invoke({"animal": "bear"})
['lion', 'tiger', 'wolf', 'gorilla', 'panda']
Async version

异步版本

from typing import AsyncIterator

async def asplit_into_list(input: AsyncIterator[str],) -> AsyncIterator[List[str]]:  
    buffer = ""
    async for (chunk) in input:  
        # `input` is a `async_generator` object, so use `async for`
        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 | asplit_into_list
async for chunk in list_chain.astream({"animal": "bear"}):
    print(chunk, flush=True)
    
await list_chain.ainvoke({"animal": "bear"})

Inspect your runnables

检查可运行对象

使用 LCEL 创建可运行程序后,可能经常需要检查它以更好地了解正在发生的情况。下面介绍了一些这样做的方法。

首先创建一个用于检索的LCEL示例:

from langchain.prompts import ChatPromptTemplate
from langchain.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

vectorstore = FAISS.from_texts(
    ["harrison worked at kensho"], embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever()

template = """Answer the question based only on the following context:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)
Get a graph

获取可运行对象的图形

chain.get_graph()
Print a graph

打印graph,虽然这不是非常易读,但您可以打印它以获得更容易理解的显示

chain.get_graph().print_ascii()
           +---------------------------------+         
           | Parallel<context,question>Input |         
           +---------------------------------+         
                    **               **                
                 ***                   ***             
               **                         **           
+----------------------+              +-------------+  
| VectorStoreRetriever |              | Passthrough |  
+----------------------+              +-------------+  
                    **               **                
                      ***         ***                  
                         **     **                     
           +----------------------------------+        
           | Parallel<context,question>Output |        
           +----------------------------------+        
                             *                         
                             *                         
                             *                         
                  +--------------------+               
                  | ChatPromptTemplate |               
                  +--------------------+               
                             *                         
                             *                         
                             *                         
                      +------------+                   
                      | ChatOpenAI |                   
                      +------------+                   
                             *                         
                             *                         
                             *                         
                   +-----------------+                 
                   | StrOutputParser |                 
                   +-----------------+                 
                             *                         
                             *                         
                             *                         
                +-----------------------+              
                | StrOutputParserOutput |              
                +-----------------------+              
Get the prompts

获取提示。每个链的一个重要部分是使用的提示。您可以获取链中存在的提示:

chain.get_prompts()
[ChatPromptTemplate(input_variables=['context', 'question'], messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['context', 'question'], template='Answer the question based only on the following context:\n{context}\n\nQuestion: {question}\n'))])]

Add message history (memory)

添加消息历史记录

RunnableWithMessageHistory 允许我们为某些类型的链添加消息历史记录。它包装了另一个Runnable并管理其聊天消息历史记录。

具体来说,它可用于以下任何输入类型的 Runnable:

  • 一个 BaseMessage 序列
  • 一个以序列 BaseMessage 为值的字典
  • 一个以字符串或序列 BaseMessage 为最新消息的键和以历史消息为值的键的字典

并且返回以下任何输出类型之一:

  • 一个可以作为 AIMessage 内容处理的字符串
  • 一个 BaseMessage 序列
  • 一个包含 BaseMessage 序列的字典

构建一个Runnable(接收字典作为输入并返回消息作为输出):

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai.chat_models import ChatOpenAI

model = ChatOpenAI()
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "你是一个擅长{ability}的助手。回答不超过20个字",
        ),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}"),
    ]
)
runnable = prompt | model

为了管理消息历史记录,我们需要:

  1. 这个Runnable
  2. 一个可调用对象,返回一个BaseChatMessageHistory实例

这里演示如何使用内存中的 ChatMessageHistory,以及使用 RedisChatMessageHistory 进行更持久的存储。

In-memory

内存中的存储

这里是一个简单的示例,聊天历史记录保存在内存中,这里使用全局Python字典。

先构建一个可调用的 get_session_history方法,它引用这个字典来返回一个 ChatMessageHistory 实例。可以通过在运行时向RunnableWithMessageHistory传递一个配置来指定可调用对象的参数。默认情况下,配置参数应该是一个单个字符串session_id。可以通过 history_factory_config 关键字参数进行调整。

使用单参数默认值:

from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

store = {}

def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]


with_message_history = RunnableWithMessageHistory(
    runnable,
    get_session_history,
    input_messages_key="input",
    history_messages_key="history",
)

指定了两个参数:

  • input_messages_key:要作为最新输入消息处理的键
  • history_messages_key:要添加历史消息的键

在调用这个新的 Runnable 时,通过配置参数指定相应的聊天历史记录:

with_message_history.invoke(
    {"ability": "数学", "input": "余弦函数是什么意思?"},
    config={"configurable": {"session_id": "abc123"}},
)
AIMessage(content='余弦函数是三角函数的一种,它计算的是直角三角形的邻边与斜边的比值。')
# 记住
with_message_history.invoke(
    {"ability": "数学", "input": "什么?"},
    config={"configurable": {"session_id": "abc123"}},
)
AIMessage(content='余弦是一种用于计算直角三角形中一条边的长度的数学函数。')
# 新的 session_id --> 不记住。
with_message_history.invoke(
    {"ability": "数学", "input": "什么?"},
    config={"configurable": {"session_id": "def234"}},
)
AIMessage(content='我可以帮助解决数学问题。你需要什么帮助?')

跟踪消息历史记录的配置参数还可以通过将 ConfigurableFieldSpec 对象列表传递给 history_factory_config 参数来自定义。

下面,我们使用了两个参数:user_idconversation_id

from langchain_core.runnables import ConfigurableFieldSpec

store = {}

def get_session_history(user_id: str, conversation_id: str) -> BaseChatMessageHistory:
    if (user_id, conversation_id) not in store:
        store[(user_id, conversation_id)] = ChatMessageHistory()
    return store[(user_id, conversation_id)]


with_message_history = RunnableWithMessageHistory(
    runnable,
    get_session_history,
    input_messages_key="input",
    history_messages_key="history",
    history_factory_config=[
        ConfigurableFieldSpec(
            id="user_id",
            annotation=str,
            name="用户 ID",
            description="用户的唯一标识符。",
            default="",
            is_shared=True,
        ),
        ConfigurableFieldSpec(
            id="conversation_id",
            annotation=str,
            name="对话 ID",
            description="对话的唯一标识符。",
            default="",
            is_shared=True,
        ),
    ],
)
with_message_history.invoke(
    {"ability": "数学", "input": "你好"},
    config={"configurable": {"user_id": "123", "conversation_id": "1"}},
)
Examples with runnables of different signatures

具有不同签名的Runnable示例

上述的 Runnable 接受字典作为输入并返回 BaseMessage。下面我们展示一些替代方案:

输入为消息,输出为字典

from langchain_core.messages import HumanMessage
from langchain_core.runnables import RunnableParallel

chain = RunnableParallel({"output_message": ChatOpenAI()})


def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]


with_message_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    output_messages_key="output_message",
)

with_message_history.invoke(
    [HumanMessage(content="Simone de Beauvoir 对自由意志的看法是什么")],
    config={"configurable": {"session_id": "baz"}},
)

输入为消息,输出为消息

RunnableWithMessageHistory(
    ChatOpenAI(),
    get_session_history,
)

字典,所有消息输入为单个键,消息输出

from operator import itemgetter

RunnableWithMessageHistory(
    itemgetter("input_messages") | ChatOpenAI(),
    get_session_history,
    input_messages_key="input_messages",
)
Persistent storage

持久化存储

RunnableWithMessageHistory 对于 get_session_history 可调用对象如何检索其聊天消息历史记录是不可知的。在许多情况下,持久化对话历史记录是更好的选择。

LangSmith

LangSmith 对于诸如消息历史记录注入之类的操作非常有用,否则很难理解链的各个部分的输入是什么。

请注意,LangSmith 不是必需的,但它很有帮助。

如果想使用 LangSmith,注册后,并设置您的环境变量以开始记录跟踪:

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()

更新消息历史记录实现只需要定义一个新的可调用对象,这次返回一个 RedisChatMessageHistory 实例:

from langchain_community.chat_message_histories import RedisChatMessageHistory

def get_message_history(session_id: str) -> RedisChatMessageHistory:
    return RedisChatMessageHistory(session_id, url=REDIS_URL)

with_message_history = RunnableWithMessageHistory(
    runnable,
    get_message_history,
    input_messages_key="input",
    history_messages_key="history",
)

查看第二次调用的 Langsmith 跟踪,我们可以看到在构建提示时,已注入了一个名为 “history” 的变量,它是一个包含两个消息(我们的第一个输入和第一个输出)的列表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值