在思考SOLID原则等的情况下,浏览LangChain的源代码

在这里,我们试图通过在LangChain的实现中找出这些要素来解释SOLID原则和契约式编程。

SOLID原則可以被描述为一个面向对象编程的指导原则集,它由五个原则组成,分别是单一责任原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。

SOLID原则是面向对象编程(OOP)设计指南之一。如前所述,它是关于设计类和函数的具体方法,旨在实现高内聚、低耦合的模块设计。

具体而言,SOLID原则包括五个要素。

    • S – Single Responsibility Principle (単一責任の原則): 変更するための理由が、一つのクラスに対して一つ以上あってはならない

 

    • O – Open/Closed Principle (開放/閉鎖の原則): ソフトウェアの実体(クラス、モジュール、関数など)は、拡張に対して開かれているべきであり、修正に対して閉じていなければならない

 

    • L – Liskov Substitution Principle (リスコフの置換原則): ある基底クラスへのポインタないし参照を扱っている関数群は、その派生クラスのオブジェクトの詳細を知らなくても扱えるようにしなければならない

 

    • I – Interface Segregation Principle (インターフェース分離の原則): 汎用なインターフェースが一つあるよりも、各クライアントに特化したインターフェースがたくさんあった方がよい

 

    D – Dependency Inversion Principle (依存性逆転の原則): 上位モジュールはいかなるものも下位モジュールから持ち込んではならない。双方とも具象ではなく、抽象(インターフェースなど)に依存するべきである

现在除了这些原则之外,还存在着多种设计指导,如设计模式和多态性等。这些指导以不同的方式重复表达着相同的主题。

SOLID原则是将已知的概念巧妙地总结起来,其易于理解并不会减弱其重要性。就像中学生也能理解平方的定理一样,但其重要性不会改变。因此,作为编程设计的基本而重要的准则,理解和应用SOLID原则是值得的。

为什么选择 LangChain

朗装是一个围绕目前流行的LLM的框架。可以轻松创建基于LLM的应用程序。

在这里,我想要专注于LangChain的源代码。因为我认为有以下几个值得关注的原因:

    • 2022年の10月にできた、新しいものな上で、

 

    • Python で作られてて、

 

    • 多くの committer がいる。 1900 人くらい。

 

    • 多くの人に使われている。

 

    更新頻度が速い。

尽管LangChain备受争议,但仅凭上述原因,就应该关注其内部设计。

事实上,可以从LangChain的代码中找到SOLID原则。 在Python的实现示例中看到这一点非常令人高兴。

因此,我打算在接下来的内容中引用LangChain的代码,以解释SOLID原则等内容。

使用LangChain的实现示例

在查看LangChain的代码之前,先展示一个使用LangChain的样例。通过这个样例,希望能够理解它的行为。

以下是所謂的RAG的實現。

pip install langchain chromadb tiktoken openai
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import TextLoader
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores.chroma import Chroma

loader = TextLoader("日本国憲法.txt")
documents = loader.load()
text_splitter = CharacterTextSplitter()
texts = text_splitter.split_documents(documents)
embeddings = OpenAIEmbeddings()
docsearch = Chroma.from_documents(texts, embeddings)
retriever = docsearch.as_retriever()
llm = ChatOpenAI(model_name="gpt-4", api_key="YOUR_API_KEY")
qa = RetrievalQA.from_chain_type(llm=llm, retriever=retriever)

以下是日本国宪法.txt。

 

问问题。

answer = qa.run("テキストにはなんと書いてある?")
print(answer)

发挥潜力

这段文字是关于日本宪法的内容,涉及皇室、放弃战争以及国民权利和义务的论述。
对于皇室来说,涉及到摄政的安排、天皇的任命行为、国家事务的执行… (省略)

DIP – 依赖倒置原则

解释

我会直接从SOLID原则的D来解释。因为在SOLID原则中,我认为这是最重要的。如果没有这个,解释其他原则也会变得困难。

DIP 是指引用维基百科的定义。

上级模块不得从下级模块引入任何内容。双方都应该依赖于抽象(如接口),而不是具体实现。

这样的东西。

让我们用代码来展示吧。

首先,這是一個違反了DIP的案例。

class MySQLDatabase:
    def connect(self):
        return "MySQLデータベースに接続"

    def fetch_data(self):
        return "データを取得"

class ReportGenerator:
    def __init__(self):
        self.database = MySQLDatabase()

    def create_report(self):
        self.database.connect()
        data = self.database.fetch_data()
        return f"レポート: {data}"

