首页 > 其他分享 >Why the Pipe Character “|” Works in LangChain’s LCEL

Why the Pipe Character “|” Works in LangChain’s LCEL

时间:2024-05-22 09:53:16浏览次数:23  
标签:__ Runnable self Character LangChain Pipe other value ror

Why the Pipe Character “|” Works in LangChain’s LCEL

http://cncc.bingj.com/cache.aspx?q=python+pipe+operator&d=4965480815663428&mkt=en-US&setlang=en-US&w=ZTsip_Llmj7SCg1Xnjy71UfpBFEYqgVM

Introduction

In LangChain, it is now recommended to describe Chains using the LangChain Expression Language (LCEL), which utilizes the pipe character “|” similar to Linux pipes. However, in Python, “|” typically acts as a bitwise OR operator, producing a logical OR result. It was unclear how LCEL assigns a unique operational function to “|”, so I investigated.

chain = prompt | model | outputparser # Investigated this "|"
chain.invoke("Question.")

 

[Background] Operator Overloading

In Python, users can change the behavior of operators for their classes by declaring special methods like __eq__.

To define “|”, you would use __or__ and __ror__:

    __or__: Executed when on the left side of "|". In A|B, A's __or__ is called.
    __ror__: Executed when on the right side of "|". In A|B, B's __ror__ is called.

 

Examples
Example 1: Checking __or__ Behavior

Declare __or__ in classes A and B. When "|" is operated in A, it outputs "A's __or__ method is called". In B, it outputs "B's __or__ method is called".

class A:
    def __init__(self, value):
        self.value = value
    def __or__(self, other):
        print("A's __or__ method is called")
        return self.value | other.value

class B:
    def __init__(self, value):
        self.value = value
    def __or__(self, other):
        print("B's __or__ method is called")
        return self.value | other.value

objA = A(2)
objB = B(3)
result = objA | objB

Output:

A's __or__ method is called

This shows A’s “|” was executed.

If reversed,

result = objB | objA
print(result)

the output is:

B's __or__ method is called

indicating B’s “|” was executed. The __or__ of the object before "|" is executed.
Example 2: Checking __ror__ Behavior

Declare __ror__ only in class B.

class A:
    def __init__(self, value):
        self.value = value

class B:
    def __init__(self, value):
        self.value = value
    def __ror__(self, other):
        print("B's __ror__ method is called")
        return self.value | other.value

objA = A(2)
objB = B(3)
result = objA | objB

Output:

B's __ror__ method is called

The __ror__ of the object after "|" was executed.
Example 3: Checking Priority of __ror__ and __or__

Declare __or__ in class A and __ror__ in class B.

class A:
    def __init__(self, value):
        self.value = value
    def __or__(self, other):
        print("A's __or__ method is called")
        return self.value | other.value

class B:
    def __init__(self, value):
        self.value = value
    def __ror__(self, other):
        print("B's __ror__ method is called")
        return self.value | other.value

objA = A(2)
objB = B(3)
result = objA | objB

Output:

A's __or__ method is called

If __or__ is declared in the class before "|", it is executed. The __ror__ of the class after "|" is ignored.

 

[Main Topic] Examining Operator Overloading in LangChain’s Source Code

In LCEL, components like prompt, model, and outputparser are all based on the Runnable class. So, we looked into __or__ and __ror__ methods of the Runnable class.

class Runnable(Generic[Input, Output], ABC):  # Excerpt
    def __or__(
            self,
            other: Union[
                Runnable[Any, Other],
                Callable[[Any], Other],
                Callable[[Iterator[Any]], Iterator[Other]],
                Mapping[str, Union[Runnable[Any, Other], Callable[[Any], Other], Any]],
            ],
        ) -> RunnableSerializable[Input, Other]:
            """Compose this runnable with another object to create a RunnableSequence."""
            return RunnableSequence(self, coerce_to_runnable(other))

    def __ror__(
        self,
        other: Union[
            Runnable[Other, Any],
            Callable[[Other], Any],
            Callable[[Iterator[Other]], Iterator[Any]],
            Mapping[str, Union[Runnable[Other, Any], Callable[[Other], Any], Any]],
        ],
    ) -> RunnableSerializable[Other, Output]:
        """Compose this runnable with another object to create a RunnableSequence."""
        return RunnableSequence(coerce_to_runnable(other), self)

