跳转到内容

组件即工具:将可视化节点暴露为 Agent 可调用的 Tool

学习目标

  • 理解 Component-as-Tool 的工作原理:如何将画布上的节点变为 Agent 工具
  • 掌握 tool_mode=True 的使用方法和参数推导规则
  • 了解工具执行时的隔离策略(deepcopy + send_message no-op)
  • 学会在 Agent 流程中调用组件工具

前置知识

本章涉及工具调用协议的通用原理,建议先阅读:

下文假设你已理解工具调用的基本协议,直接聚焦 Langflow 如何将组件包装为工具的具体实现。


项目实践

1. 从 Input 到 StructuredTool

在 Langflow 中,组件工具化的核心入口是 Inputtool_mode 标记:

class VectorStoreComponent:
inputs = [
Input(
name="query",
field_type="str",
tool_mode=True, # 标记为 Agent 工具参数
display_name="查询文本",
),
Input(
name="top_k",
field_type="int",
default=5,
advanced=True, # 不暴露为工具参数
),
]

当组件被注册为工具时,ComponentToolkit 执行以下推导:

参数推导规则

Input 属性工具 Schema 映射
name工具参数名
field_typeJSON Schema type(str→string, int→integer, float→number, bool→boolean)
input_types联合类型(如 ["Message", "str"]string
defaultJSON Schema default 值
description / display_name工具参数 description
advanced=True不纳入工具参数

2. 执行隔离:防止并发污染

当 Agent 并发调用同一组件工具时(如同时向向量数据库查询多个问题),Langflow 采用深拷贝隔离策略:

def _build_output_function(component, output_method):
def output_function(*args, **kwargs):
# 深拷贝组件实例,每个调用操作独立的副本
comp = deepcopy(component)
local_method = getattr(comp, output_method.__name__)
return local_method(*args, **kwargs)
return output_function

为什么需要深拷贝:组件实例在执行过程中会修改内部状态(如 _results_token_usage),如果多个并发调用共享同一实例,会导致状态交叉和不可预测的结果。

3. 消息静默:工具执行期间禁止 UI 输出

组件正常执行时会通过 send_message 向 UI 推送中间状态。但作为工具被调用时,这些中间消息不应该出现在用户的对话流中:

def _patch_send_message_decorator(component, func):
async def async_wrapper(*args, **kwargs):
original_send_message = component.send_message
component.send_message = send_message_noop # 替换为 no-op
try:
return await func(*args, **kwargs)
finally:
component.send_message = original_send_message # 恢复
return async_wrapper

关键设计:仅调用工具的组件自身可以发送消息到 UI,被调用的工具子组件的消息被静默。这样用户只看到最终结果,而非工具内部的执行细节。


问题与规避

陷阱现象对策
并发状态污染同时调用同一工具时返回混乱的结果Langflow 已自动 deepcopy,但组件内不应使用类级别的共享状态(如 class 级别的 listdict
工具参数过多Agent 收到过多参数,选择困难使用 advanced=True 隐藏非关键参数,仅暴露 tool_mode=True 的核心参数
工具描述缺失Agent 不知道何时调用该工具编写清晰的 description,这是 Agent 决定是否调用工具的唯一依据
输出类型不匹配下游组件无法接收工具输出确保 Output(types=[...]) 声明的类型与下游组件的 input_types 兼容

设计取舍

Component-as-Tool vs 手写 Tool 函数

维度Component-as-Tool手写 Tool 函数
开发成本低,在现有组件上加标记即可高,需要单独实现工具函数
一致性工具行为与画布行为一致可能 diverge
灵活性受限于组件的 Input/Output 模型完全自由
可调试性可在画布中单独测试组件需要独立测试环境

Langflow 选择 Component-as-Tool 模式的核心原因是其十元原则之一:「每个后端功能必须落在画布上」。如果一个能力不能作为组件出现在画布上,那它就不应该存在于后端。


参考来源

  • 源码:src/lfx/src/lfx/base/tools/component_tool.py
  • 源码:src/lfx/src/lfx/custom/custom_component/component.py
  • 组件开发指南:docs/agents/COMPONENTS.md