# 使用例
report_generator = ReportGenerator()
print(report_generator.create_report())

如果绘制成类图,它会是这样的。

这是一个常见的实现方式。在 ReportGenerator 中,MySQLDatabase 被实例化。

这是有问题的。

首先,在这个阶段,实施单元测试非常困难。我希望 MySQLDatabase 的实例能够正常运行。每次都要启动 Docker 容器,准备适当的数据来进行”单元测试”?这太愚蠢了。

还有一个常见的习惯。与上述的本质上没有太大区别。

class C:
    def operation_c(self):
        return "Cの操作"

class B:
    def __init__(self):
        self.c = C()

    def operation_b(self):
        return f"Bの操作と {self.c.operation_c()}"

class A:
    def __init__(self):
        self.b = B()

    def operation_a(self):
        return f"Aの操作と {self.b.operation_b()}"

# 使用例
a = A()
print(a.operation_a())

类图

这两个例子中,类之间存在紧密的耦合。在这个例子中,我们想要解决类 C 的问题,但却不得不考虑到依赖于它的类 B。而且,随之而来的是,如果修改了 B 的代码,就必须同时处理 A 的相关问题。

如果紧密结合,会存在以下问题。

    • テストが困難

拡張が困難。 MySQL を Postgres に変えようかなと思ったら、それに依存している人全員が「大丈夫かな」「影響ないかな」と思わねばならない。

用 DIP 进行重写后,它变成了这样。

from abc import ABC, abstractmethod

class IDatabase(ABC):
    @abstractmethod
    def connect(self):
        pass

    @abstractmethod
    def fetch_data(self):
        pass

class MySQLDatabase(IDatabase):
    def connect(self):
        return "MySQLデータベースに接続"

    def fetch_data(self):
        return "データを取得"

class ReportGenerator:
    def __init__(self, database: IDatabase):
        self.database = database

    def create_report(self):
        self.database.connect()
        data = self.database.fetch_data()
        return f"レポート: {data}"

# 使用例
# MySQLデータベースを使用
mysql_db = MySQLDatabase()
report_generator = ReportGenerator(mysql_db)
print(report_generator.create_report())

ReportGenerator依赖于作为抽象类实现的IDatabase。重要的是它仅依赖于IDatabase。而且MySQLDatabase是IDatabase的实现。也就是说,ReportGenerator不依赖于MySQLDatabase。换句话说,它是松散耦合的。

这让你觉得开心的是什么?

举个例子,考试问题得到解决。

只需按照下面的方式实施即可。

import pytest
from unittest.mock import MagicMock

def test_create_report():
    # IDatabaseのモックを作成
    mock_database = MagicMock(spec=IDatabase)
    mock_database.fetch_data.return_value = "テストデータ"

    # ReportGeneratorのインスタンスを作成
    report_generator = ReportGenerator(mock_database)

    # レポートを生成
    report = report_generator.create_report()

    # メソッド呼び出しを検証
    mock_database.connect.assert_called_once()
    mock_database.fetch_data.assert_called_once()

    # 生成されたレポートを検証
    assert report == "レポート: テストデータ"

只需要准备一个行为类似的模拟 Mock 用于测试。

同时,扩展也变得更加简单。

class PostgreSQLDatabase(IDatabase):
    def connect(self):
        return "PostgreSQLデータベースに接続"

    def fetch_data(self):
        return "データを取得"

# PostgreSQLデータベースに切り替える場合
postgresql_db = PostgreSQLDatabase()
report_generator = ReportGenerator(postgresql_db)
print(report_generator.create_report())

我们还需要按照 DIP 原则对另一个例子进行重构。

from abc import ABC, abstractmethod

class IC(ABC):
    @abstractmethod
    def operation_c(self):
        pass

class IB(ABC):
    @abstractmethod
    def operation_b(self):
        pass

class C(IC):
    def operation_c(self):
        return "Cの操作"

class B(IB):
    def __init__(self, c: IC):
        self.c = c

    def operation_b(self):
        return f"Bの操作と {self.c.operation_c()}"

class A:
    def __init__(self, b: IB):
        self.b = b

    def operation_a(self):
        return f"Aの操作と {self.b.operation_b()}"

# 使用例
c = C()
b = B(c)
a = A(b)
print(a.operation_a())

课程图将如下所示。

顺便说一下,这也可以称为界面设计。

有什么缺点?

