Langchain框架的设计哲学

设计理念

  1. 组合优于继承:核心抽象 Runnable 定义了统一的调用协议(invoke/ainvoke/batch/stream),任何组件只要实现该接口即可无缝嵌入链条 init.py:1-19 。
  2. 分层架构:仓库按 Core(基础抽象)→ Implementation(langchain具体实现)→ Integration(第三方 partners/)分层,用户通常不需要关心 Core 层细节 AGENTS.md:30-33 。
  3. 轻量化 Corelangchain-core 有意保持依赖极简,不包含任何第三方集成,只定义聊天模型、LLM、向量库、检索器等接口和 Runnable 语法 init.py:1-9 。
  4. 模型互操作性 & 快速原型:README 明确指出优势包括"Model interoperability"(随时切换模型提供商)和"Rapid prototyping"(组件化架构支持快速试验) README.md:63-68 。
  5. 与 LangGraph/LangSmith 协同langchain 包本身基于 langgraph构建 agent,用于提供 durable execution、streaming、human-in-the-loop 等能力 README.md:19-25 ;langchain-core 内置对 langsmith的依赖以支持自动 tracing。

设计手法(LCEL / Runnable 体系)

  • LCEL(LangChain Expression Language):通过 | 管道运算符声明式地组合 Runnable,天然支持同步、异步、批处理和流式操作,有利于高并发和更好的用户体验 init.py:1-19 。
"""LangChain **Runnable** and the **LangChain Expression Language (LCEL)**.
 
The LangChain Expression Language (LCEL) offers a declarative method to build
production-grade programs that harness the power of LLMs.
 
Programs created using LCEL and LangChain `Runnable` objects inherently support
synchronous asynchronous, batch, and streaming operations.
 
Support for **async** allows servers hosting LCEL based programs to scale bette for
higher concurrent loads.
 
**Batch** operations allow for processing multiple inputs in parallel.
 
**Streaming** of intermediate outputs, as they're being generated, allows for creating
more responsive UX.
 
This module contains schema and implementation of LangChain `Runnable` object
primitives.
"""
  • Serializable 基类:所有核心组件继承 Serializable/RunnableSerializable,可以序列化/反序列化用于持久化和跨网络传输;Chain 类本身就是 RunnableSerializable 的子类 base.py:52-73 。

class Chain(RunnableSerializable[dict[str, Any], dict[str, Any]], ABC):
    """Abstract base class for creating structured sequences of calls to components.
 
    Chains should be used to encode a sequence of calls to components like
    models, document retrievers, other chains, etc., and provide a simple interface
    to this sequence.
 
    The Chain interface makes it easy to create apps that are:
        - Stateful: add Memory to any Chain to give it state,
        - Observable: pass Callbacks to a Chain to execute additional functionality,
            like logging, outside the main sequence of component calls,
        - Composable: the Chain API is flexible enough that it is easy to combine
            Chains with other components, including other Chains.
 
    The main methods exposed by chains are:
        - `__call__`: Chains are callable. The `__call__` method is the primary way to
            execute a Chain. This takes inputs as a dictionary and returns a
            dictionary output.
        - `run`: A convenience method that takes inputs as args/kwargs and returns the
            output as a string or object. This method can only be used for a subset of
            chains and cannot return as rich of an output as `__call__`.
    """
  • 安全设计(反序列化)load()/loads() 使用 allowlist 机制防止任意代码执行,明确将反序列化视为跨信任边界的操作,并给出详细的威胁模型说明 load.py:11-34 。
 
When deserializing, the class path from the JSON `'id'` field is checked against an
allowlist. If the class is not in the allowlist, deserialization raises a `ValueError`.
 
## Threat model
 
A serialized LangChain payload crosses a trust boundary because the manifest
may contain serialized objects and configuration that affect runtime behavior.
For example, a payload can configure a chat model with a custom `base_url`,
custom headers, a different model name, or other constructor arguments. These
are supported features, but they also mean the payload contents should be
treated as executable configuration rather than plain text.
 
Concretely, deserialization instantiates Python objects, so any constructor
(`__init__`) or validator on an allowed class can run during `load()`. A
crafted payload that is allowed to reach an unintended class or an intended
class with attacker-controlled kwargs  could cause network calls, file
operations, or environment-variable access while the object is being built.
 
!!! warning "Do not use with untrusted input"
 
    If the source is untrusted, avoid calling `load()` / `loads()` on it. If
    you must, restrict `allowed_objects` to types that do not execute logic
    during init  `allowed_objects='messages'` (or an explicit list of
    message classes) is the safe choice. Keep `secrets_from_env=False`
  • 可插拔集成层:Partner 包(langchain-openailangchain-anthropiclangchain-ollama 等)各自实现 BaseChatModel/BaseEmbeddings/BaseLLM 接口,通过 optional-dependencies 按需安装 pyproject.toml:32-51 。
[project.optional-dependencies]
community = ["langchain-community"]
anthropic = ["langchain-anthropic"]
openai = ["langchain-openai"]
azure-ai = ["langchain-azure-ai"]
#cohere = ["langchain-cohere"]
google-vertexai = ["langchain-google-vertexai"]
google-genai = ["langchain-google-genai"]
fireworks = ["langchain-fireworks"]
ollama = ["langchain-ollama"]
together = ["langchain-together"]
mistralai = ["langchain-mistralai"]
huggingface = ["langchain-huggingface"]
groq = ["langchain-groq"]
aws = ["langchain-aws"]
baseten = ["langchain-baseten>=0.2.0"]
deepseek = ["langchain-deepseek"]
xai = ["langchain-xai"]
perplexity = ["langchain-perplexity"]
meta = ["langchain-meta"]
  • Monorepo + uv 工具链:多包独立版本管理,使用 uv 作为包管理器、ruff 做 lint、mypy 做类型检查,保证跨包工程质量一致 CLAUDE.md:37-41 。
 
- `uv`  Fast Python package installer and resolver (replaces pip/poetry)
- `make`  Task runner for common development commands. Feel free to look at the `Makefile` for available commands and usage patterns.
- `ruff`  Fast Python linter and formatter
- `mypy`  Static type checking
- `pytest`

技术优点

优点依据
高度模块化,组件独立于具体模型提供商README.md:27-33
统一接口带来的模型可替换性README.md:64
原生支持同步/异步/批/流式四种执行模式init.py:6-15
与 LangSmith 深度集成,天然支持可观测性/评估/调试README.md:66
稳定的版本化策略,Core 层"battle-tested"README.md:31-33
生态丰富,社区集成多README.md:67

