Skip to content

Commit b5b2848

Browse files
authored
Merge pull request #2 from droq-ai/0x3bfc/feat/loop
feat: update loop, condition components, and csv loader logic
2 parents 729d54c + 400dedb commit b5b2848

File tree

24 files changed

+977
-1437
lines changed

24 files changed

+977
-1437
lines changed

.dockerignore

Lines changed: 22 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,30 @@
1-
# Git
2-
.git
3-
.gitignore
4-
.gitattributes
5-
6-
# Documentation
7-
README.md
8-
docs/
9-
*.md
10-
11-
# Tests
12-
tests/
13-
.pytest_cache/
14-
.coverage
15-
16-
# Python cache
17-
__pycache__/
1+
__pycache__
182
*.pyc
193
*.pyo
204
*.pyd
215
.Python
22-
23-
# Virtual environments
24-
venv/
25-
env/
6+
*.so
7+
*.egg
8+
*.egg-info
9+
dist
10+
build
2611
.venv
27-
28-
# IDEs
29-
.vscode/
30-
.idea/
31-
*.swp
32-
*.swo
33-
34-
# Environment files
12+
venv
13+
env
3514
.env
3615
.env.local
37-
38-
# Build artifacts
39-
dist/
40-
build/
41-
*.egg-info/
42-
43-
# Docker
44-
docker-compose.yml
45-
Dockerfile.dev
46-
.dockerignore
47-
48-
# CI/CD
49-
.github/
50-
.gitlab-ci.yml
51-
52-
# Misc
53-
.DS_Store
5416
*.log
55-
17+
.git
18+
.gitignore
19+
.github
20+
tests
21+
.pytest_cache
22+
.coverage
23+
htmlcov
24+
.mypy_cache
25+
.ruff_cache
26+
node_modules
27+
.DS_Store
28+
*.swp
29+
*.swo
30+
*~

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
.env
33
.env.local
44
.env.*.local
5-
app/langflow-data/
65

6+
myenv/
77
# Python
88
__pycache__/
99
*.py[cod]

Dockerfile

Lines changed: 9 additions & 10 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 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 .
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 ../
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 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
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
3434

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

3838
# Copy Langflow source (needed for installation)
39-
COPY droqflow/app/src/lfx/src /app/src/lfx/src
39+
COPY 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,7 +47,6 @@ 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" \
5150
langchain-anthropic \
5251
langchain-openai \
5352
langchain-community \
@@ -70,10 +69,10 @@ RUN --mount=type=cache,target=/root/.cache/uv \
7069
python-dotenv
7170

7271
# Copy executor node source
73-
COPY nodes/langflow-executor-node/src /app/node/src
72+
COPY node/src /app/node/src
7473

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

7877
################################
7978
# RUNTIME STAGE

components.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
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",
78
"AlterMetadataComponent": "lfx.components.processing.alter_metadata",
89
"AmazonBedrockComponent": "lfx.components.amazon.amazon_bedrock_model",
910
"AmazonBedrockConverseComponent": "lfx.components.amazon.amazon_bedrock_converse",