由于实现依赖于抽象,因此代码变得难以直观阅读。

尽管如此,我们应该将其视为一种权衡吗?这并不意味着我们应该容忍难以阅读而无法进行单元测试的代码。

DI 和 DI 容器

在这里引入另一个术语。 DI – 依赖注入。

如果用上述的代码表示,DI就是指。

mysql_db = MySQLDatabase()
report_generator = ReportGenerator(mysql_db)

c = C()
b = B(c)
a = A(b)

这部分。

依赖于上述抽象类的类定义,如果保持原样自然不能运行。最终,必须在某处依赖具体类,才能使其运行。

实际进行该依存的操作,就是所谓的依赖注入。

在这里,我们还需要谈论另一个话题,那就是 DI 容器的存在。

希望你能够想象一下。一个产品类别有几十个。每个类别都依赖复杂的抽象类来运作。那么,对这几十个类别,必须进行依赖注入。这意味着依赖注入的管理会带来痛苦。

看起来很棘手。有一个系统可以解决这个问题,那就是被称为 DI 容器的机制。

如果使用富有功能的 DI 容器,即使不编写任何与 DI 相关的代码,DI 也能够实现。例如,Java 的 Spring Framework。DI 容器非常聪明,只要类型设置正确,它会自动搜索并实例化合适的具体类。

如果将 DI 容器安装进去,将解决最初提到的与 DI 管理相关的痛点。

换句话说,如果要实行依赖注入,最好有一个依赖注入容器。

然而,据我观察,在Python中,尚未有成熟的依赖注入容器库。

虽然有很多Python的DI工具,但其中最受欢迎且被FastAPI等项目使用的示例是python-dependency-injector。在FastAPI的文档中,也有用它实现的示例。

然而,最近没有进行维护。还存在以下不安的问题。

https://github.com/ets-labs/python-dependency-injector/issues/688
メンテナーがいないぽいのが、大変ぽい。

所以目前在Python中,我手动管理DI。

实际上,可以通过将DI的管理集中在一个类中来减少痛苦。除此之外,不进行DI。这样一来,责任范围就清晰了。我们可以在类中巧妙地实现让该类适当地进行DI,并将责任分开。虽然有许多复杂的事情,但只要在类中合理地分隔函数,或者在最初创建时小心处理,就不需要之后进行大幅度的更改。在这里,可以使用GoF的Builder模式或Factory模式。

请问在LangChain怎么样呢?

现在话题扯得有点远,但我们来看看 LangChain 的相关实现。

在LangChain中,DIP得到了全面贯彻。

我去看看RetrievalQA的代码。

 

class RetrievalQA(BaseRetrievalQA):
    """Chain for question-answering against an index.

    Example:
        .. code-block:: python

            from langchain.llms import OpenAI
            from langchain.chains import RetrievalQA
            from langchain.vectorstores import FAISS
            from langchain_core.vectorstores import VectorStoreRetriever
            retriever = VectorStoreRetriever(vectorstore=FAISS(...))
            retrievalQA = RetrievalQA.from_llm(llm=OpenAI(), retriever=retriever)

    """

    retriever: BaseRetriever = Field(exclude=True)

顺便提一下,可能变量定义的方式看起来有点奇怪,但这是因为我在使用pydantic。稍后会详细讨论一下pydantic。

这个 BaseRetriever 是以下抽象类的样式。

 

from abc import ABC, abstractmethod
...()...
class BaseRetriever(RunnableSerializable[str, List[Document]], ABC):

Retriever 的测试可以通过以下方式进行,我们为每个测试用例创建了一个名为 FakeRetriever 的测试实例,并进行测试。

 

@pytest.fixture
def fake_retriever_v1() -> BaseRetriever:
    with pytest.warns(
        DeprecationWarning,
        match="Retrievers must implement abstract "
        "`_get_relevant_documents` method instead of `get_relevant_documents`",
    ):

        class FakeRetrieverV1(BaseRetriever):
            def get_relevant_documents(  # type: ignore[override]
                self,
                query: str,
            ) -> List[Document]:
                assert isinstance(self, FakeRetrieverV1)
                return [
                    Document(page_content=query, metadata={"uuid": "1234"}),
                ]

            async def aget_relevant_documents(  # type: ignore[override]
                self,
                query: str,
            ) -> List[Document]:
                assert isinstance(self, FakeRetrieverV1)
                return [
                    Document(
                        page_content=f"Async query {query}", metadata={"uuid": "1234"}
                    ),
                ]

        return FakeRetrieverV1()  # type: ignore[abstract]