技术缺点/权衡

  • 遗留包负担:为兼容旧版,仓库同时维护 langchain-classic(legacy,不再新增特性)和新版 langchainlangchain_v1),存在架构上的历史包袱和迁移复杂度 AGENTS.md:14-16 。同样地 langchain_classic/runnables 和 langchain_classic/schema/runnable 两个模块几乎重复导出同一套 Runnable 符号,反映了旧 API 兼容层的冗余 init.py:1-18__init__.py:18-37 。
  • 序列化带来的安全风险:反序列化机制本质上是"信任边界"问题——即便有 allowlist,被允许的类如果在 __init__ 中有副作用(网络请求、读取环境变量等),仍可能被恶意 payload 利用(如 SSRF),文档中特别警告"不要对不可信输入调用 load()" load.py:49-65 。
  • 深度依赖 LangGraph:新版 langchain 的 agent 能力构建在 langgraph 之上,意味着复杂场景下用户实际上需要理解 LangGraph 的编排模型,脱离了"纯 LangChain"的简单性 README.md:21-25 。
  • 抽象层次带来的学习曲线:虽然文档宣传"从高层链到低层组件"的灵活抽象层次是优点,但这也意味着开发者需要在多层抽象(Runnable、Chain、Agent、LangGraph)之间做选择,增加了认知负担(源自 README 中 “Flexible abstraction layers” 描述本身隐含此权衡) README.md:68 。

关键技术原理

管道运算符(|)是什么以及如何实现

在 LangChain 中,管道运算符 | 是 Runnable 类重载的 __or__ 方法,用于将两个 Runnable 组合成一个 RunnableSequence,是 LCEL(LangChain Expression Language)声明式链式组合的核心语法糖 base.py:648-667 。


工作原理

a | b 等价于调用 a.__or__(b),内部先用 coerce_to_runnable() 把 b(可以是 Runnable、普通函数、或 dict)自动转换成 Runnable,然后构造 RunnableSequence(a, coerced_b) base.py:648-667 。

    def __or__(
        self,
        other: Runnable[Output, Other]
        | Callable[[Iterator[Output]], Iterator[Other]]
        | Callable[[AsyncIterator[Output]], AsyncIterator[Other]]
        | Callable[[Output], Other]
        | Mapping[str, Runnable[Output, Any] | Callable[[Output], Any] | Any],
    ) -> RunnableSerializable[Input, Any]:
        """Runnable "or" operator.
 
        Compose this `Runnable` with another object to create a
        `RunnableSequence`.
 
        Args:
            other: Another `Runnable` or a `Runnable`-like object.
 
        Returns:
            A new `Runnable`.
        """
        return RunnableSequence(self, coerce_to_runnable(other))