Executing self(Runnable object)|other generates and returns a RunnableSequence(self, coerce_to_runnable(other)) object. other|self(Runnable object) is also possible.

coerce_to_runnable used here converts non-Runnable standard Python components to Runnable ones but only accepts Runnable, callable, or dict. Anything else raises an exception.

def coerce_to_runnable(thing: RunnableLike) -> Runnable[Input, Output]:
    """Coerce a runnable-like object into a Runnable.

    Args:
        thing: A runnable-like object.

    Returns:
        A Runnable.
    """
    if isinstance(thing, Runnable):
        return thing
    elif inspect.isasyncgenfunction(thing) or inspect.isgeneratorfunction(thing):
        return RunnableGenerator(thing)
    elif callable(thing):
        return RunnableLambda(cast(Callable[[Input], Output], thing))
    elif isinstance(thing, dict):
        return cast(Runnable[Input, Output], RunnableParallel(thing))
    else:
        raise TypeError(
            f"Expected a Runnable, callable or dict."
            f"Instead got an unsupported type: {type(thing)}"
        )

So, it’s clear that because __or__ and __ror__ are declared in Runnable, we can use expressions like Runnable object | other (Runnable object, callable, or dict) or other | Runnable object. The returned RunnableSequence is then invoked.

Practical Use of “|” in LCEL

Try callable or dict | Runnable object.

 

from langchain_core.runnables import RunnableLambda
from operator import itemgetter

# Function that returns the length of text
def length_function(text):
    return len(text)

# In the following chain, the itemgetter fetches the value with key="foo" and passes it to the length_function.
chain = itemgetter("foo") | RunnableLambda(length_function)

# Output is 2 ("aa" has 2 characters).
chain.invoke({"foo":"aa"})

Key points:

    Use the callable itemgetter.
    Wrap length_function in RunnableLambda to make it Runnable.

However, the following results in an error:

# Error
chain = {"foo":"aa"} | RunnableLambda(length_function)
chain.invoke({"foo":"aa"})

In LCEL, dictionary values must also be runnable, callable, or a dict, likely checked recursively. If you need to include a dictionary in the chain, use the following:

chain = (lambda x: {"foo":"aa"}) | RunnableLambda(length_function)
chain.invoke({"foo":"aa"})

This makes the entire dictionary part callable, avoiding issues. The argument x in the lambda, which is the input to invoke, is unused here and thus discarded.

How the Pipe Operator Works

https://www.pinecone.io/learn/series/langchain/langchain-expression-language/

 

from langchain_core.runnables import (
    RunnableParallel,
    RunnablePassthrough
)

retriever_a = vecstore_a.as_retriever()
retriever_b = vecstore_b.as_retriever()

prompt_str = """Answer the question below using the context:

Context: {context}

Question: {question}

Answer: """
prompt = ChatPromptTemplate.from_template(prompt_str)

retrieval = RunnableParallel(
    {"context": retriever_a, "question": RunnablePassthrough()}
)

chain = retrieval | prompt | model | output_parser

 

 

| 结合 pipe库做数据预处理

https://mathdatasimplified.com/write-clean-python-code-using-pipes-3/

https://www.the-analytics.club/pipe-operations-in-python/

https://zhuanlan.zhihu.com/p/432755818

 

标签:__,Runnable,self,Character,LangChain,Pipe,other,value,ror
From: https://www.cnblogs.com/lightsong/p/18205539