LSP – 里氏替换原则

解释

尽管无法从名字中完全理解其意义的原则,但其实并不重要。 维基百科对此作了如下解释。

处理指向或引用基类的函数组必须能够处理派生类对象的详细信息,即使不了解派生类的细节也要进行处理。

在另一个解释中,如下所述:

如果S是T的子类型,那么在不破坏程序的情况下,T类型的对象必须能够替换为S类型的对象。

如果以我的话来表达,那就是指:“如果要继承父类并实现子类,就必须忠实于父类定义的行为。”

让我们来看一下实现。以下是违反LSP的实现示例。

from abc import ABC, abstractmethod


class MetaA(ABC):
    @abstractmethod
    def run(self, input_data: list):
        """Run the model"""


class A(MetaA):
    def run(self, input_data: dict):
        """Run the model"""
        return input_data

    def custome_method(self):
        return "custome_method"


a = A()

上述的内容是指run方法的类型规定错误。这违反了LSP原则。

如果违反LSP会导致什么问题?

如果回想一下上面提到的 DIP,就可以明白了。如果在具体类中没有按照抽象类中定义的规则进行实现,那么就没有 DIP 的意义了。

将LSP(Liskov Substitution Principle,里氏替换原则)应用于重构,意味着使父类定义的方法参数的类型保持一致。在上述例子中,就是将字典(dict)转换为列表(list)。

那么,有没有一种更简便的方法来遵循LSP?

可以使用某种语法检查工具,尤其是在Python中,mypy是一个选择。

如果使用mypy,它会告诉你在哪里违反了LSP,并明确地表示 “这违反了Liskov替换原则”。

$ mypy langchain_handson/a.py 
langchain_handson/a.py:61: error: Argument 1 of "run" is incompatible with supertype "MetaA"; supertype defines the argument type as "list[Any]"  [override]
langchain_handson/a.py:61: note: This violates the Liskov substitution principle
langchain_handson/a.py:61: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
Found 1 error in 1 file (checked 1 source file)

如果你在使用VSCode,只要安装了mypy的插件,就不需要在控制台上输入命令。

在LangChain中有何推荐的做法?

我在LangChain中也使用了mypy。

 

ISP – 接口隔离原则 (接口隔离原則)

解释

在维基百科上如下解释:

有多个针对每个客户端专门定制的接口比只有一个通用接口更好。

也许我能大致理解关于这个问题说的是什么。

让我们来看一个代码示例。以下是违反ISP的例子。

class WorkerInterface:
    def work(self):
        pass

    def eat(self):
        pass

class Human(WorkerInterface):
    def work(self):
        print("人間は働く")

    def eat(self):
        print("人間は食事をする")

class Robot(WorkerInterface):
    def work(self):
        print("ロボットは働く")

    # Robot は食事をしないが、インターフェイスの一部として定義されている
    def eat(self):
        pass

违反ISP政策会引起什么问题?

    • 不必要な依存性: クラスが使用しないメソッドに依存することになり、システム全体の結合度が不必要に高くなる。これは保守性や拡張性に悪影響を与える。

 

    理解しにくい設計: 大きくて複雑なインターフェイスは、理解や使用が難しくなることがある。特に、関係のないメソッドが混在している場合、その目的や機能が明確でなくなる。

DB的表设计可能有类似的氛围。存在一些只适用于特定条件的列,且一直保持为空值。这种情况下,我们会想要进行规范化。本次也是如此,让我们进行规范化吧,就是这个感觉。

请将其重构以符合ISP(界面隔离原则)的要求。

class Workable:
    def work(self):
        pass

class Eatable:
    def eat(self):
        pass

class Human(Workable, Eatable):
    def work(self):
        print("人間は働く")

    def eat(self):
        print("人間は食事をする")

class Robot(Workable):
    def work(self):
        print("ロボットは働く")

通过界面级别进行分离,并继承它。

类图

请问在 LangChain 是否可以?

有一个与之完全相同的例子。 巧妙地利用了 Mix-in。

 

有一个叫做 Docstore 的抽象类,它有两个实现类 InMemoryDocstore 和 Wikipedia。在这里发现 InMemoryDocstore 需要一个名为 add 的函数。那么,在 Docstore 的抽象类中定义 add 函数吗?但这样的话,Wikipedia 类就必须实现一个无用的 add 函数(这违反了LSP原则)。
为了解决这个问题,实现了一个叫做 AddableMixin 的 Mix-in 类。Mix-in 类是指不用于实例化的类。InMemoryDocstore 继承自 Docstore 和 AddableMixin。而 Wikipedia 只使用 Docstore。

 