components.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Build a JSON mapping of component class names to their module paths.
4+
5+
Scans the lfx/components directory and creates a mapping file.
6+
"""
7+
8+
import ast
9+
import json
10+
import os
11+
from pathlib import Path
12+
13+
14+
def find_component_classes(directory: str) -> dict[str, str]:
15+
"""Scan directory for component classes and build mapping.
16+
17+
Args:
18+
directory: Root directory to scan (e.g., lfx/src/lfx/components)
19+
20+
Returns:
21+
Dictionary mapping class names to module paths
22+
"""
23+
mapping = {}
24+
components_dir = Path(directory)
25+
26+
if not components_dir.exists():
27+
print(f"Warning: Components directory not found: {directory}")
28+
return mapping
29+
30+
# Walk through all Python files in components directory
31+
for py_file in components_dir.rglob("*.py"):
32+
# Skip __init__.py and __pycache__
33+
if py_file.name.startswith("__") or "__pycache__" in str(py_file):
34+
continue
35+
36+
try:
37+
# Read and parse the file
38+
with open(py_file, 'r', encoding='utf-8') as f:
39+
content = f.read()
40+
41+
# Parse AST to find component classes
42+
tree = ast.parse(content, filename=str(py_file))
43+
44+
for node in ast.walk(tree):
45+
if isinstance(node, ast.ClassDef):
46+
# Check if it's a component class (ends with Component or inherits from Component)
47+
class_name = node.name
48+
if class_name.endswith("Component") or any(
49+
base.id == "Component" or base.id.endswith("Component")
50+
for base in node.bases
51+
if isinstance(base, ast.Name)
52+
):
53+
# Build module path from file path
54+
# e.g., lfx/src/lfx/components/input_output/text.py
55+
# -> lfx.components.input_output.text
56+
rel_path = py_file.relative_to(components_dir.parent.parent)
57+
module_parts = list(rel_path.parts[:-1]) + [rel_path.stem]
58+
module_path = ".".join(module_parts)
59+
60+
# Only add if not already in mapping (first occurrence wins)
61+
if class_name not in mapping:
62+
mapping[class_name] = module_path
63+
print(f"Found: {class_name} -> {module_path}")
64+
65+
except (SyntaxError, UnicodeDecodeError) as e:
66+
print(f"Warning: Could not parse {py_file}: {e}")
67+
continue
68+
except Exception as e:
69+
print(f"Error processing {py_file}: {e}")
70+
continue
71+
72+
return mapping
73+
74+
75+
def main():
76+
"""Main function to build component mapping."""
77+
# Get the script directory
78+
script_dir = Path(__file__).parent
79+
80+
# Try to find lfx components directory
81+
lfx_paths = [
82+
script_dir / "lfx" / "src" / "lfx" / "components",
83+
script_dir.parent / "app" / "src" / "lfx" / "src" / "lfx" / "components",
84+
]
85+
86+
components_dir = None
87+
for path in lfx_paths:
88+
if path.exists():
89+
components_dir = path
90+
break
91+
92+
if not components_dir:
93+
print("Error: Could not find lfx/components directory")
94+
print(f"Tried: {lfx_paths}")
95+
return 1
96+
97+
print(f"Scanning components directory: {components_dir}")
98+
mapping = find_component_classes(str(components_dir))
99+
100+
# Save to JSON file
101+
output_file = script_dir / "components.json"
102+
with open(output_file, 'w') as f:
103+
json.dump(mapping, f, indent=2, sort_keys=True)
104+
105+
print(f"\n✅ Created component mapping: {output_file}")
106+
print(f" Found {len(mapping)} components")
107+
108+
return 0
109+
110+
111+
if __name__ == "__main__":
112+
exit(main())
113+

lfx/src/lfx/base/agents/utils.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,23 @@ def maybe_unflatten_dict(flat: dict[str, Any]) -> dict[str, Any]:
220220

221221

222222
def get_chat_output_sender_name(self) -> str | None:
223-
"""Get sender_name from ChatOutput component."""
223+
"""Get sender_name from ChatOutput component.
224+
225+
Returns None if:
226+
- No graph available
227+
- Graph has no vertices (e.g., PlaceholderGraph on executor)
228+
- No ChatOutput component found
229+
"""
224230
if not hasattr(self, "graph") or not self.graph:
225231
return None
226232

227-
for vertex in self.graph.vertices:
233+
# Use getattr with default - PlaceholderGraph on executor has no vertices
234+
# because executor only has the single component being executed, not the full graph
235+
vertices = getattr(self.graph, "vertices", None)
236+
if not vertices:
237+
return None
238+
239+
for vertex in vertices:
228240
# Safely check if vertex has data attribute, correct type, and raw_params
229241
if (
230242
hasattr(vertex, "data")

0 commit comments

Comments
 (0)