对称地,还有 __ror__(反向 or),用于处理左侧不是 Runnable 的情况,比如 dict() | some_runnable base.py:691-710 。

    @overload
    def __ror__(


        | Callable[[Other], Input],
    ) -> RunnableSerializable[Other, Output]: ...
 
    def __ror__(
        self,
        other: Runnable[Other, Input]
        | Callable[[Iterator[Other]], Iterator[Input]]
        | Callable[[AsyncIterator[Other]], AsyncIterator[Input]]
        | Callable[[Other], Any]
        | Mapping[str, Runnable[Other, Input] | Callable[[Other], Any] | Any],
    ) -> RunnableSerializable[Any, Output]:
        """Runnable "reverse-or" operator.
 
        Compose this `Runnable` with another object to create a
        `RunnableSequence`.
 
        Args:
            other: Another `Runnable` or a `Runnable`-like object.
 
        Returns:
            A new `Runnable`.
        """
        return RunnableSequence(coerce_to_runnable(other), self

等价写法:.pipe()

除了 | 运算符,Runnable 还提供 .pipe(*others) 方法,效果完全等价,示例中展示了三种等价写法 base.py:712-759 :

sequence = runnable_1.pipe(runnable_2)  # 等价于 
sequence = runnable_1 | runnable_2  # 等价于 
sequence = RunnableSequence(first=runnable_1, last=runnable_2)
def pipe(
        self,
        *others: Runnable[Any, Other] | Callable[[Any], Other],
        name: str | None = None,
    ) -> RunnableSerializable[Input, Other]:
        """Pipe `Runnable` objects.
 
        Compose this `Runnable` with `Runnable`-like objects to make a
        `RunnableSequence`.
 
        Equivalent to `RunnableSequence(self, *others)` or `self | others[0] | ...`
 
        Example:
            ```python
            from langchain_core.runnables import RunnableLambda
 
 
            def add_one(x: int) -> int:
                return x + 1
 
 
            def mul_two(x: int) -> int:
                return x * 2
 
 
            runnable_1 = RunnableLambda(add_one)
            runnable_2 = RunnableLambda(mul_two)
            sequence = runnable_1.pipe(runnable_2)
            # Or equivalently:
            # sequence = runnable_1 | runnable_2
            # sequence = RunnableSequence(first=runnable_1, last=runnable_2)
            sequence.invoke(1)
            await sequence.ainvoke(1)
            # -> 4
 
            sequence.batch([1, 2, 3])
            await sequence.abatch([1, 2, 3])
            # -> [4, 6, 8]
            ```
 
        Args:
            *others: Other `Runnable` or `Runnable`-like objects to compose
            name: An optional name for the resulting `RunnableSequence`.
 
        Returns:
            A new `Runnable`.
        """
        return RunnableSequence(self, *others, name=name)

组合的结果:RunnableSequence

RunnableSequence 是"上一步的输出作为下一步的输入"的线性管道,内部拆分为 firstmiddlelist)、last 三部分,构造函数会自动展平嵌套的 RunnableSequence(比如 (a|b) | (c|d) 会被展平成一个含 4 步的序列,而不是嵌套两层) base.py:3063-3196 。

 class RunnableSequence(RunnableSerializable[Input, Output]):
    """Sequence of `Runnable` objects, where the output of one is the input of the next.
 
    **`RunnableSequence`** is the most important composition operator in LangChain
    as it is used in virtually every chain.
 
    A `RunnableSequence` can be instantiated directly or more commonly by using the
    `|` operator where either the left or right operands (or both) must be a
    `Runnable`.
 
    Any `RunnableSequence` automatically supports sync, async, batch.
 
    The default implementations of `batch` and `abatch` utilize threadpools and
    asyncio gather and will be faster than naive invocation of `invoke` or `ainvoke`
    for IO bound `Runnable`s.
 
    Batching is implemented by invoking the batch method on each component of the
    `RunnableSequence` in order.
 
    A `RunnableSequence` preserves the streaming properties of its components, so if
    all components of the sequence implement a `transform` method -- which
    is the method that implements the logic to map a streaming input to a streaming
    output -- then the sequence will be able to stream input to output!
 
    If any component of the sequence does not implement transform then the
    streaming will only begin after this component is run. If there are
    multiple blocking components, streaming begins after the last one.
 
    !!! note
        `RunnableLambdas` do not support `transform` by default! So if you need to
        use a `RunnableLambdas` be careful about where you place them in a
        `RunnableSequence` (if you need to use the `stream`/`astream` methods).
 
        If you need arbitrary logic and need streaming, you can subclass
        Runnable, and implement `transform` for whatever logic you need.
 
    Here is a simple example that uses simple functions to illustrate the use of
    `RunnableSequence`:
 
        ```python
        from langchain_core.runnables import RunnableLambda
 
 
        def add_one(x: int) -> int:
            return x + 1
 
 
        def mul_two(x: int) -> int:
            return x * 2
 
 
        runnable_1 = RunnableLambda(add_one)
        runnable_2 = RunnableLambda(mul_two)
        sequence = runnable_1 | runnable_2
        # Or equivalently:
        # sequence = RunnableSequence(first=runnable_1, last=runnable_2)
        sequence.invoke(1)
        await sequence.ainvoke(1)
 
        sequence.batch([1, 2, 3])
        await sequence.abatch([1, 2, 3])
        ```
 
    Here's an example that uses streams JSON output generated by an LLM:
 
        ```python
        from langchain_core.output_parsers.json import SimpleJsonOutputParser
        from langchain_openai import ChatOpenAI
 
        prompt = PromptTemplate.from_template(
            "In JSON format, give me a list of {topic} and their "
            "corresponding names in French, Spanish and in a "
            "Cat Language."
        )
 
        model = ChatOpenAI()
        chain = prompt | model | SimpleJsonOutputParser()
 
        async for chunk in chain.astream({"topic": "colors"}):
            print("-")  # noqa: T201
            print(chunk, sep="", flush=True)  # noqa: T201
        ```
    """
 
    # The steps are broken into first, middle and last, solely for type checking
    # purposes. It allows specifying the `Input` on the first type, the `Output` of
    # the last type.
    first: Runnable[Input, Any]
    """The first `Runnable` in the sequence."""
    middle: list[Runnable[Any, Any]] = Field(default_factory=list)
    """The middle `Runnable` in the sequence."""
    last: Runnable[Any, Output]
    """The last `Runnable` in the sequence."""
 
    def __init__(
        self,
        *steps: RunnableLike[Any, Any],
        name: str | None = None,
        first: Runnable[Any, Any] | None = None,
        middle: list[Runnable[Any, Any]] | None = None,
        last: Runnable[Any, Any] | None = None,
    ) -> None:
        """Create a new `RunnableSequence`.
 
        Args:
            steps: The steps to include in the sequence.
            name: The name of the `Runnable`.
            first: The first `Runnable` in the sequence.
            middle: The middle `Runnable` objects in the sequence.
            last: The last `Runnable` in the sequence.
 
        Raises:
            ValueError: If the sequence has less than 2 steps.
        """
        steps_flat: list[Runnable[Any, Any]] = []
        if not steps and first is not None and last is not None:
            steps_flat = [first] + (middle or []) + [last]
        for step in steps:
            if isinstance(step, RunnableSequence):
                steps_flat.extend(step.steps)
            else:
                steps_flat.append(coerce_to_runnable(step))
        if len(steps_flat) < _RUNNABLE_SEQUENCE_MIN_STEPS:
            msg = (
                f"RunnableSequence must have at least {_RUNNABLE_SEQUENCE_MIN_STEPS} "
                f"steps, got {len(steps_flat)}"
            )
            raise ValueError(msg)
        super().__init__(
            first=steps_flat[0],
            middle=list(steps_flat[1:-1]),
            last=steps_flat[-1],
            name=name,
        )

典型用法(提示词 | 模型 | 输出解析器):

chain = prompt | model | SimpleJsonOutputParser()

这一用法在文档字符串和单元测试中都有体现 base.py:3126-3144test_runnable.py:2648-2661 。

特殊情况:dict 自动转 RunnableParallel

当管道右侧是一个普通 dict(如 {"chat": chat_model, "llm": llm})时,会被 coerce_to_runnable 自动包装成 RunnableParallel,实现"并行分支"效果,测试用例中有完整验证 test_runnable.py:2838-2851 。

    llm = FakeListLLM(responses=["i'm a textbot"])
 
    chain = (
        prompt
        | passthrough
        | {
            "chat": chat,
            "llm": llm,
        }
    )
 
    assert repr(chain) == snapshot
    assert isinstance(chain, RunnableSequence)
    assert chain.first == prompt
    assert chain.middle == [RunnableLambda(passthrough)]
    assert isinstance(chain.last, RunnableParallel)
    assert dumps(chain, pretty=True) == snapshot

好处

由于 RunnableSequence(以及所有 Runnable)统一实现了 invoke/ainvoke/batch/abatch/stream/astream 接口,管道拼接出来的链自动获得同步、异步、批处理、流式四种能力,这也是 LCEL 模块 docstring 中强调的设计目的 init.py:1-19 。

Notes

.pick().assign() 等方法内部也是通过 self | RunnablePick(...) / self | RunnableAssign(...) 实现的,本质上都是基于同一个 __or__ 管道机制 base.py:819-822 base.py:866-869 。旧版 langchain_classic 中也导出了同一套 Runnable 体系,行为一致 init.py:1-18 。

Serializable 基类的设计要点、必要性以及开发者怎样受益于此设计

Serializable 是 langchain-core 中所有可序列化对象的基类,定义在 serializable.py:88-113 ,它基于 Pydantic BaseModel,通过显式声明式的 opt-in 机制(而非默认可序列化)来控制哪些对象能被转成 JSON、如何标识类身份、以及如何安全处理密钥。

设计要点

