Skip to content

3.3 Protocol 与 ABC:LangChain 的设计模式

引言

小白理解 - Protocol vs ABC,有什么区别?

两种方式都是定义"规范",但理念不同:

方式类比特点
ABC(抽象基类)驾照:必须考试持证上岗必须写 class MyClass(ABC) 显式继承
Protocol技能检测:会开车就行,不管有没有驾照不需要继承,只要有对应方法就行

Protocol 的核心理念 = 鸭子类型(Duck Typing)

"如果它走路像鸭子、叫声像鸭子,那它就是鸭子"

python
# 不管你是什么类,只要有 invoke() 方法,你就是 Runnable
class 鸭子:
    def(self): return "嘎嘎"

class 玩具鸭:
    def(self): return "嘎嘎"  # 虽然不是真鸭子,但会叫就行

Protocol:结构化子类型

小白理解 - 看懂 Protocol 代码

python
from typing import Protocol

class Runnable(Protocol):    # ← 定义一个"规范"
    def invoke(self, input): # ← 规定必须有 invoke 方法
        ...

class MyTool:                # ← 注意:没有继承任何东西!
    def invoke(self, input): # ← 但它有 invoke 方法
        return "结果"

# MyTool 自动"符合" Runnable 规范,因为它有 invoke 方法

LangChain 为什么用 Protocol?

  • 你的自定义类不需要继承 LangChain 的类
  • 只要实现了规定的方法,就能和 LangChain 配合使用
  • 更灵活、更解耦
python
from typing import Protocol

class Runnable(Protocol):
    """可运行接口(LangChain 风格)"""

    def invoke(self, input: dict) -> dict:
        """调用方法"""
        ...

class MyTool:
    """实现 Runnable 接口"""

    def invoke(self, input: dict) -> dict:
        return {"result": "完成"}

def run_component(component: Runnable) -> dict:
    """接受任何实现 Runnable 的对象"""
    return component.invoke({"task": "execute"})

# Protocol 不需要显式继承
tool = MyTool()
print(run_component(tool))

关键理解

  • MyTool 没有写 class MyTool(Runnable)
  • 但它有 invoke() 方法,所以符合 Runnable 规范
  • run_component 函数接受任何"像 Runnable"的对象

ABC:抽象基类

小白理解 - ABC 的设计模式

ABC 常用于"模板方法模式":

父类定义流程框架:
┌─────────────────────────┐
│ run() 方法              │  ← 父类写好的,不用改
│   1. 打印日志           │
│   2. 调用 _run()        │  ← 子类必须实现
│   3. 返回结果           │
└─────────────────────────┘

子类只需要实现 _run(),其他流程父类已经搞定了

类比:麦当劳的汉堡制作流程

  • 总部规定:先放面包 → 放肉饼 → 放蔬菜 → 盖面包(框架不变)
  • 各门店只需要决定:放什么肉饼(牛肉?鸡肉?)
python
from abc import ABC, abstractmethod

class BaseTool(ABC):
    """工具抽象基类"""

    @property
    @abstractmethod
    def name(self) -> str:
        """工具名称"""
        pass

    @abstractmethod
    def _run(self, query: str) -> str:
        """执行逻辑"""
        pass

    def run(self, query: str) -> str:
        """公共运行方法"""
        print(f"[{self.name}] 执行")
        return self._run(query)

class SearchTool(BaseTool):
    @property
    def name(self) -> str:
        return "search"

    def _run(self, query: str) -> str:
        return f"搜索结果: {query}"

tool = SearchTool()
print(tool.run("Python"))

代码解读

代码含义
@property + @abstractmethod子类必须实现这个属性
def _run(self, ...)下划线开头 = "内部方法",外部不应该直接调用
def run(self, ...)公开方法,内部调用 _run()

这就是 LangChain 的 Tool 设计

  • 你只需要实现 name_run()
  • run() 方法已经帮你写好了(包含日志、错误处理等)

Protocol vs ABC:如何选择?

场景推荐方式理由
第三方库集成Protocol不需要改动已有代码
团队内部规范ABC强制子类实现,编译时检查
LangChain 风格两者结合Protocol 做接口,ABC 做基类

一句话记忆

  • Protocol = "看起来像就行"(鸭子类型)
  • ABC = "必须继承才行"(正式身份)

本节小结

概念一句话解释使用场景
Protocol定义"长什么样"的规范灵活的接口定义
ABC定义"必须继承"的模板强制规范的基类
鸭子类型有方法就算数,不管类型Protocol 的核心理念
模板方法父类定框架,子类填细节ABC 常见设计模式

下一节:3.4 dataclass

基于 MIT 许可证发布。内容版权归作者所有。