相关文章

  • RAG Project with Ollama and LangChain via Gradio Interface
    RAGProjectwithOllamaandLangChainviaGradioInterfacehttps://github.com/fanqingsong/rag-ollama-langchainThisrepositoryhoststheimplementationofaRetrieval-AugmentedGeneration(RAG)projectleveragingthecapabilitiesofOllamatorunopen-so......
  • 在langchain中的rag学习使用之旅-从半知半解到实现效果
    rag我简单理解来看就是我先有一段文本,先把它转成向量,保存到向量数据库中,下次我调用llm时将向量数据库中查询的结果给llm作参考并回答。对rag了解不多,所以开启学习之旅,学完了要应用到实际的需求中,因为最近手里有一个订单就是需要用到这个技术,但是又一知半解。现在新知识太多了,学......
  • Python - pyenv, virtualenv, pipenv
    Pyenv可托管多个不同的Python版本。Installpyenv:gitclonehttps://github.com/pyenv/pyenv.git~/.pyenvAdd~/.pyenv/bintoPATH:if[[$(echo$PATH|grep'pyenv'|wc-l)-eq0]];thenPATH=$PATH:~/.pyenv/binfi 查看当前系统上已经安装和正在使用的......
  • LangChain 进阶历史对话管理
    自动历史管理前面的示例将消息显式地传递给链。这是一种完全可接受的方法,但确实需要外部管理新消息。LangChain还包括一个名为RunnableWithMessageHistory的包裹器,能够自动处理这个过程。为了展示其工作原理,我们稍微修改上面的提示,增加一个最终输入变量,该变量在聊天历史记录之后......
  • IceRPC之调用管道Invocation pipeline与传出请求Outgoing request->快乐的RPC
    作者引言.Net8.0下的新RPC很高兴啊,我们来到了IceRPC之调用管道Invocationpipeline与传出请求Outgoingrequest->快乐的RPC,基础引导,让自已不在迷茫,快乐的畅游世界。调用管道Invocationpipeline了解如何发送请求requests和接收响应responses。定义发送请求并接收......
  • OSS_PIPE:Rust编写的大规模文件迁移工具| 京东云技术团队
    文盘rust好久没有更新了。这段时间笔者用rust写了个小东西,跟各位分享一下背景随着业务的发展,文件数量和文件大小会急剧增加,文件迁移的数量和难度不断攀升。oss_pipe是rust编写的文件迁移工具,旨在支撑大规模的文件迁移场景。编写oss_pipe的初衷•同类产品面临的问题•rust......
  • 困扰了一天的flask结合智谱ai langchain流式输出json问题终于解决了
    本次对接的大模型是智谱,首先想到去智谱开放平台找找有没有和langchain结合的文档: 结果还真有,就省去了谷歌的时间,但是智谱的文档只提供了非流式的示例代码,想着先拷过来跑一下再说,结果就是非流式是正常输出的,流式就遇到问题了,不管我咋配置,好像只能在控制台输出流失内容,遂去谷歌......
  • 云效 Pipeline as Code 来了!这些场景,用好它效率翻倍!
    从可视化编排到支持YAML编排云效流水线Flow是开箱即用的企业级持续集成和持续交付工具,支持丰富的代码源、构建、自动化测试工具、多种部署类型和部署方式,与阿里云深度集成,还提供多种企业级特性,助力企业高效完成从开发到上线CICD过程。在业界,流水线产品通常有2种使用方式......
  • 云效 Pipeline as Code 来了!这些场景,用好它效率翻倍!
    从可视化编排到支持YAML编排云效流水线Flow是开箱即用的企业级持续集成和持续交付工具,支持丰富的代码源、构建、自动化测试工具、多种部署类型和部署方式,与阿里云深度集成,还提供多种企业级特性,助力企业高效完成从开发到上线CICD过程。在业界,流水线产品通常有2种使用方式......
  • langchain教程
    参考网址:https://python.langchain.com/docs/get_started/introduction/https://python.langchain.com/docs/modules/1、简介LangChain是一个用于开发由大型语言模型(LLM)驱动的应用程序的框架。LangChain简化了LLM申请生命周期的每个阶段:开发:使用LangChain的LCLE和......