class Docstore(ABC):
    """Interface to access to place that stores documents."""

    @abstractmethod
    def search(self, search: str) -> Union[str, Document]:
        """Search for document.

        If page exists, return the page summary, and a Document object.
        If page does not exist, return similar entries.
        """

    def delete(self, ids: List) -> None:
        """Deleting IDs from in memory dictionary."""
        raise NotImplementedError


class AddableMixin(ABC):
    """Mixin class that supports adding texts."""

    @abstractmethod
    def add(self, texts: Dict[str, Document]) -> None:
        """Add more documents."""

 

class InMemoryDocstore(Docstore, AddableMixin):

 

class Wikipedia(Docstore):

类图

OCP – 开放/闭合原则

说明

首先,根据维基百科的定义:

软件的实体(类、模块、函数等)应该对扩展开放,对修改封闭。

这也是以代码为例子。

下面是违反OCP原则的例子。

class DiscountCalculator:
    def calculate(self, amount, customer_type):
        if customer_type == "standard":
            return amount * 0.9  # 10% discount
        elif customer_type == "premium":
            return amount * 0.8  # 20% discount
        # 新しい顧客タイプを追加するたびにこのメソッドを変更する必要がある

首先,解释一下这个问题有哪些地方违反了OCP原则。

OCP 表明的是,在新增新功能扩展时,不需要修改现有代码,以便达到这个目的。在阅读此代码时考虑到这一点,每次添加新的客户类型,都需要修改这个类。因此违反了OCP。

给出一个符合OCP(开闭原则)的重构示例。

from abc import ABC, abstractmethod

class Customer(ABC):
    @abstractmethod
    def apply_discount(self, amount):
        pass

class StandardCustomer(Customer):
    def apply_discount(self, amount):
        return amount * 0.9  # 10% discount

class PremiumCustomer(Customer):
    def apply_discount(self, amount):
        return amount * 0.8  # 20% discount

class DiscountCalculator:
    def calculate(self, amount, customer: Customer):
        return customer.apply_discount(amount)

# DI
customer = PremiumCustomer()
calculator = DiscountCalculator(customer)

这个结构与DIP中所提到的一样。DiscountCalculator只依赖于抽象类Customer,并将具体行为委托给具体类。

这一点的好处是,不需要修改现有的 DiscountCalculator 类。也就是说,不需要重新进行与此相关的测试之类的事情。

这个东西叫做所谓的”多态”。

确实,如果在这里定义一个名为CheepCustomer的新客户类型,特别是不需要修改现有的代码。因此,扩展很容易。

但是,关于这个OCP,有一些理由不太合适。

需要在某个地方编写逻辑来判断PremiumCustomer或StandardCustomer。如下所示。

if customer_type == "standard":
    customer = StandardCustomer()
elif customer_type == "premium":
    customer = PremiumCustomer()
...
calculator = DiscountCalculator(customer)

换句话说,DI 逻辑会嵌入到进行 DI 的部分。而进行 DI的部分是现有代码,所以如果要添加新的CheepCustomer, 就必须修改此处正在进行 DI 的部分。这又是违反了OCP原则吗?

此外,在预见中,还有一种称为“扩展”的事物,并且有这种前提存在。因此,它并不太适合。

所以,我对这里的处理只是以深入研究 DIP 为主要目标。

可以说LangChain怎么样呢

对于VectorStore,它经常在这里出现。

有一个名为Vector Store的数据库,用于存储将字符串转换为向量的内容。一些著名的例子包括FAISS、ChromaDB和Pinecone。最近,Elasticsearch和Redis也已经开始支持这个数据库。

目前 LangChain 可支持大约60至70种语言。我们支持的语言很多,而且对新语言的支持速度也非常快,这真是非常出色。请点击链接查看更多详细信息:https://python.langchain.com/docs/integrations/vectorstores/

看起来这正是充分享受了OCP的好处。如果看看类似的宣传,就能更清楚地理解。

 

这是 Cassanddra 的处理方式,实质上只进行了添加 cassandra.py 文件的额外操作。

S – 单一责任原则 yī zé zé)

解释

SOLID 的终点。

维基百科的定义:

每个班级不能有一个以上的理由来进行更改

