Skip to content

TR-001: TFNeuralModel / Neural deepcopy 循环引用

字段
编号 TR-001
标题 TFNeuralModel / Neural deepcopy 循环引用
状态 已修复
日期 2026-02-12
影响版本 <= 0.3.3b1

问题现象

在对包含 BaseTool(或其他 TFNeuralModel 子类)的容器执行 copy.deepcopy() 或 Pydantic 的 model_copy(deep=True) 时,会出现以下三种异常之一:

异常类型 触发条件 表现
RecursionError 默认递归深度 maximum recursion depth exceeded
TypeError: cannot pickle 'socket' object Neural 持有网络相关资源时 deepcopy 尝试复制不可序列化对象
TypeError: cannot pickle 'module' object Neural 持有模块引用时 deepcopy 尝试复制 Python 模块

已确认的触发点:

  • dc_chains.py:60chain_ctx.model_copy(deep=True)
  • TFRobotServer dc_chain.pysplit_funccontext.model_copy(deep=True)

两者都通过 ChainContext.tools: list[BaseTool] 触发。

根因分析

循环引用结构

TFNeuralModelNeural 之间存在双向引用:

TFNeuralModel                     Neural
┌──────────────────┐              ┌──────────────────┐
│                  │   _neural    │                  │
│  _neural: Neural ├─────────────►│  _tf_models:     │
│                  │              │    WeakSet[       │
│                  │◄─────────────┤      TFNeuralModel│
│                  │   (弱引用)    │    ]             │
└──────────────────┘              └──────────────────┘

deepcopy 递归路径

copy.deepcopy(ChainContext)
  └─ deepcopy(tools: list[BaseTool])
       └─ deepcopy(BaseTool)                    # TFNeuralModel 子类
            └─ deepcopy(BaseTool._neural)        # Neural 实例
                 └─ deepcopy(Neural._tf_models)  # WeakSet
                      └─ deepcopy(BaseTool)      # 回到起点 → 无限递归
                           └─ deepcopy(BaseTool._neural)
                                └─ ...           # RecursionError

weakref.WeakSet 在被 deepcopy 时会迭代其全部成员并逐一深拷贝,因此无法自动打断循环。

影响范围

所有 TFNeuralModel 的子类均受影响:

子类 模块
BaseTool tfrobot.drive.tool.base
BaseChain tfrobot.brain.chain.base
Chains tfrobot.brain.chain.chain_structures.base
BaseLLM tfrobot.brain.chain.llms.base
BaseBrain tfrobot.brain.base
BaseDrive tfrobot.drive.base
BaseMemory tfrobot.brain.memory.base

修复方案

方案:TFNeuralModel + Neural 实现 __deepcopy__ / __copy__ 返回 self

TFNeuralModelNeural 类上分别添加 __copy____deepcopy__ 方法,直接返回 self

# tfrobot/schema/meta/tf_neural_model.py
class TFNeuralModel(TFBaseModel):
    ...
    def __copy__(self) -> "TFNeuralModel":
        return self

    def __deepcopy__(self, memo: dict[int, Any]) -> "TFNeuralModel":
        memo[id(self)] = self
        return self
# tfrobot/neural/base.py
class Neural(TFBaseModel):
    ...
    def __copy__(self) -> "Neural":
        return self

    def __deepcopy__(self, memo: dict[int, Any]) -> "Neural":
        memo[id(self)] = self
        return self

为什么返回 self 是安全的

  1. _prepare_chain_context 已使用浅拷贝base.py:550copy.copy(tools) 只拷贝列表容器,工具对象本身保持引用共享。这是有意设计——注释明确写道"浅拷贝,避免循环引用"。

  2. 深拷贝 Neural 组件在架构上不成立:深拷贝的副本不会注册到 Neural 信号系统中,成为"幽灵"实例——没有信号连接、没有事件处理能力,违反了组件必须通过 Neural 注册才能工作的架构不变量。

  3. 代码库中没有故意 deepcopy TFNeuralModel 的场景:所有触发 deepcopy 的代码都是对容器(如 ChainContext)做深拷贝时意外递归到组件。

为什么不在 ChainContext 层修复

考量 说明
覆盖不完整 只保护 ChainContext 一个容器,其他持有 TFNeuralModel 的容器仍有风险
维护负担 ChainContext 的 __deepcopy__ 需要在每次字段变更时同步维护
不必要 TFNeuralModel 层修复后,ChainContext.model_copy(deep=True) 自动正常工作

验证

修复后的行为:

import copy

neural = Neural(name="test")
tool = StubTool()
neural.register(tool)

# 不再抛出 RecursionError
copied = copy.deepcopy(tool)
assert copied is tool  # 返回同一实例

# ChainContext.model_copy(deep=True) 正常工作
ctx = ChainContext(tools=[tool], ...)
ctx_copy = ctx.model_copy(deep=True)
assert ctx_copy.tools[0] is tool          # tools 内对象共享引用
assert ctx_copy.tools is not ctx.tools    # tools 列表本身是新的