1. 默认"不能序列化",这是为了安全

写一个新类继承 Serializable,如果什么都不做,is_lc_serializable() 返回 False serializable.py:120-131 

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Is this class serializable?
 
        By design, even if a class inherits from `Serializable`, it is not serializable
        by default. This is to prevent accidental serialization of objects that should
        not be serialized.
 
        Returns:
            Whether the class is serializable. Default is `False`.
        """
        return False

也就是说,即使有人调用序列化函数,你的对象也不会被转成 JSON。这是"安全默认值"的设计:防止你不小心把一个内部对象(可能带有敏感状态)暴露出去。

要真正支持序列化,必须像 OpenAI 这个类一样显式重写为 True

@classmethod  def is_lc_serializable(cls) -> bool:      """Return whether this model can be serialized by LangChain."""      return True

base.py:892-895

2. 类要有"身份证":get_lc_namespace / lc_id

序列化出来的 JSON 里要写清楚"这是哪个类",这样以后反序列化时程序才知道该实例化哪个 Python 类。默认实现直接用模块路径拆分,例如 langchain_openai.chat_models 会变成 ["langchain_openai", "chat_models"] serializable.py:133-157 。lc_id() 再加上类名,组成完整"身份证号",例如 ["langchain", "llms", "openai", "OpenAI"]serializable.py:177-195 。

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """Get the namespace of the LangChain object.
 
        The default implementation splits `cls.__module__` on `'.'`, e.g.
        `langchain_openai.chat_models` becomes
        `["langchain_openai", "chat_models"]`. This value is used by `lc_id` to
        build the serialization identifier.
 
        New partner packages should **not** override this method. The default
        behavior is correct for any class whose module path already reflects
        its package name. Some older packages (e.g. `langchain-openai`,
        `langchain-anthropic`) override it to return a legacy-style namespace
        like `["langchain", "chat_models", "openai"]`, matching the module
        paths that existed before those integrations were split out of the
        main `langchain` package. Those overrides are kept for
        backwards-compatible deserialization; new packages should not copy them.
 
        Deserialization mapping is handled separately by
        `SERIALIZABLE_MAPPING` in `langchain_core.load.mapping`.
 
        Returns:
            The namespace.
        """
        return cls.__module__.split(".")

OpenAI 类为了兼容以前的老版本格式,重写了 get_lc_namespace 直接返回 ["langchain", "llms", "openai"]

@classmethod  def get_lc_namespace(cls) -> list[str]:      """Get the namespace of the LangChain object.        Returns:          `["langchain", "llms", "openai"]`      """      return ["langchain", "llms", "openai"]

base.py:883-890

开发者受益:你完全不用自己写这套"身份证生成"逻辑,只有极少数需要兼容旧格式的情况才需要重写。

3. 密钥自动脱敏:lc_secrets

如果你把一个带有 API Key 的对象序列化成 JSON 存到文件或数据库里,密钥不能明文出现。OpenAI 类通过声明 lc_secrets 告诉框架"openai_api_key 这个构造参数对应环境变量 OPENAI_API_KEY":

@property  def lc_secrets(self) -> dict[str, str]:      """Mapping of secret keys to environment variables."""      return {"openai_api_key": "OPENAI_API_KEY"}

base.py:901-904

    @property
    def lc_secrets(self) -> dict[str, str]:
        """Mapping of secret keys to environment variables."""
        return {"openai_api_key": "OPENAI_API_KEY"}

序列化逻辑 to_json() 内部会遍历这个映射,把对应的值替换成一个"密钥占位符"而不是明文写出去 serializable.py:221-269 。

开发者受益:你写自己的模型类时,只需要声明这一行 lc_secrets,剩下的脱敏逻辑框架帮你做好了,不用自己写正则去过滤敏感字符串。

4. 额外信息也能带上:lc_attributes

有些配置不是 Pydantic 的字段(field),而是普通 Python 属性,但你仍希望它出现在序列化结果里,方便还原时用得上。OpenAI 用 lc_attributes把 openai_api_baseopenai_organizationopenai_proxy 这几个属性(如果有值)加进去:

@property  def lc_attributes(self) -> dict[str, Any]:      """LangChain attributes for this class."""      attributes: dict[str, Any] = {}      if self.openai_api_base:          attributes["openai_api_base"] = self.openai_api_base      ...      return attributes

base.py:906-919

    @property
    def lc_attributes(self) -> dict[str, Any]:
        """LangChain attributes for this class."""
        attributes: dict[str, Any] = {}
        if self.openai_api_base:
            attributes["openai_api_base"] = self.openai_api_base
 
        if self.openai_organization:
            attributes["openai_organization"] = self.openai_organization
 
        if self.openai_proxy:
            attributes["openai_proxy"] = self.openai_proxy
 
        return attributes
5. 整个机制怎么串起来:to_json()

to_json() 是真正做序列化的方法,它会:

  1. 遍历所有 Pydantic 字段,过滤出"有意义"的(非默认值、必填的)
  2. 沿着类的继承链(MRO)依次收集每一层的 lc_secrets 和 lc_attributes
  3. 用收集到的 secrets 把对应值替换成占位符
  4. 最终打包成 {"lc": 1, "type": "constructor", "id": ..., "kwargs": ...} serializable.py:209-269

    def to_json(self) -> SerializedConstructor | SerializedNotImplemented:
        """Serialize the object to JSON.
 
        Raises:
            ValueError: If the class has deprecated attributes.
 
        Returns:
            A JSON serializable object or a `SerializedNotImplemented` object.
        """
        if not self.is_lc_serializable():
            return self.to_json_not_implemented()
 
        model_fields = type(self).model_fields
        secrets = {}
        # Get latest values for kwargs if there is an attribute with same name
        lc_kwargs = {}
        for k, v in self:
            if not _is_field_useful(self, k, v):
                continue
            # Do nothing if the field is excluded
            if k in model_fields and model_fields[k].exclude:
                continue
 
            lc_kwargs[k] = getattr(self, k, v)
 
        # Merge the lc_secrets and lc_attributes from every class in the MRO
        for cls in [None, *self.__class__.mro()]:
            # Once we get to Serializable, we're done
            if cls is Serializable:
                break
 
            if cls:
                deprecated_attributes = [
                    "lc_namespace",
                    "lc_serializable",
                ]
 
                for attr in deprecated_attributes:
                    if hasattr(cls, attr):
                        msg = (
                            f"Class {self.__class__} has a deprecated "
                            f"attribute {attr}. Please use the corresponding "
                            f"classmethod instead."
                        )
                        raise ValueError(msg)
 
            # Get a reference to self bound to each class in the MRO
            this = cast("Serializable", self if cls is None else super(cls, self))
 
            secrets.update(this.lc_secrets)
            # Now also add the aliases for the secrets
            # This ensures known secret aliases are hidden.
            # Note: this does NOT hide any other extra kwargs
            # that are not present in the fields.
            for key in list(secrets):
                value = secrets[key]
                if (key in model_fields) and (
                    alias := model_fields[key].alias
                ) is not None:
                    secrets[alias] = value
            lc_kwargs.update(this.lc_attributes)
 
        # include all secrets, even if not specified in kwargs
        # as these secrets may be passed as an environment variable instead
6. 序列化系统的"防注入"守卫

在真正调用 to_json() 生成 JSON 的入口函数 default() 里,只有 isinstance(obj, Serializable) 才会走这套逻辑,否则走另一个"未实现"的分支,这本质上是一个 allowlist(白名单)机制,防止普通用户 dict 数据被误判成 LangChain 对象 dump.py:29-40 ;dump.py 顶部的说明也解释了这个思路 dump.py:1-16 。

可插拔集成层的设计

可插拔集成层是指 libs/partners/* 下一组独立发布的 Python 包(如 langchain-openailangchain-anthropiclangchain-ollama 等),它们各自只依赖 langchain-core,实现 BaseChatModel/Embeddings/BaseLLM 等核心接口,通过 optional-dependencies 机制按需安装 README.md:9-18 。

设计要点

1. 每个 partner 包独立、轻量、只依赖 core

例如 langchain-openai 的依赖仅为 langchain-coreopenaitiktoken,不依赖 langchain 或其它 partner 包 pyproject.toml:23-29;langchain-anthropic 同样只依赖 anthropiclangchain-corepydantic pyproject.toml:23-29 ;langchain-ollamalangchain-groqlangchain-mistralai 等均遵循相同模式 pyproject.toml:23-28pyproject.toml:23-28 pyproject.toml:23-31 。这保证了"轻量 Core + 可插拔集成"的架构:用户不需要的 provider 依赖不会被强制安装。

2. 版本独立、发布独立

每个 partner 包有自己的 pyproject.toml、独立版本号(如 langchain-openai 1.4.1、langchain-anthropic 1.5.2、langchain-perplexity1.4.0),互相之间不共享版本号 pyproject.toml:6-23 pyproject.toml:6-23 。这允许某个 provider SDK 升级或修 bug 时单独发版,不影响其它包。

3. 通过 optional-dependencies 挂载到主包

langchainlibs/langchain_v1/)主包本身不直接依赖任何 partner 包,而是把它们声明成 [project.optional-dependencies] 里的 extra,比如 langchain[openai]langchain[anthropic]langchain[ollama]等 pyproject.toml:32-51 。旧版 langchain-classic 也是同样模式(额外多了 cohere extra) pyproject.toml:36-53 ,且体现在其 uv.lock 的 optional-dependencies 段中 uv.lock:2713-2760 。这样开发者只需 pip install langchain[openai] 就能按需拉取对应 provider 集成,而不必安装全部 SDK。

4. 开发期通过 uv.sources 本地路径联动

在 monorepo 内部开发时,partner 包与其依赖方之间通过 [tool.uv.sources] 用相对路径 + editable = true 互相引用,例如 langchain-deepseek 直接依赖并 editable 引用 langchain-openai(因为 DeepSeek 兼容 OpenAI 协议) pyproject.toml:25-28 pyproject.toml:59-60 ;langchain-xai 同样 editable 依赖 langchain-openaipyproject.toml:68-71 。这说明有些 partner 包会复用另一个 partner 包的实现(如 OpenAI 兼容层),而不是每次都从零实现 BaseChatModel

5. 部分集成移出仓库,进一步解耦

Google、AWS 等大型 provider 的集成已经完全移出这个 monorepo,维护在独立仓库(langchain-ai/langchain-googlelangchain-ai/langchain-aws)中,AGENTS.md/CLAUDE.md 明确说明了这一分层策略,并指出这些仓库通常克隆在同级目录方便交叉引用 AGENTS.md:30-33 。这是比 monorepo 内 partner 包更进一步的解耦——第三方甚至可以完全脱离本仓库维护自己的集成包。

6. 统一测试标准(langchain-tests

所有 partner 包(openaianthropicgroqmistralaiollamaperplexityxaideepseekchromaexanomic 等)都在测试依赖中引入 langchain-tests>=1.1.9,这是共享的标准化测试套件,用来保证每个集成都遵守相同的接口契约,例如都能测试 ChatModelIntegrationTests/ChatModelUnitTests 一类基类 pyproject.toml:42-58 pyproject.toml:41-59 。这保证了"可插拔"不仅是依赖层面的插拔,接口行为也有一致性保证。

为什么都基于Pydantic Model,利用了Pydantic的哪些特性/优点?具体如何利用?

LangChain 几乎所有核心类都继承 Pydantic 的 BaseModel(通过 Serializable(BaseModel, ABC) serializable.py:88 ),主要是为了利用 Pydantic 提供的数据校验、类型系统、自动 schema 生成、以及运行时字段内省这几大能力,这些能力被广泛用于配置、序列化和工具调用等子系统。

具体利用方式

1. 数据校验与类型强制

所有构造参数在实例化时自动经过 Pydantic 校验(类型转换、必填检查等),这是 BaseModel 的原生能力,Serializable 直接继承而不做特殊处理,只是清空了默认的 __init__ docstring serializable.py:115-118 。

2. model_fields 用于运行时内省

Pydantic v2 的 model_fields 元数据被大量用于框架内部逻辑:

  • configurable_fields() 用 type(self).model_fields 校验用户传入的 key 是否是合法字段名,防止配置不存在的字段 base.py:2890-2897。
  • Serializable.to_json() 遍历 model_fields 判断哪些字段"有用"(非默认值、必填等)从而决定是否纳入序列化输出,同时检测字段的 exclude/alias 元数据来隐藏内部字段或映射密钥别名 serializable.py:221-269 。
  • _is_field_useful 直接用 type(inst).model_fields.get(key) 判断字段是否必填(is_required())、是否有 default_factory 等,来决定序列化时是否保留该字段 serializable.py:296-335 。
3. 自动生成 JSON Schema(用于工具调用、结构化输出)

Runnable.as_tool() 直接依赖 Pydantic 从类型注解推断出 args_schematype[BaseModel]),无需手写 schema,示例中 FSchema(BaseModel)通过 Field(..., description=...) 定义参数描述,最终这个 Pydantic 模型被传给 convert_runnable_to_tool 生成工具的调用签名 base.py:2747-2812 。这是 Pydantic 在"结构化输出/function calling"场景里的核心用途——LLM 的 tool-calling JSON schema 本质上就是 Pydantic 模型的 .model_json_schema()

4. 动态生成/合并模型(create_model_v2

RunnableAssign.get_output_schema() 会读取输入/输出的 Pydantic 字段(get_fields(...)),通过 _get_schema_field_definition 把 Pydantic v1/v2 的字段统一转换成字段定义元组,再用 create_model_v2 动态拼出一个新的输出 Pydantic 模型 RunnableAssignOutputpassthrough.py:439-458 ,其中 _get_schema_field_definition 专门处理 v1/v2 差异(比如 v1 必填字段用 None 默认值但 required=True,需转换成 ... 哨兵值) base.py:3043-3057 。这让 RunnableSequence/RunnableParallel 能在组合时自动推导出准确的输入输出类型给下游工具/UI 使用。

5. ConfigDict 定制模型行为

框架大量使用 Pydantic 的 model_config = ConfigDict(...) 来精细控制模型行为:

  • Serializable 设置 extra="ignore",允许构造时传入未声明字段而不报错,方便向后兼容 serializable.py:197-199 。
  • RunnableSerializable 设置 protected_namespaces=(),抑制 Pydantic 对 model_ 前缀字段名的警告(因为 LangChain 有很多 model_name 之类字段) base.py:2824-2828 。
  • RunnableSequence/RunnableParallel/DynamicRunnable 都设置 arbitrary_types_allowed=True,允许字段类型是任意 Python 类(如 Runnable 本身),绕开 Pydantic 默认只支持已知类型的限制 base.py:3223-3225 configurable.py:63-65 。
6. RootModel 判断输入输出形态

RunnableAssign.get_input_schema/get_output_schema 用 issubclass(map_input_schema, RootModel) 来判断该 schema 到底是"单一值"(RootModel 包装)还是"字典结构",从而决定是否直接返回原 schema 或需要合并字段 passthrough.py:430-437 ,这利用了 Pydantic 的 RootModel 特性来统一表示非 dict 类型的输入/输出。

7. 自定义 __repr_args__ 精简调试输出

Serializable 重写了 Pydantic BaseModel 的 __repr_args__,只显示与默认值不同的字段值,减少调试噪音,这是直接扩展 Pydantic repr 机制的例子 serializable.py:201-207 。

8. 动态配置类 DynamicRunnable

DynamicRunnableconfigurable_fields/configurable_alternatives 背后的实现)本身也是 RunnableSerializable(进而是 BaseModel),字段 default: RunnableSerializable[Input, Output] 和 config: RunnableConfig | None 都靠 Pydantic 字段声明和校验来保证类型正确 configurable.py:50-65 。

架构图

Notes

以上关于 v1/v2 兼容处理(_get_schema_field_definition)说明 LangChain 需要同时支持 Pydantic v1 和 v2 生态(部分旧 partner 包或用户代码可能仍用 v1 风格),这也是选择 Pydantic 而非自造校验系统的一个历史原因

langchain-core的核心数据结构

底层数据结构

1. Runnable[Input, Output] —— 泛型抽象类

Runnable 用 Python 的 Generic[Input, Output] 定义,是整个框架最基础的类型参数化结构,任何组件的输入输出类型都通过这两个类型变量表达 base.py:27-41 。它唯一的抽象方法是 invoke base.py:873-894 ,ainvoke 默认通过线程池调用它 base.py:896-917 。

    @abstractmethod
    def invoke(
        self,
        input: Input,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Output:
        """Transform a single input into an output.
 
        Args:
            input: The input to the `Runnable`.
            config: A config to use when invoking the `Runnable`.
 
                The config supports standard keys like `'tags'`, `'metadata'` for
                tracing purposes, `'max_concurrency'` for controlling how much work to
                do in parallel, and other keys.
 
                Please refer to `RunnableConfig` for more details.
 
        Returns:
            The output of the `Runnable`.
        """
    async def ainvoke(
        self,
        input: Input,
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> Output:
        """Transform a single input into an output.
 
        Args:
            input: The input to the `Runnable`.
            config: A config to use when invoking the `Runnable`.
 
                The config supports standard keys like `'tags'`, `'metadata'` for
                tracing purposes, `'max_concurrency'` for controlling how much work to
                do in parallel, and other keys.
 
                Please refer to `RunnableConfig` for more details.
 
        Returns:
            The output of the `Runnable`.
        """
        return await run_in_executor(config, self.invoke, input, config, **kwargs)
2. Serializable(BaseModel, ABC) —— Pydantic 数据模型

所有具体组件的字段声明都基于 Pydantic 的 BaseModelserializable.py:88 。这是"数据类型"层面的核心:字段用 Python 类型注解声明,由 Pydantic 在实例化时自动做类型校验和转换。

class Serializable(BaseModel, ABC):
    """Serializable base class.
 