在SOLID原则中,他所说的是最容易理解的。

这实际上只是一个说“要高度凝聚”的信息。

我們遇到了一個難題,就是如何建立高聚合度的模塊。然而,單一職責原則 (SRP) 給出了一個解決方案的指南。

“只需做这一件事”的信息不断在各个地方出现。

例如,就算是UNIX哲学,也会以以下方式被述说。

写一个能够执行一个任务并且成功完成的程序。
写一个能够协调运行的程序。
写一个处理标准输入输出(文本流)的程序。标准输入输出是一种通用接口。

 

在「可读代码」中,也以类似「一次只做一件事」的方式表达了类似的概念。

 

如果尝试创建一个仅承担一个职责的班级,很容易想象班级规模将会变得”小”。

换言之,如果一个班级给人一种“庞大”的感觉,那么或许是因为这个班级可能承担了过多的角色。

有时候我们用“石狮”来形容一个巨大的模块。

再說一次,並不一定能夠從一開始就完美地定義班級的責任。

在这种情况下,初期可以采用单体结构也可以。也经常看到术语”Monolithic First”。

 

如果分割会使得类之间的耦合度密集,那么宁可让一个类变得稍大一些,以便改变仅限于一个类,也可以接受这种方式的想法。

然后,稍后进行重构就可以了。

请问您对LangChain的看法如何?

朗足链并不完全顺利,已进行一些破坏性的改变。

经过一年的时间,终于看到了一个相对稳定的班级结构。因此,将这种稳定的设计的班级分离到另一个地方,称为langchain-core,并在这个分离的地方编写了几乎不需要频繁更新的代码,同时以LangChain的稳定版本作为目标进行调整。

 

设计由契约确定 (DbC – Design by Contract)

解释

SOLID原则的讨论已经结束了。最后让我们谈谈与此无关的 DbC。

 

维基百科不够简明易懂,以下是其摘要:

通过明确指定各组件(如函数、方法、类等)应满足的前提条件、后条件和不变条件,来提高系统的准确性和可靠性的编程方法。

总的来说,对于使用组件的人和提供组件的人来说,大家都有各自需要遵守的承诺,所以用契约这个词来表达大家应该遵守这些承诺的意思。

用编码来解释。以下是违反设计按契约编程(DbC)的例子。

假设有一个名为 User 的类,其中包含表示年龄的属性 age。

from dataclasses import dataclass


@dataclass
class User:
    id: int
    name: str
    age: int


u = User(id=1, name="John Doe", age=-1)

假设创建这个User类的人心里想着“不希望age被赋予负数。”

因为age的值为负数,所以上面的u是不合法的。这违反了DbC的前置条件。

在DbC中提到的其中一个前提条件是,“使用类的人有责任正确地输入参数”。

现在,我想要遵守DbC。所以,作为User类的提供者,如果出现了不合法的输入,我希望能够对其进行验证并将这一违规行为通知给违规者。

需要逐一实现它吗?不需要。因为这里有pydantic。

from pydantic import BaseModel, PositiveInt, ValidationError


class User(BaseModel):
    id: int
    name: str
    age: PositiveInt


# 正しいデータを使用
try:
    user = User(id=123, name="John Doe", age=30)
    print(user)
except ValidationError as e:
    print(e)

# 不正なデータを使用(例: 年齢に文字列を渡す)
try:
    user = User(id=123, name="John Doe", age="thirty")
except ValidationError as e:
    print(e)

# 不正なデータを使用(例: 年齢に負の数を渡す)
try:
    user = User(id=1, name="John Doe", age=-1)
except ValidationError as e:
    print(e)
id=123 name='John Doe' age=30
1 validation error for User
age
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='thirty', input_type=str]
    For further information visit https://errors.pydantic.dev/2.5/v/int_parsing
1 validation error for User
age
  Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    For further information visit https://errors.pydantic.dev/2.5/v/greater_than

当查看Pydantic的HP时,可以看到它也被用于Transformer和FastAPI。与mypy一起,它为Python提供了实现基于类型的新鲜健壮性的方式。

请问,LangChain 怎么样呢?

所有的类都使用了 pydantic。确切地说,只要按照继承关系追溯,就一定会到达 BaseModel。

 

最后

总结一下

    • まず DIP が本質的。 DIP をやってから他をケアしていく。

 

    mypy, pydantic を使おう

这就是意思。我有点累,所以就在这里结束吧。

广告
将在 10 秒后关闭
bannerAds