Skip to content

Commit 729d54c

Browse files
committed
feat: detach tool execution outside runtime
1 parent efee43b commit 729d54c

File tree

12 files changed

+1219
-113
lines changed

12 files changed

+1219
-113
lines changed

Dockerfile

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# syntax=docker/dockerfile:1
22
# Dockerfile for Langflow Executor Node
3-
# Build from repo root: docker build -f node/Dockerfile -t langflow-executor-node .
4-
# OR build from node directory: docker build -f Dockerfile -t langflow-executor-node ../
3+
# Build from repo root: docker build -f nodes/langflow-executor-node/Dockerfile -t langflow-executor-node .
4+
# OR from droqflow directory: cd droqflow && docker build -f ../nodes/langflow-executor-node/Dockerfile -t langflow-executor-node .
55

66
################################
77
# BUILDER STAGE
@@ -28,15 +28,15 @@ ENV UV_COMPILE_BYTECODE=1
2828
ENV UV_LINK_MODE=copy
2929

3030
# Copy Langflow dependency files first (for better caching)
31-
# These paths work when building from repo root
32-
COPY app/src/lfx/pyproject.toml /app/src/lfx/pyproject.toml
33-
COPY app/src/lfx/README.md /app/src/lfx/README.md
31+
# These paths assume build context includes droqflow directory
32+
COPY droqflow/app/src/lfx/pyproject.toml /app/src/lfx/pyproject.toml
33+
COPY droqflow/app/src/lfx/README.md /app/src/lfx/README.md
3434

3535
# Copy executor node dependency files
36-
COPY node/pyproject.toml /app/node/pyproject.toml
36+
COPY nodes/langflow-executor-node/pyproject.toml /app/node/pyproject.toml
3737

3838
# Copy Langflow source (needed for installation)
39-
COPY app/src/lfx/src /app/src/lfx/src
39+
COPY droqflow/app/src/lfx/src /app/src/lfx/src
4040

4141
# Install Langflow (lfx) package with all dependencies
4242
# This installs lfx and all its dependencies from pyproject.toml
@@ -47,6 +47,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
4747
# Install common Langchain integration packages needed by components
4848
RUN --mount=type=cache,target=/root/.cache/uv \
4949
uv pip install --system --no-cache \
50+
"langchain-core>=0.3.79,<0.4.0" \
5051
langchain-anthropic \
5152
langchain-openai \
5253
langchain-community \
@@ -69,10 +70,10 @@ RUN --mount=type=cache,target=/root/.cache/uv \
6970
python-dotenv
7071

7172
# Copy executor node source
72-
COPY node/src /app/node/src
73+
COPY nodes/langflow-executor-node/src /app/node/src
7374

7475
# Copy components.json mapping file
75-
COPY node/components.json /app/components.json
76+
COPY nodes/langflow-executor-node/components.json /app/components.json
7677

7778
################################
7879
# RUNTIME STAGE

components.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"APIRequestComponent": "lfx.components.data.api_request",
55
"AddContentToPage": "lfx.components.Notion.add_content_to_page",
66
"AgentComponent": "lfx.components.agents.agent",
7-
"AgentQL": "lfx.components.agentql.agentql_api",
87
"AlterMetadataComponent": "lfx.components.processing.alter_metadata",
98
"AmazonBedrockComponent": "lfx.components.amazon.amazon_bedrock_model",
109
"AmazonBedrockConverseComponent": "lfx.components.amazon.amazon_bedrock_converse",

lfx/src/lfx/base/tools/component_tool.py

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

33
import asyncio
4+
import copy
5+
import logging
46
import re
57
from typing import TYPE_CHECKING, Literal
68

@@ -25,6 +27,8 @@
2527
from lfx.schema.content_block import ContentBlock
2628
from lfx.schema.dotdict import dotdict
2729

30+
logger = logging.getLogger(__name__)
31+
2832
TOOL_TYPES_SET = {"Tool", "BaseTool", "StructuredTool"}
2933

3034

@@ -168,6 +172,54 @@ def _should_skip_output(self, output: Output) -> bool:
168172
output.name == TOOL_OUTPUT_NAME or any(tool_type in output.types for tool_type in TOOL_TYPES_SET)
169173
)
170174

