langchain-core 核心对象
本周开始学习 langchain,大约经历了如下的过程:
- 简单阅读了 langchain 文档,学习了 LCEL,LCEL 的基本接口是 Runnable
- 读到 astream_log 方法时候,感觉模棱两可,看 langchain-core 的代码
- 看到 RunnableSequence invoke 方法时,里面涉及的对象逐渐变多,开始有点迷失在代码细节,无法掌握整体的调用流程
- 使用 pycharm Diagrams 查看了不通模块核心类的继承关系
下面是 langchain-core 核心结构记录。查看类继承关系时,用过 pyreverse、pyan3 都不太好用,pyreverse 无法解析泛型,pyan3 一直报错,这里做一下记录。
1. langchain 代码结构
langchain-core 包含如下几个模块。
|
|
1.1 runnables
Runnable 定义在 runnables 模块,是 langchain 核心的抽象类。
可以看到:
- Runnable 是最基础的抽象类,但是大多数对象都是继承自 RunnableSerializable
- 除此之外还有一个 RunnableConfig 类。虽然类图中没有显示,但是这个对象也很重要,RunnableConfig 用于在整个 LCEL 中传递配置和参数。
具体每个类是干什么的,我们先放一放,后面再详细介绍。
1.2 callbacks
callbacks 类图如下,类非常多,看起来也比较混乱。
简单浏览一下代码,callbacks 的实现用有许多 Mixin 混合类,这些类的目的是提供一些方法的默认实现。把这些类删除之后,得到如下的类图:
可以看到 callbacks 提供了三个核心的基础抽象:
- BaseCallbackHandler
- BaseCallbackManager
- BaseRunManager
1.3 tracer
tracer 模块中的类也很多。但是可以看到:
- 所有的 tracer 类都继承自 callbacks 的 BaseCallbackHandler
- _TracerCore 和 BaseModelV1 应该是为监控所做的基本抽象,这个对我们理解真个 LCEL 来说暂时不重要,我们也可以忽略。
1.4 messages
messages 模块提供了一个 BaseMessage 的抽象。
1.5 prompts
prompts 模块提供的抽象继承自 runnables.RunnableSerializable
1.6 vectorstores
vectorstores 模块提供的BaseRetriever 抽象也继承自 runnables.RunnableSerializable。
1.7 总结
到这里虽然我们还不知道上面这些类是做什么的,但是我们已经可以理解 langchain 文档所说的 “Runnable 协议已为大多数组件实现”。而且我们也知道在 langchain-core 的抽象中,有以下核心的对象:
- Runnable
- RunnableConfig
- BaseCallbackHandler
- BaseCallbackManager
- BaseRunManager
2. LCEL 的简化实现
现在我们可以借助大模型的能力,来实现一个简单的 LCEL 应用,来帮助我们理解 LCEL 的实现原理。
|
|
下面是 ChatGpt 给我的回答:
2.1🧩 简化版 LCEL 核心组件
Runnable
: 可运行对象的抽象
这是 LCEL 的核心接口,定义了调用模型的行为:
|
|
RunnableConfig
: 控制运行的配置对象
它在 LCEL 中作为“调用上下文”,控制 callback、tags、metadata 等。
|
|
BaseCallbackHandler
: 回调钩子定义
用于在每个调用过程中插入自定义逻辑(如日志、链路追踪等):
|
|
BaseCallbackManager
: 管理多个 CallbackHandler
用于聚合和调用所有的回调处理器:
|
|
BaseRunManager
: 一个生命周期管理器
它在 Runnable.invoke()
执行中生成、跟踪并管理回调生命周期:
|
|
2.2 ✅ 示例:实现一个简单的 EchoRunnable
|
|
如果你想我继续实现 RunnableSequence
、RunnableLambda
等组合式结构(更接近 LCEL),也可以告诉我,我来帮你扩展。是否继续?
2.3 追问
现在的 AI 真是太强大了,回答结束还给了我们进一步的提示。我们可以继续追问。因为我之前已经简单看过 Runnable 的源码,知道 Runnable 有一个 astream_events 方法,用于异步流式返回链中发生的事件。我想了解 LCEL 中事件的触发流程,所以这一次我们在提问中加上有关 event 的内容。
|
|
✅ 1. 定义 LCEL Event 对象
LCEL 中的事件是通过 callback handler 机制发出的。我们模拟一个简单的事件系统:
|
|
✅ 2. 改造 BaseCallbackHandler
支持事件钩子
|
|
✅ 3. 改造 BaseRunManager
触发事件机制
|
|
✅ 4. 支持组合结构:RunnableSequence
允许组合多个 Runnable
顺序执行。
|
|
✅ 5. 支持动态表达式:RunnableLambda
用于包装一个任意函数:
|
|
✅ 6. 示例 CallbackHandler:监听事件
|
|
✅ 7. 组合调用示例(完整链)
|
|
2.4 补充提问
这一次提问回答的其实不是很好,所以我追加了一次提问:
|
|
🔍 正确理解 LCEL 中事件触发机制
你提到的非常准确:
LCEL 中的事件并不都是在 BaseRunManager
中触发的。
在 LangChain 的设计中:
- 事件的触发主要依赖于每个
Runnable
的实现者,比如RunnableMap
,RunnableSequence
,RunnableLambda
,RunnablePassthrough
等。 BaseRunManager
的职责是提供通用的 callback 调用,比如on_start
,on_end
,on_error
,有时也提供on_event
的快捷方法。- 但真正触发某个业务相关的 event,比如
on_chat_model_start
,on_tool_start
,on_chain_end
,通常是在每个 Runnable 的实现内部显式触发的。
你可以把 BaseRunManager
理解为提供了“事件转发的管道”,但 具体“什么时候发出哪个事件”是在 Runnable 逻辑内部决定的。
🧩 CustomRunnable
|
|
✅ 简单的链组合类:RunnableSequence
|
|
✅ 完整运行示例(不再使用 RunnableLambda)
|
|
2.5 总结
最新的示例没有用到 BaseRunManager,所以 BaseRunManager 的作用需要进一步查看源码。但是这个不妨碍我们对 LCEL 有个感性的理解,现在我们来看 Runnable 的源码。
3. runnable 源码框架
3.1 Runnable 类
Runnable 类的核心方法就是 invoke,这个是一个抽象方法,基于这个方法,Runnable 提供异步、批次调用的 invoke 的其他方法。
|
|
我列举了 Runnable 的一些方法:
__or__
:|
管道符语法的实现,内部调用 RunnableSequence- config_specs: 与程序自定义配置有关
- astream_log: 异步流式日志,需要重点理解
- astream_events: 异步流式事件,需要重点理解
- with_ 的方法,都是一些便携方法,比如 with_config、with_retry 等
接下来我们来看 RunnableSequence 的 invoke 方法,这是我们理解 LCEL 的关键方法。
3.2 RunnableSequence
|
|
下面是一个 LCEL 的简单使用示例:
|
|
可以看到基本上与 ChatGpt 给我们的 demo 实现一致。下一节我们将详细介绍 RunnableSequence invoke 方法的实现。