3. 组合结构:RunnableSequence 的 first/middle/last

RunnableSequence 用三个字段表示一条链:

first: Runnable[Input, Any]
middle: list[Runnable[Any, Any]] = Field(default_factory=list)  
last: Runnable[Any, Output]

base.py:3150-3155

这是一个"头 + 中间列表 + 尾"的结构,而非简单 list[Runnable],目的是让类型检查器能分别推断出整条链的输入类型(来自 first)和输出类型(来自 last) base.py:3147-3156 。构造时会自动展平嵌套序列 base.py:3177-3196 ,

    def __init__(
        self,
        *steps: RunnableLike[Any, Any],

        Raises:
            ValueError: If the sequence has less than 2 steps.
        """
        steps_flat: list[Runnable[Any, Any]] = []
        if not steps and first is not None and last is not None:
            steps_flat = [first] + (middle or []) + [last]
        for step in steps:
            if isinstance(step, RunnableSequence):
                steps_flat.extend(step.steps)
            else:
                steps_flat.append(coerce_to_runnable(step))
        if len(steps_flat) < _RUNNABLE_SEQUENCE_MIN_STEPS:
            msg = (
                f"RunnableSequence must have at least {_RUNNABLE_SEQUENCE_MIN_STEPS} "
                f"steps, got {len(steps_flat)}"
            )
            raise ValueError(msg)
        super().__init__(
            first=steps_flat[0],
            middle=list(steps_flat[1:-1]),
            last=steps_flat[-1],
            name=name,
       

steps 属性把三段重新拼成一个 list[Runnable] 供遍历 base.py:3208-3215 。

    @property
    def steps(self) -> list[Runnable[Any, Any]]:
        """All the `Runnable`s that make up the sequence in order.
 
        Returns:
            A list of `Runnable`s.
        """
        return [self.first, *self.middle, self.last]
4. RunnableParallel —— dict[str, Runnable] 映射结构

RunnableParallel 本质上把一个输入"扇出"给多个 Runnable,返回 dict[str, Any] 输出,类定义直接继承 RunnableSerializable[Input, dict[str, Any]] base.py:3852 ,示例中 steps 字典把 key 映射到各自的 Runnable 分支 base.py:3883-3906 。

5. DynamicRunnable —— 简单字段组合

DynamicRunnableconfigurable_fields 背后的实现)用两个字段表达"默认 Runnable + 运行时配置":

default: RunnableSerializable[Input, Output]  config: RunnableConfig | None = None

configurable.py:57-61

6. 序列化产物:TypedDict 风格的 JSON 结构

Serializable.to_json() 输出的最终数据结构是一个固定形状的 dict

{      "lc": 1,      "type": "constructor",      "id": self.lc_id(),  # list[str]      "kwargs": lc_kwargs,  # dict[str, Any]  }

体现在 SerializedConstructor 这个 BaseSerialized 子类中,字段包括 type: Literal["constructor"] 和 kwargs: dict[str, Any]serializable.py:33-39 。类似地还有 SerializedSecret(占位密钥) serializable.py:42-46 和 SerializedNotImplemented(无法序列化时的兜底,带 repr: str | None) serializable.py:49-55 。这三种都是简单的、扁平的数据类(继承一个共同基类 BaseSerialized),用 Literal 类型区分标签(tagged union 模式)。

7. RunnableConfig —— TypedDict

不同于用 Pydantic 模型表达的组件本身,运行时配置 RunnableConfig 是一个 TypedDict(未在本次片段中展开定义,但此前架构讨论中提到过),字段包括 tags: list[str]metadata: dict[str, Any]callbacksmax_concurrency: int | Noneconfigurable: dict[str, Any] 等 —— 这些字段本身都是 Python 基本数据结构(listdictint)的直接使用,而非自定义类。

8. lc_secrets / lc_attributes —— 简单 dict[str, str] / dict[str, Any]

密钥映射和额外属性都用最基础的 dict 表达,例如 OpenAI 的 lc_secrets 返回 dict[str, str](构造参数名 → 环境变量名) ,lc_attributes 返回 dict[str, Any] 。

消息系统

BaseMessage(Serializable) 的字段是整个消息体系的基础结构 base.py:93-144 :

class BaseMessage(Serializable):
    """Base abstract message class.
 
    Messages are the inputs and outputs of a chat model.
 
    Examples include [`HumanMessage`][langchain.messages.HumanMessage],
    [`AIMessage`][langchain.messages.AIMessage], and
    [`SystemMessage`][langchain.messages.SystemMessage].
    """
 
    content: str | list[str | dict[Any, Any]]
    """The contents of the message."""
 
    additional_kwargs: dict[Any, Any] = Field(default_factory=dict)
    """Reserved for additional payload data associated with the message.
 
    For example, for a message from an AI, this could include tool calls as
    encoded by the model provider.
 
    """
 
    response_metadata: dict[Any, Any] = Field(default_factory=dict)
    """Examples: response headers, logprobs, token counts, model name."""
 
    type: str
    """The type of the message. Must be a string that is unique to the message type.
 
    The purpose of this field is to allow for easy identification of the message type
    when deserializing messages.
 
    """
 
    name: str | None = None
    """An optional name for the message.
 
    This can be used to provide a human-readable name for the message.
 
    Usage of this field is optional, and whether it's used or not is up to the
    model implementation.
 
    """
 
    id: str | None = Field(default=None, coerce_numbers_to_str=True)
    """An optional unique identifier for the message.
 
    This should ideally be provided by the provider/model which created the message.
 
    """
 
    model_config = ConfigDict(
        extra="allow",
    )
字段类型说明
contentstr | list[str | dict[Any, Any]]消息正文,纯字符串或内容块列表
additional_kwargsdict[Any, Any]provider 特定的额外数据(如工具调用编码)
response_metadatadict[Any, Any]响应元信息(headers、token 数等)
typestr区分子类的判别字段(Literal
namestr | None可选人类可读名字
idstr | None唯一标识符

model_config = ConfigDict(extra="allow") 允许 provider 附加未声明字段而不报错 base.py:142-144 。

type 是判别式联合(tagged union)的关键:各子类用 Literal 固定该值——SystemMessage.type = "system" system.py:29-30 、HumanMessage.type = "human" human.py:29-30 。

消息子类与 Chunk 变体

每种消息都有一个流式用的 *Chunk 变体,采用多重继承(先继承本类型再继承 BaseMessageChunk),如 SystemMessageChunk(SystemMessage, BaseMessageChunk) system.py:63-70 、HumanMessageChunk(HumanMessage, BaseMessageChunk) human.py:63-70。所有消息类型的完整清单(AIMessage/ToolMessage/ChatMessage/FunctionMessage/RemoveMessage 及各自 Chunk)都在 messages/__init__.py 的公开导出列表中 init.py:72-130 。

class SystemMessageChunk(SystemMessage, BaseMessageChunk):
    """System Message chunk."""
 
    # Ignoring mypy re-assignment here since we're overriding the value
    # to make sure that the chunk variant can be discriminated from the
    # non-chunk variant.
    type: Literal["SystemMessageChunk"] = "SystemMessageChunk"  # type: ignore[assignment]
    """The type of the message (used for serialization)."""
class HumanMessageChunk(HumanMessage, BaseMessageChunk):
    """Human Message chunk."""
 
    # Ignoring mypy re-assignment here since we're overriding the value
    # to make sure that the chunk variant can be discriminated from the
    # non-chunk variant.
    type: Literal["HumanMessageChunk"] = "HumanMessageChunk"  # type: ignore[assignment]
    """The type of the message (used for serialization)."""

模型层:BaseLanguageModel 及其字段

BaseLanguageModel(RunnableSerializable[LanguageModelInput, LanguageModelOutputVar], ABC) 是所有模型的基础结构,本身是一个泛型 Runnable base.py:166-174 ,字段如下 base.py:175-205 :

字段类型说明
cacheBaseCache | bool | None是否/如何缓存响应(exclude=True 不入序列化)
verbosebool是否打印响应文本
callbacksCallbacks挂载到运行链的回调
tagslist[str] | Nonetrace 标签
metadatadict[str, Any] | Nonetrace 元数据
custom_get_token_idsCallable[[str], list[int]] | None自定义分词器

这些字段普遍用 Field(..., exclude=True),意味着它们不会出现在 Serializable.to_json() 的序列化输出里(呼应之前讨论过的序列化字段过滤机制)。

输入输出的类型别名

模型层定义了几个关键类型别名,用来统一描述"输入/输出是什么形状" base.py:125-135 :

LanguageModelInput = PromptValue | str | Sequence[MessageLikeRepresentation]
"""Input to a language model."""
 
LanguageModelOutput = BaseMessage | str
"""Output from a language model."""
 
LanguageModelLike = Runnable[LanguageModelInput, LanguageModelOutput]
"""Input/output interface for a language model."""
 
LanguageModelOutputVar = TypeVar("LanguageModelOutputVar", AIMessage, str)
"""Type variable for the output of a language model."""
 

LanguageModelOutputVar 是关键——它把两条分支绑死:BaseChatModel用 AIMessage 实例化这个类型变量,BaseLLM 用 str 实例化 llms.py:296-304 。这正是"数据结构"层面体现 Chat Model vs. LLM 区分的地方 init.py:1-29 。

BaseLLM 的类型收窄

BaseLLM(BaseLanguageModel[str], ABC) 把泛型参数固定为 strOutputType 属性直接返回 str 类型 llms.py:296-319 ;

class BaseLLM(BaseLanguageModel[str], ABC):
    """Base LLM abstract interface.
 
    It should take in a prompt and return a string.
    """
 
    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )
 
    @functools.cached_property
    def _serialized(self) -> builtins.dict[str, Any]:
        # self is always a Serializable object in this case, thus the result is
        # guaranteed to be a dict since dumpd uses the default callback, which uses
        # obj.to_json which always returns TypedDict subclasses
        return cast("builtins.dict[str, Any]", dumpd(self))
 
    # --- Runnable methods ---
 
    @property
    @override
    def OutputType(self) -> type[str]:
        """Get the output type for this `Runnable`."""
        return str

其 _convert_input() 方法把输入统一转换成 PromptValueStringPromptValue 或 ChatPromptValue)这两个具体数据结构之一 llms.py:321-332 。

    def _convert_input(self, model_input: LanguageModelInput) -> PromptValue:
        if isinstance(model_input, PromptValue):
            return model_input
        if isinstance(model_input, str):
            return StringPromptValue(text=model_input)
        if isinstance(model_input, Sequence):
            return ChatPromptValue(messages=convert_to_messages(model_input))
        msg = (  # type: ignore[unreachable]
            f"Invalid input type {type(model_input)}. "
            "Must be a PromptValue, str, or list of BaseMessages."
        )
        raise ValueError(msg)
输出结果结构:LLMResult / ChatResult

模型调用的原始输出用两个容器数据结构表示:LLMResult(内含 list[list[Generation]])用于 LLM,ChatResult(内含 list[ChatGeneration])用于 Chat Model,二者都从 langchain_core.outputs 导出 init.py:1-55 。ChatGeneration 包一层 BaseMessageGeneration 包一层纯文本——这是消息系统与模型输出结构之间的桥梁。

""Output classes.
 
Used to represent the output of a language model call and the output of a chat.
 
The top container for information is the `LLMResult` object. `LLMResult` is used by both
chat models and LLMs. This object contains the output of the language model and any
additional information that the model provider wants to return.
 
When invoking models via the standard runnable methods (e.g. invoke, batch, etc.):
 
- Chat models will return `AIMessage` objects.
- LLMs will return regular text strings.
 
In addition, users can access the raw output of either LLMs or chat models via
callbacks. The `on_chat_model_end` and `on_llm_end` callbacks will return an `LLMResult`
object containing the generated outputs and any additional information returned by the
model provider.
 
In general, if information is already available in the AIMessage object, it is
recommended to access it from there rather than from the `LLMResult` object.
"""
 
from typing import TYPE_CHECKING
 
from langchain_core._import_utils import import_attr
 
if TYPE_CHECKING:
    from langchain_core.outputs.chat_generation import (
        ChatGeneration,
        ChatGenerationChunk,
    )
    from langchain_core.outputs.chat_result import ChatResult
    from langchain_core.outputs.generation import Generation, GenerationChunk
    from langchain_core.outputs.llm_result import LLMResult
    from langchain_core.outputs.run_info import RunInfo
 
__all__ = (
    "ChatGeneration",
    "ChatGenerationChunk",
    "ChatResult",
    "Generation",
    "GenerationChunk",
    "LLMResult",
    "RunInfo",
)
 
_dynamic_imports = {
    "ChatGeneration": "chat_generation",
    "ChatGenerationChunk": "chat_generation",
    "ChatResult": "chat_result",
    "Generation": "generation",
    "GenerationChunk": "generation",
    "LLMResult": "llm_result",
    "RunInfo": "run_info",
}
汇总表
数据结构归属关键字段/类型引用
BaseMessage消息系统content/type/additional_kwargs/response_metadata/idbase.py:93-144
SystemMessage/HumanMessage消息子类type: Literal[...] 判别字段system.py:29-30human.py:29-30
AnyMessage消息系统基于 type 的判别式联合utils.py:33-49
BaseLanguageModel模型层cache/callbacks/tags/metadatabase.py:175-205
LanguageModelInput/Output/OutputVar模型层输入输出类型别名base.py:125-135
LLMResult/ChatResult模型输出
CoolCats
CoolCats
理学学士

我的研究兴趣是时空数据分析、知识图谱、自然语言处理与服务端开发