175+
def _attach_runtime_metadata(
176+
self,
177+
tool: BaseTool | StructuredTool,
178+
output: Output,
179+
*,
180+
is_async: bool,
181+
args_schema: type | None,
182+
) -> None:
183+
"""Annotate tool metadata with component/runtime information."""
184+
metadata = tool.metadata or {}
185+
metadata["_component_method"] = output.method
186+
metadata["_component_is_async"] = is_async
187+
metadata["component_class"] = self.component.__class__.__name__
188+
metadata["component_module"] = self.component.__class__.__module__
189+
metadata["component_id"] = self.component.get_id()
190+
metadata["stream_topic"] = self.component._build_stream_topic() # noqa: SLF001
191+
192+
try:
193+
component_state = self.component._serialize_for_executor() # noqa: SLF001
194+
metadata["_component_state"] = component_state
195+
except Exception as exc: # noqa: BLE001
196+
logger.warning(
197+
"Failed to serialize component state for tool '%s': %s",
198+
tool.name,
199+
exc,
200+
)
201+
202+
metadata["_tool_input_keys"] = self._extract_input_keys(args_schema, output)
203+
204+
executor_meta = self.component.get_executor_node_metadata()
205+
if executor_meta:
206+
metadata["executor_node"] = executor_meta
207+
tool.metadata = metadata
208+
209+
@staticmethod
210+
def _extract_input_keys(args_schema: type | None, output: Output) -> list[str]:
211+
"""Return list of input field names for this tool."""
212+
keys: list[str] = []
213+
if args_schema is not None:
214+
for attr_name in ("model_fields", "__fields__"):
215+
schema_fields = getattr(args_schema, attr_name, None)
216+
if schema_fields:
217+
keys = list(schema_fields.keys())
218+
break
219+
if not keys and getattr(output, "required_inputs", None):
220+
keys = list(output.required_inputs)
221+
return keys
222+
171223
def get_tools(
172224
self,
173225
tool_name: str | None = None,
@@ -228,37 +280,37 @@ def get_tools(
228280
formatted_name = _format_tool_name(name)
229281
event_manager = self.component.get_event_manager()
230282
if asyncio.iscoroutinefunction(output_method):
231-
tools.append(
232-
StructuredTool(
233-
name=formatted_name,
234-
description=build_description(self.component),
235-
coroutine=_build_output_async_function(self.component, output_method, event_manager),
236-
args_schema=args_schema,
237-
handle_tool_error=True,
238-
callbacks=callbacks,
239-
tags=[formatted_name],
240-
metadata={
241-
"display_name": formatted_name,
242-
"display_description": build_description(self.component),
243-
},
244-
)
283+
structured_tool = StructuredTool(
284+
name=formatted_name,
285+
description=build_description(self.component),
286+
coroutine=_build_output_async_function(self.component, output_method, event_manager),
287+
args_schema=args_schema,
288+
handle_tool_error=True,
289+
callbacks=callbacks,
290+
tags=[formatted_name],
291+
metadata={
292+
"display_name": formatted_name,
293+
"display_description": build_description(self.component),
294+
},
245295
)
296+
tools.append(structured_tool)
297+
self._attach_runtime_metadata(structured_tool, output, is_async=True, args_schema=args_schema)
246298
else:
247-
tools.append(
248-
StructuredTool(
249-
name=formatted_name,
250-
description=build_description(self.component),
251-
func=_build_output_function(self.component, output_method, event_manager),
252-
args_schema=args_schema,
253-
handle_tool_error=True,
254-
callbacks=callbacks,
255-
tags=[formatted_name],
256-
metadata={
257-
"display_name": formatted_name,
258-
"display_description": build_description(self.component),
259-
},
260-
)
299+
structured_tool = StructuredTool(
300+
name=formatted_name,
301+
description=build_description(self.component),
302+
func=_build_output_function(self.component, output_method, event_manager),
303+
args_schema=args_schema,
304+
handle_tool_error=True,
305+
callbacks=callbacks,
306+
tags=[formatted_name],
307+
metadata={
308+
"display_name": formatted_name,
309+
"display_description": build_description(self.component),
310+
},
261311
)
312+
tools.append(structured_tool)
313+
self._attach_runtime_metadata(structured_tool, output, is_async=False, args_schema=args_schema)
262314
if len(tools) == 1 and (tool_name or tool_description):
263315
tool = tools[0]
264316
tool.name = _format_tool_name(str(tool_name)) or tool.name

0 commit comments

Comments
 (0)