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 代码
pythonfrom 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