mirror of
https://github.com/hanzoai/flow.git
synced 2026-10-17 01:29:18 +00:00
docs: rewrite AGENTS.md as hub with topical project philosophy docs (#13038)
Splits monolithic agent instructions into a slim AGENTS.md hub plus docs/agents/ topic files (philosophy, architecture, components, contracts, testing, anti-patterns) so AI coding agents have a single source of truth for how the project thinks. Removes the .cursor/rules/ directory; AGENTS.md is now the only agent-facing doc. Fixes the long-standing pointer to the empty src/backend/base/langflow/components/ stubs (real components live in src/lfx/src/lfx/components/) and corrects the v2 API description from 'future' to its actual mounted state.
This commit is contained in:
committed by
GitHub
parent
81470aa59c
commit
2985af2735
@@ -1,268 +0,0 @@
|
||||
---
|
||||
description: "Guidelines for backend development in Langflow, focusing on Python components, FastAPI services, and backend testing."
|
||||
globs:
|
||||
- "src/backend/**/*.py"
|
||||
- "tests/**/*.py"
|
||||
- "Makefile"
|
||||
- "pyproject.toml"
|
||||
- "uv.lock"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
|
||||
# Backend Development Guidelines
|
||||
|
||||
## Purpose
|
||||
Guidelines for backend development in Langflow, focusing on Python components, FastAPI services, and backend testing.
|
||||
|
||||
---
|
||||
|
||||
## 1. Backend Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
- **Python Package Manager:** `uv` (>=0.4) for dependency management
|
||||
- **Database:** SQLite for development, PostgreSQL for production
|
||||
- **Development Tools:** `make` for build coordination
|
||||
|
||||
### Backend Service
|
||||
```bash
|
||||
make backend # Start FastAPI backend on port 7860
|
||||
```
|
||||
- Auto-reloads on file changes
|
||||
- Health check: http://localhost:7860/health
|
||||
- Backend components: `src/backend/base/langflow/`
|
||||
|
||||
---
|
||||
|
||||
## 2. Component Development
|
||||
|
||||
### Component Structure
|
||||
```
|
||||
src/backend/base/langflow/components/
|
||||
├── agents/ # Agent components
|
||||
├── data/ # Data processing components
|
||||
├── embeddings/ # Embedding components
|
||||
├── input_output/ # Input/output components
|
||||
├── models/ # Language model components
|
||||
├── processing/ # Text processing components
|
||||
├── prompts/ # Prompt components
|
||||
├── tools/ # Tool components
|
||||
└── vectorstores/ # Vector store components
|
||||
```
|
||||
|
||||
### Adding New Components
|
||||
1. **Location:** Add to appropriate subdirectory under `src/backend/base/langflow/components/`
|
||||
2. **Import:** Update `__init__.py` with alphabetical imports:
|
||||
```python
|
||||
from .my_component import MyComponent
|
||||
|
||||
__all__ = [
|
||||
"ExistingComponent",
|
||||
"MyComponent", # Add alphabetically
|
||||
]
|
||||
```
|
||||
3. **Auto-restart:** Backend auto-restarts on save
|
||||
4. **Browser refresh:** Refresh browser to see component changes
|
||||
|
||||
### Component Testing
|
||||
- **Unit Tests:** `src/backend/tests/unit/components/`
|
||||
- **Test Structure:** Mirror component directory structure
|
||||
- **Test Base Classes:** Use `ComponentTestBaseWithClient` or `ComponentTestBaseWithoutClient`
|
||||
- **Version Testing:** Provide `file_names_mapping` for backward compatibility
|
||||
|
||||
### Development Tips
|
||||
- **Fast iteration:** Edit component in UI first, then save to source
|
||||
- **Component updates:** Old components show "Updates Available" after backend restart
|
||||
- **Testing:** Create comprehensive unit tests for all new components
|
||||
|
||||
---
|
||||
|
||||
## 3. Backend Code Quality
|
||||
|
||||
### Formatting (CRITICAL)
|
||||
```bash
|
||||
make format_backend # Format Python code
|
||||
```
|
||||
**Important:** Run `make format_backend` _early and often_ (ideally before running linting or committing changes). It auto-corrects the majority of style issues, preventing lengthy manual fixes when lint errors surface later.
|
||||
|
||||
### Linting
|
||||
```bash
|
||||
make lint # Run linting checks
|
||||
```
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
make unit_tests # Run backend unit tests
|
||||
```
|
||||
|
||||
### Pre-commit Workflow
|
||||
1. **Run `make format_backend`** (FIRST - saves time on lint fixes)
|
||||
2. Run `make lint`
|
||||
3. Run `make unit_tests`
|
||||
4. Commit changes
|
||||
|
||||
---
|
||||
|
||||
## 4. FastAPI Development
|
||||
|
||||
### API Structure
|
||||
```
|
||||
src/backend/base/langflow/api/
|
||||
├── v1/ # API version 1
|
||||
│ ├── chat.py # Chat endpoints
|
||||
│ ├── flows.py # Flow management
|
||||
│ ├── users.py # User management
|
||||
│ └── ...
|
||||
└── v2/ # API version 2 (future)
|
||||
```
|
||||
|
||||
### Testing APIs
|
||||
- Use `client` fixture from `conftest.py`
|
||||
- Test with `logged_in_headers` for authenticated endpoints
|
||||
- Example:
|
||||
```python
|
||||
async def test_flows_endpoint(client, logged_in_headers):
|
||||
response = await client.post(
|
||||
"api/v1/flows/",
|
||||
json=flow_data,
|
||||
headers=logged_in_headers
|
||||
)
|
||||
assert response.status_code == 201
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Database Development
|
||||
|
||||
### Models Location
|
||||
```
|
||||
src/backend/base/langflow/services/database/models/
|
||||
├── api_key/ # API key models
|
||||
├── flow/ # Flow models
|
||||
├── folder/ # Folder models
|
||||
├── user/ # User models
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Database Testing
|
||||
- Use in-memory SQLite for tests
|
||||
- Database tests may fail in batch runs - run individually if needed:
|
||||
```bash
|
||||
uv run pytest src/backend/tests/unit/test_database.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Async Development Patterns
|
||||
|
||||
### Component Async Methods
|
||||
```python
|
||||
async def run(self) -> MessageType:
|
||||
"""Main component execution method."""
|
||||
# Use await for async operations
|
||||
result = await self.async_operation()
|
||||
return result
|
||||
|
||||
async def message_response(self) -> Message:
|
||||
"""Return a Message object for chat components."""
|
||||
return Message(
|
||||
text=self.input_value,
|
||||
sender=self.sender,
|
||||
session_id=self.session_id,
|
||||
)
|
||||
```
|
||||
|
||||
### Background Tasks
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
async def process_in_background(self):
|
||||
"""Process items without blocking."""
|
||||
# Use asyncio.create_task for background work
|
||||
task = asyncio.create_task(self.heavy_operation())
|
||||
|
||||
# Ensure proper cleanup
|
||||
try:
|
||||
result = await task
|
||||
return result
|
||||
except asyncio.CancelledError:
|
||||
# Handle cancellation gracefully
|
||||
await self.cleanup()
|
||||
raise
|
||||
```
|
||||
|
||||
### Queue Operations
|
||||
```python
|
||||
async def queue_processing(self):
|
||||
"""Non-blocking queue operations."""
|
||||
queue = asyncio.Queue()
|
||||
|
||||
# Non-blocking put
|
||||
queue.put_nowait(data)
|
||||
|
||||
# Timeout-controlled get
|
||||
try:
|
||||
result = await asyncio.wait_for(queue.get(), timeout=5.0)
|
||||
return result
|
||||
except asyncio.TimeoutError:
|
||||
# Handle timeout appropriately
|
||||
raise ComponentError("Processing timeout")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Component Integration Testing
|
||||
|
||||
### Flow Testing
|
||||
```python
|
||||
from tests.unit.build_utils import create_flow, build_flow, get_build_events
|
||||
|
||||
async def test_component_in_flow(client, json_flow, logged_in_headers):
|
||||
"""Test component within a complete flow."""
|
||||
flow_id = await create_flow(client, json_flow, logged_in_headers)
|
||||
build_response = await build_flow(client, flow_id, logged_in_headers)
|
||||
|
||||
# Validate flow execution
|
||||
job_id = build_response["job_id"]
|
||||
events_response = await get_build_events(client, job_id, logged_in_headers)
|
||||
assert events_response.status_code == 200
|
||||
```
|
||||
|
||||
### External API Testing
|
||||
```python
|
||||
@pytest.mark.api_key_required
|
||||
@pytest.mark.no_blockbuster
|
||||
async def test_with_real_api(self):
|
||||
"""Test component with external service."""
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
component = MyComponent(api_key=api_key, model="gpt-4o")
|
||||
|
||||
response = await component.run()
|
||||
assert response is not None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Known Backend Issues
|
||||
|
||||
### Testing Quirks
|
||||
- `test_database.py` may fail in batch runs but pass individually
|
||||
- Use `@pytest.mark.no_blockbuster` to skip blockbuster plugin when needed
|
||||
- Context variables may not propagate correctly in `asyncio.to_thread` - test both patterns
|
||||
|
||||
### File Changes
|
||||
- Starter project files auto-format after `langflow run`
|
||||
- These formatting changes can be committed or ignored
|
||||
|
||||
---
|
||||
|
||||
## Backend Development Checklist
|
||||
- [ ] Component added to appropriate subdirectory
|
||||
- [ ] `__init__.py` updated with alphabetical imports
|
||||
- [ ] Code formatted with `make format_backend` (FIRST)
|
||||
- [ ] Linting passed with `make lint`
|
||||
- [ ] Unit tests created and passing with `make unit_tests`
|
||||
- [ ] Component tested in UI with backend restart + browser refresh
|
||||
- [ ] Version mapping provided for backward compatibility
|
||||
- [ ] Async patterns implemented correctly with proper cleanup
|
||||
- [ ] External API calls use appropriate pytest markers
|
||||
@@ -1,82 +0,0 @@
|
||||
---
|
||||
description: "Guide for creating a basic Langflow component, including requirements gathering, component structure, and implementation best practices."
|
||||
globs:
|
||||
- "src/backend/**/components/**/*.py"
|
||||
- "src/backend/base/langflow/components/**/*.py"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
|
||||
# Rule: How to Create a Basic Langflow Component
|
||||
|
||||
## Purpose
|
||||
Guide for Creating a Langflow Component
|
||||
|
||||
---
|
||||
|
||||
### 1. Gather Requirements
|
||||
|
||||
Ask the user for:
|
||||
- **Component Name:** What should the component be called?
|
||||
- **Description:** What does the component do?
|
||||
- **Inputs:** What are the required inputs? (e.g., text, dropdown, boolean, etc.)
|
||||
- **Outputs:** What should the component output? (e.g., a message, a value, etc.)
|
||||
- **Category:** Which component category should this component be stored under in `langflow/src/backend/base/langflow/components`
|
||||
|
||||
### 2. Define the Component
|
||||
|
||||
- Inherit from `Component`.
|
||||
- Set `display_name`, `description`, `icon`.
|
||||
- Define the `inputs` and `outputs` as lists of input/output field objects (e.g., `DropdownInput`, `MessageTextInput`, `Output`).
|
||||
- Implement the main logic as a method (e.g., `get_current_date`, `true_response`, etc.).
|
||||
|
||||
### 3. Example: Conditional If-Else Component
|
||||
|
||||
```python
|
||||
class ConditionalRouterComponent(Component):
|
||||
display_name = "If-Else"
|
||||
description = "Routes an input message to a corresponding output based on text comparison."
|
||||
icon = "split"
|
||||
name = "ConditionalRouter"
|
||||
inputs = [
|
||||
# Define your inputs here
|
||||
]
|
||||
outputs = [
|
||||
# Define your outputs here
|
||||
]
|
||||
# Implement your logic methods here
|
||||
```
|
||||
|
||||
### 4. Example: Current Date Component
|
||||
|
||||
```python
|
||||
class CurrentDateComponent(Component):
|
||||
display_name = "Current Date"
|
||||
description = "Returns the current date and time in the selected timezone."
|
||||
icon = "clock"
|
||||
name = "CurrentDate"
|
||||
inputs = [
|
||||
# Define your inputs here
|
||||
]
|
||||
outputs = [
|
||||
# Define your outputs here
|
||||
]
|
||||
# Implement your logic methods here
|
||||
```
|
||||
|
||||
### 5. Best Practices
|
||||
|
||||
- Use clear and descriptive names for inputs and outputs.
|
||||
- Provide helpful `info` for each input to guide users.
|
||||
- Handle errors gracefully and provide meaningful error messages.
|
||||
- Use appropriate icons to visually represent the component's function.
|
||||
- Use a Lucide icon or if you want a custom icon follow the icon rules (`./cursor/rules/icons.mdc`)
|
||||
|
||||
---
|
||||
|
||||
## Checklist for Creating a Component
|
||||
- [ ] Ask the user for component name, description, inputs, and outputs.
|
||||
- [ ] Define the component class with the required fields.
|
||||
- [ ] Implement the main logic.
|
||||
- [ ] Add helpful info and error handling.
|
||||
- [ ] Test the component.
|
||||
@@ -1,443 +0,0 @@
|
||||
---
|
||||
description: "Guidelines for developing and maintaining Langflow documentation using Docusaurus, including content structure, style, and deployment processes."
|
||||
globs:
|
||||
- "docs/**/*.{md,mdx}"
|
||||
- "docs/**/*.{js,jsx,ts,tsx}"
|
||||
- "docs/package*.json"
|
||||
- "docs/docusaurus.config.js"
|
||||
- "docs/sidebars.js"
|
||||
- "docs/static/**/*"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
|
||||
# Documentation Development Guidelines
|
||||
|
||||
## Purpose
|
||||
Guidelines for developing and maintaining Langflow documentation using Docusaurus, including content structure, style, and deployment processes.
|
||||
|
||||
---
|
||||
|
||||
## 1. Documentation Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
- **Node.js:** v22.12 LTS for runtime
|
||||
- **Package Manager:** npm for dependency management
|
||||
- **Documentation Framework:** Docusaurus v3
|
||||
|
||||
### Documentation Service
|
||||
```bash
|
||||
cd docs
|
||||
npm install # Install dependencies
|
||||
npm run start # Start dev server (usually port 3001)
|
||||
```
|
||||
- Auto-reloads on documentation changes
|
||||
- Access at: http://localhost:3001/
|
||||
- Documentation source: `docs/`
|
||||
|
||||
---
|
||||
|
||||
## 2. Documentation Structure
|
||||
|
||||
### Directory Layout
|
||||
```
|
||||
docs/
|
||||
├── docs/ # Main documentation content
|
||||
│ ├── agents/ # Agent and MCP guides
|
||||
│ ├── get-started/ # Getting started guides
|
||||
│ ├── tutorials/ # Langflow tutorials
|
||||
│ ├── components/ # Component documentation
|
||||
│ ├── flows/ # Guides to build, run, and test flows
|
||||
│ ├── deployment/ # Guides for deploying and hosting a Langflow server
|
||||
│ ├── develop/ # Guides for developing apps with Langflow
|
||||
│ ├── support/ # Help and release notes
|
||||
│ ├── contributing/ # Contribution guidelines
|
||||
│ └── api-reference/ # API documentation
|
||||
├── src/ # Custom React components
|
||||
├── static/ # Static assets (images, etc.)
|
||||
├── sidebars.js # Sidebar configuration
|
||||
├── docusaurus.config.js # Main configuration
|
||||
└── package.json # Dependencies
|
||||
```
|
||||
|
||||
### Content Types
|
||||
- **Guides:** Step-by-step tutorials (`docs/getting-started/`)
|
||||
- **Reference:** API and component reference (`docs/api-reference/`)
|
||||
- **How-to:** Problem-solving articles (`docs/components/`)
|
||||
- **Concepts:** Explanatory articles about Langflow concepts
|
||||
- **Blog:** Release notes, announcements (`blog/`)
|
||||
|
||||
---
|
||||
|
||||
## 3. Writing Documentation
|
||||
|
||||
### Markdown Conventions
|
||||
```markdown
|
||||
---
|
||||
title: Page Title
|
||||
description: Brief description for SEO
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Page Title
|
||||
|
||||
Brief introduction paragraph.
|
||||
|
||||
## Section Header
|
||||
|
||||
Content with proper formatting.
|
||||
|
||||
### Subsection
|
||||
|
||||
More detailed content.
|
||||
|
||||
:::tip
|
||||
Use admonitions for important information.
|
||||
:::
|
||||
|
||||
:::warning
|
||||
Use warnings for potential issues.
|
||||
:::
|
||||
|
||||
:::danger
|
||||
Use danger for critical warnings.
|
||||
:::
|
||||
```
|
||||
|
||||
### Code Blocks
|
||||
````markdown
|
||||
```python title="component_example.py"
|
||||
from langflow.components.base import Component
|
||||
|
||||
class MyComponent(Component):
|
||||
display_name = "My Component"
|
||||
description = "Example component"
|
||||
|
||||
def run(self):
|
||||
return "Hello, World!"
|
||||
```
|
||||
````
|
||||
|
||||
### Images and Assets
|
||||
```markdown
|
||||
<!-- Images go in static/img/ -->
|
||||

|
||||
|
||||
<!-- Use descriptive alt text -->
|
||||

|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Component Documentation
|
||||
|
||||
### Component Page Template
|
||||
```markdown
|
||||
---
|
||||
title: Component Name
|
||||
description: Brief description of what the component does
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Component Name
|
||||
|
||||
Brief overview of the component's purpose.
|
||||
|
||||
## Overview
|
||||
|
||||
What this component does and when to use it.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Inputs
|
||||
|
||||
| Input | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `input_text` | String | Yes | The text to process |
|
||||
| `model_name` | String | No | Model to use (default: gpt-3.5-turbo) |
|
||||
|
||||
### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `result` | Message | Processed result |
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
# Example of using the component
|
||||
component = MyComponent(
|
||||
input_text="Hello, world!",
|
||||
model_name="gpt-4"
|
||||
)
|
||||
result = component.run()
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Issue: Component not loading
|
||||
|
||||
**Solution:** Check that all required inputs are provided.
|
||||
|
||||
### Issue: API key errors
|
||||
|
||||
**Solution:** Ensure your API key is properly configured.
|
||||
```
|
||||
|
||||
### API Documentation
|
||||
```markdown
|
||||
---
|
||||
title: API Endpoint
|
||||
description: REST API endpoint documentation
|
||||
---
|
||||
|
||||
# API Endpoint Name
|
||||
|
||||
## Endpoint
|
||||
|
||||
`POST /api/v1/endpoint`
|
||||
|
||||
## Request
|
||||
|
||||
### Headers
|
||||
```json
|
||||
{
|
||||
"Authorization": "Bearer <token>",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
```
|
||||
|
||||
### Body
|
||||
```json
|
||||
{
|
||||
"parameter": "value",
|
||||
"optional_param": "optional_value"
|
||||
}
|
||||
```
|
||||
|
||||
## Response
|
||||
|
||||
### Success (200)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"result": "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error (400)
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Error message"
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:7860/api/v1/endpoint \
|
||||
-H "Authorization: Bearer your-token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"parameter": "value"}'
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Blog Posts and Announcements
|
||||
|
||||
### Blog Post Template
|
||||
```markdown
|
||||
---
|
||||
title: "Release: Langflow v1.1.0"
|
||||
description: "New features and improvements in Langflow v1.1.0"
|
||||
authors: [author-name]
|
||||
date: 2024-01-15
|
||||
tags: [release, features]
|
||||
---
|
||||
|
||||
# Release: Langflow v1.1.0
|
||||
|
||||
Brief introduction to the release.
|
||||
|
||||
## New Features
|
||||
|
||||
### Feature 1
|
||||
Description of the feature and how to use it.
|
||||
|
||||
### Feature 2
|
||||
Another feature description.
|
||||
|
||||
## Improvements
|
||||
|
||||
- List of improvements
|
||||
- Bug fixes
|
||||
- Performance enhancements
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
:::warning
|
||||
List any breaking changes that require user action.
|
||||
:::
|
||||
|
||||
## Migration Guide
|
||||
|
||||
Steps to migrate from previous versions.
|
||||
```
|
||||
|
||||
### Announcement Posts
|
||||
```markdown
|
||||
---
|
||||
title: "Announcement: New Integration"
|
||||
description: "Langflow now supports integration with XYZ service"
|
||||
authors: [author-name]
|
||||
date: 2024-01-15
|
||||
tags: [announcement, integration]
|
||||
---
|
||||
|
||||
Brief announcement content with clear call-to-action.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Configuration
|
||||
|
||||
### Sidebar Configuration (`sidebars.js`)
|
||||
```javascript
|
||||
module.exports = {
|
||||
docs: [
|
||||
'introduction',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Getting Started',
|
||||
items: [
|
||||
'getting-started/installation',
|
||||
'getting-started/quickstart',
|
||||
'getting-started/first-flow',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Components',
|
||||
items: [
|
||||
'components/overview',
|
||||
'components/inputs',
|
||||
'components/outputs',
|
||||
'components/processing',
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### Site Configuration (`docusaurus.config.js`)
|
||||
```javascript
|
||||
module.exports = {
|
||||
title: 'Langflow Documentation',
|
||||
tagline: 'Build AI flows visually',
|
||||
url: 'https://docs.langflow.org',
|
||||
baseUrl: '/',
|
||||
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
title: 'Langflow',
|
||||
logo: {
|
||||
alt: 'Langflow Logo',
|
||||
src: 'img/logo.svg',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
docId: 'introduction',
|
||||
position: 'left',
|
||||
label: 'Docs',
|
||||
},
|
||||
{
|
||||
to: '/blog',
|
||||
label: 'Blog',
|
||||
position: 'left'
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Documentation Testing
|
||||
|
||||
### Link Checking
|
||||
```bash
|
||||
# Check for broken internal links
|
||||
npm run build
|
||||
npm run serve
|
||||
# Manual testing or use link checker tools
|
||||
```
|
||||
|
||||
### Content Review
|
||||
- **Accuracy:** Verify all code examples work
|
||||
- **Completeness:** Ensure all features are documented
|
||||
- **Clarity:** Review for clear, concise language
|
||||
- **Navigation:** Test sidebar and cross-references
|
||||
|
||||
### Screenshots
|
||||
- Keep screenshots up-to-date with current UI
|
||||
- Use consistent browser/OS for screenshots
|
||||
- Highlight relevant UI elements
|
||||
- Use descriptive file names
|
||||
|
||||
---
|
||||
|
||||
## 8. Style Guide
|
||||
|
||||
### Writing Style
|
||||
- **Tone:** Professional but approachable
|
||||
- **Voice:** Second person ("you") for instructions
|
||||
- **Tense:** Present tense for current features
|
||||
- **Length:** Keep paragraphs short and scannable
|
||||
|
||||
### Formatting
|
||||
- **Headers:** Use sentence case
|
||||
- **Code:** Inline code with `backticks`
|
||||
- **Emphasis:** Use **bold** for UI elements, *italic* for emphasis
|
||||
- **Lists:** Use parallel structure
|
||||
|
||||
### Terminology
|
||||
- **Langflow:** Always capitalize
|
||||
- **Component:** Capitalize when referring to Langflow components
|
||||
- **Flow:** Capitalize when referring to Langflow flows
|
||||
- **API:** Always uppercase
|
||||
- **JSON:** Always uppercase
|
||||
|
||||
---
|
||||
|
||||
## 9. Deployment
|
||||
|
||||
### Local Testing
|
||||
```bash
|
||||
npm run build # Build static site
|
||||
npm run serve # Serve built site locally
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
- Documentation is automatically deployed on commit to main branch
|
||||
- Build artifacts go to `build/` directory
|
||||
- Static site is served via CDN
|
||||
|
||||
---
|
||||
|
||||
## Documentation Development Checklist
|
||||
- [ ] Documentation service running with `npm run start`
|
||||
- [ ] Content follows markdown conventions
|
||||
- [ ] Code examples are tested and working
|
||||
- [ ] Images have descriptive alt text
|
||||
- [ ] Internal links are functional
|
||||
- [ ] Sidebar navigation is updated
|
||||
- [ ] Content follows style guide
|
||||
- [ ] Screenshots are current and properly formatted
|
||||
- [ ] Cross-references between related topics
|
||||
- [ ] Build succeeds with `npm run build`
|
||||
@@ -1,374 +0,0 @@
|
||||
---
|
||||
description: "Guidelines for frontend development in Langflow, focusing on React/TypeScript UI components, build processes, and frontend testing."
|
||||
globs:
|
||||
- "src/frontend/**/*.{ts,tsx,js,jsx}"
|
||||
- "src/frontend/**/*.{css,scss,json}"
|
||||
- "src/frontend/package*.json"
|
||||
- "src/frontend/vite.config.*"
|
||||
- "src/frontend/tailwind.config.*"
|
||||
- "src/frontend/tsconfig.json"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
## 1. Frontend Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
- **Node.js:** v22.12 LTS for JavaScript runtime
|
||||
- **Package Manager:** npm (v10.9) for dependency management
|
||||
- **Development Tools:** Vite for build tooling
|
||||
|
||||
### Frontend Service
|
||||
```bash
|
||||
make frontend # Start Vite dev server on port 3000
|
||||
```
|
||||
- Hot-reload enabled for UI changes
|
||||
- Access at: http://localhost:3000/
|
||||
- Frontend source: `src/frontend/`
|
||||
|
||||
---
|
||||
|
||||
## 2. Frontend Structure
|
||||
|
||||
### Directory Layout
|
||||
```
|
||||
src/frontend/src/
|
||||
├── components/ # Reusable UI components
|
||||
├── pages/ # Page-level components
|
||||
├── icons/ # Component icons and lazy loading
|
||||
├── stores/ # State management (Zustand)
|
||||
├── types/ # TypeScript type definitions
|
||||
├── utils/ # Utility functions
|
||||
├── hooks/ # Custom React hooks
|
||||
├── services/ # API service functions
|
||||
└── assets/ # Static assets
|
||||
```
|
||||
|
||||
### Key Technologies
|
||||
- **React 18** with TypeScript
|
||||
- **Vite** for build tooling and dev server
|
||||
- **Tailwind CSS** for styling
|
||||
- **Zustand** for state management
|
||||
- **React Flow** for flow graph visualization
|
||||
- **Lucide React** for icons
|
||||
|
||||
---
|
||||
|
||||
## 3. Frontend Code Quality
|
||||
|
||||
### Formatting
|
||||
```bash
|
||||
make format_frontend # Format TypeScript/JavaScript code
|
||||
```
|
||||
|
||||
### Linting
|
||||
```bash
|
||||
make lint # Run ESLint and TypeScript checks
|
||||
```
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
make tests_frontend # Run frontend tests (requires additional setup)
|
||||
```
|
||||
|
||||
### Pre-commit Workflow
|
||||
1. Run `make format_frontend`
|
||||
2. Run `make lint`
|
||||
3. Test changes in browser
|
||||
4. Commit changes
|
||||
|
||||
---
|
||||
|
||||
## 4. State Management
|
||||
|
||||
### Zustand Stores
|
||||
```typescript
|
||||
// stores/myStore.ts
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface MyState {
|
||||
value: string;
|
||||
setValue: (value: string) => void;
|
||||
}
|
||||
|
||||
export const useMyStore = create<MyState>((set) => ({
|
||||
value: '',
|
||||
setValue: (value) => set({ value }),
|
||||
}));
|
||||
```
|
||||
|
||||
### Using Stores in Components
|
||||
```typescript
|
||||
// components/MyComponent.tsx
|
||||
import { useMyStore } from '@/stores/myStore';
|
||||
|
||||
export function MyComponent() {
|
||||
const { value, setValue } = useMyStore();
|
||||
|
||||
return (
|
||||
<input
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API Integration
|
||||
|
||||
### Service Functions
|
||||
```typescript
|
||||
// services/api.ts
|
||||
import { api } from '@/controllers/API';
|
||||
|
||||
export async function createFlow(flowData: FlowData) {
|
||||
const response = await api.post('/flows/', flowData);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getFlows() {
|
||||
const response = await api.get('/flows/');
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```typescript
|
||||
// hooks/useApi.ts
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
export function useApi<T>(apiFunction: (...args: any[]) => Promise<T>) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const execute = useCallback(async (...args: any[]) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const result = await apiFunction(...args);
|
||||
return result;
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [apiFunction]);
|
||||
|
||||
return { execute, loading, error };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. React Flow Integration
|
||||
|
||||
### Flow Graph Components
|
||||
```typescript
|
||||
// components/FlowGraph.tsx
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
Edge,
|
||||
Controls,
|
||||
Background
|
||||
} from 'reactflow';
|
||||
|
||||
interface FlowGraphProps {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
onNodesChange: (changes: NodeChange[]) => void;
|
||||
onEdgesChange: (changes: EdgeChange[]) => void;
|
||||
}
|
||||
|
||||
export function FlowGraph({
|
||||
nodes,
|
||||
edges,
|
||||
onNodesChange,
|
||||
onEdgesChange
|
||||
}: FlowGraphProps) {
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
fitView
|
||||
>
|
||||
<Controls />
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Node Types
|
||||
```typescript
|
||||
// components/nodes/ComponentNode.tsx
|
||||
import { memo } from 'react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
interface ComponentNodeProps {
|
||||
data: {
|
||||
label: string;
|
||||
icon?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const ComponentNode = memo(({ data }: ComponentNodeProps) => {
|
||||
return (
|
||||
<div className="px-4 py-2 shadow-md rounded-md bg-white border">
|
||||
<Handle type="target" position={Position.Top} />
|
||||
|
||||
<div className="flex items-center">
|
||||
{data.icon && (
|
||||
<img src={data.icon} alt="" className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
<span className="text-sm">{data.label}</span>
|
||||
</div>
|
||||
|
||||
<Handle type="source" position={Position.Bottom} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Styling with Tailwind
|
||||
|
||||
### Component Styling
|
||||
```typescript
|
||||
// components/Button.tsx
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
interface ButtonProps {
|
||||
variant?: 'primary' | 'secondary';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export function Button({
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
children,
|
||||
onClick
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
'rounded-md font-medium transition-colors',
|
||||
{
|
||||
'bg-blue-600 hover:bg-blue-700 text-white': variant === 'primary',
|
||||
'bg-gray-200 hover:bg-gray-300 text-gray-900': variant === 'secondary',
|
||||
'px-2 py-1 text-sm': size === 'sm',
|
||||
'px-4 py-2 text-base': size === 'md',
|
||||
'px-6 py-3 text-lg': size === 'lg',
|
||||
}
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Dark Mode Support
|
||||
```typescript
|
||||
// hooks/useDarkMode.ts
|
||||
import { useDarkStore } from '@/stores/darkStore';
|
||||
|
||||
export function useDarkMode() {
|
||||
const { dark, setDark } = useDarkStore();
|
||||
|
||||
const toggle = () => setDark(!dark);
|
||||
|
||||
return { isDark: dark, toggle };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Frontend Testing
|
||||
|
||||
### Component Testing
|
||||
```typescript
|
||||
// __tests__/Button.test.tsx
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { Button } from '@/components/Button';
|
||||
|
||||
describe('Button', () => {
|
||||
it('renders with correct text', () => {
|
||||
render(<Button>Click me</Button>);
|
||||
expect(screen.getByText('Click me')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onClick when clicked', () => {
|
||||
const handleClick = jest.fn();
|
||||
render(<Button onClick={handleClick}>Click me</Button>);
|
||||
|
||||
fireEvent.click(screen.getByText('Click me'));
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
```typescript
|
||||
// __tests__/FlowEditor.test.tsx
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { FlowEditor } from '@/pages/FlowEditor';
|
||||
|
||||
describe('FlowEditor', () => {
|
||||
it('loads flow data correctly', async () => {
|
||||
render(<FlowEditor flowId="test-flow-id" />);
|
||||
|
||||
// Wait for flow to load
|
||||
await screen.findByText('Flow loaded');
|
||||
|
||||
expect(screen.getByTestId('flow-canvas')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Build and Deployment
|
||||
|
||||
### Development Build
|
||||
```bash
|
||||
make build_frontend # Build frontend static files
|
||||
```
|
||||
|
||||
### Production Build
|
||||
```bash
|
||||
cd src/frontend
|
||||
npm run build # Creates dist/ directory
|
||||
```
|
||||
|
||||
### Build Integration
|
||||
- Frontend builds to `src/frontend/dist/`
|
||||
- Backend serves static files from this directory in production
|
||||
- `make init` includes frontend build step
|
||||
---
|
||||
|
||||
## Frontend Development Checklist
|
||||
- [ ] Frontend service running with `make frontend`
|
||||
- [ ] Changes hot-reload correctly in browser
|
||||
- [ ] State management uses Zustand stores
|
||||
- [ ] API calls use proper error handling
|
||||
- [ ] Components styled with Tailwind CSS
|
||||
- [ ] Dark mode support implemented where needed
|
||||
- [ ] Code formatted with `make format_frontend`
|
||||
- [ ] Linting passed with `make lint`
|
||||
- [ ] Changes tested in both light and dark mode
|
||||
|
||||
- [ ] Components styled with Tailwind CSS
|
||||
- [ ] Dark mode support implemented where needed
|
||||
- [ ] Code formatted with `make format_frontend`
|
||||
- [ ] Changes tested in both light and dark mode
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
---
|
||||
description: "Rules for creating and implementing component icons in Langflow, covering both backend Python component icon attributes and frontend React icon implementation."
|
||||
globs:
|
||||
- "src/frontend/src/icons/**/*"
|
||||
- "src/frontend/src/icons/lazyIconImports.ts"
|
||||
- "src/backend/**/*component*.py"
|
||||
- "src/backend/**/components/**/*.py"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Component Icon Rules
|
||||
|
||||
## Purpose
|
||||
|
||||
To ensure consistent, clear, and functional icon usage for components, covering both backend (Python) and frontend (React/TypeScript) steps.
|
||||
|
||||
---
|
||||
|
||||
## 1. Backend (Python) — Setting the Icon Name
|
||||
|
||||
- **Where:** In your component class (e.g., in `src/backend/base/langflow/components/vectorstores/astradb.py`)
|
||||
- **How:**
|
||||
Set the `icon` attribute to a string matching the icon you want to use.
|
||||
```python
|
||||
icon = "AstraDB"
|
||||
```
|
||||
- **Tip:**
|
||||
The string must match the frontend icon mapping exactly (case-sensitive).
|
||||
|
||||
---
|
||||
|
||||
## 2. Frontend (React/TypeScript) — Adding the Icon
|
||||
|
||||
### a. Create the Icon Component
|
||||
|
||||
- **Where:**
|
||||
In a new directory for your icon, e.g., `src/frontend/src/icons/AstraDB/`.
|
||||
- **How:**
|
||||
|
||||
- Add your SVG as a React component, e.g., `AstraSVG` in `AstraDB.jsx`.
|
||||
```jsx
|
||||
const AstraSVG = (props) => (
|
||||
<svg {...props}>
|
||||
<path
|
||||
// ...
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
```
|
||||
- Create an `index.tsx` that exports your icon using `forwardRef`:
|
||||
|
||||
```tsx
|
||||
import React, { forwardRef } from "react";
|
||||
import AstraSVG from "./AstraDB";
|
||||
|
||||
export const AstraDBIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
>((props, ref) => {
|
||||
return <AstraSVG ref={ref} isDark={isDark} {...props} />;
|
||||
});
|
||||
```
|
||||
|
||||
#### Supporting Light and Dark Mode Icons
|
||||
|
||||
- **How:**
|
||||
- In your SVG component (e.g., `AstraDB.jsx`), use the `isDark` prop to switch colors:
|
||||
```jsx
|
||||
const AstraSVG = (props) => (
|
||||
<svg {...props}>
|
||||
<path
|
||||
fill={props.isDark ? "#ffffff" : "#0A0A0A"}
|
||||
// ...
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
```
|
||||
- The `isDark` prop is passed from the icon wrapper (see above) and should be used to toggle between light and dark color schemes.
|
||||
- You can use a utility like `stringToBool` to ensure the prop is interpreted correctly.
|
||||
|
||||
### b. Add to Lazy Icon Imports
|
||||
|
||||
- **Where:**
|
||||
In `src/frontend/src/icons/lazyIconImports.ts`
|
||||
- **How:**
|
||||
Add an entry to the `lazyIconsMapping` object:
|
||||
```ts
|
||||
AstraDB: () =>
|
||||
import("@/icons/AstraDB").then((mod) => ({ default: mod.AstraDBIcon })),
|
||||
```
|
||||
- **Tip:**
|
||||
The key (`AstraDB`) must match the string used in the backend.
|
||||
|
||||
---
|
||||
|
||||
## 3. Best Practices
|
||||
|
||||
- **Naming:**
|
||||
Use clear, recognizable names (e.g., `"AstraDB"`, `"Postgres"`, `"OpenAI"`).
|
||||
- **Consistency:**
|
||||
Always use the same icon name for the same service across backend and frontend.
|
||||
- **Missing Icon:**
|
||||
If no icon exists, use a [lucide icon](https://lucide.dev/icons)
|
||||
- **Light/Dark Mode:**
|
||||
Always support both light and dark mode for custom icons by using the `isDark` prop in your SVG.
|
||||
|
||||
---
|
||||
|
||||
## 4. Checklist for Adding a New Icon
|
||||
|
||||
- [ ] Decide on a clear, descriptive icon name (e.g., `AstraDB`).
|
||||
- [ ] In your Python component, set `icon = "YourIconName"`.
|
||||
- [ ] Create a new icon directory in `src/frontend/src/icons/YourIconName/`.
|
||||
- [ ] Add your SVG as a React component (e.g., `YourIconNameIcon.jsx`).
|
||||
- [ ] Create an `index.tsx` that exports your icon using `forwardRef` and passes the `isDark` prop.
|
||||
- [ ] Add your icon to `lazyIconsMapping` in `src/frontend/src/icons/lazyIconImports.ts` with the exact same name.
|
||||
- [ ] Verify the icon appears correctly in the UI in both light and dark mode.
|
||||
- [ ] If no suitable icon exists, use a generic icon and request a new one if needed.
|
||||
|
||||
---
|
||||
|
||||
**Example for AstraDB:**
|
||||
|
||||
- Backend:
|
||||
```python
|
||||
icon = "AstraDB"
|
||||
```
|
||||
- Frontend:
|
||||
- `src/icons/AstraDB/AstraDB.jsx` (SVG as React component, uses `isDark` prop)
|
||||
- `src/icons/AstraDB/index.tsx` (exports `AstraDBIcon` and passes `isDark`)
|
||||
- Add to `lazyIconImports.ts`:
|
||||
```ts
|
||||
AstraDB: () =>
|
||||
import("@/icons/AstraDB").then((mod) => ({ default: mod.AstraDBIcon })),
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,561 +0,0 @@
|
||||
---
|
||||
description: "Guidelines for testing Langflow components and backend code, with emphasis on async patterns and robust, well-documented testing practices."
|
||||
globs:
|
||||
- "src/backend/tests/**/*.py"
|
||||
- "src/frontend/**/*.test.{ts,tsx,js,jsx}"
|
||||
- "src/frontend/**/*.spec.{ts,tsx,js,jsx}"
|
||||
- "tests/**/*.py"
|
||||
- "conftest.py"
|
||||
- "pytest.ini"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Testing Guidelines for Langflow
|
||||
|
||||
## Purpose
|
||||
Guidelines for testing Langflow components and backend code, with emphasis on async patterns and robust, well-documented testing practices.
|
||||
|
||||
---
|
||||
|
||||
## 1. Testing Structure
|
||||
|
||||
### Backend Tests Location
|
||||
- **Unit Tests:** `src/backend/tests/`
|
||||
- **Component Tests:** `src/backend/tests/unit/components/` (organized by component subdirectory)
|
||||
- **Integration Tests:** Available via `make integration_tests` (requires additional setup)
|
||||
|
||||
### Test File Naming
|
||||
- Use same filename as component with appropriate test prefix/suffix
|
||||
- Example: `my_component.py` → `test_my_component.py`
|
||||
|
||||
---
|
||||
|
||||
## 2. Built-in Fixtures & Base Classes
|
||||
|
||||
### `client` Fixture (FastAPI Test Client)
|
||||
- Defined in `src/backend/tests/conftest.py`
|
||||
- Provides an **async** `httpx.AsyncClient` connected to the full application via `ASGITransport` + `LifespanManager`.
|
||||
- Use it for API tests:
|
||||
```python
|
||||
async def test_login_endpoint(client):
|
||||
response = await client.post("api/v1/login", data={"username": "foo", "password": "bar"})
|
||||
assert response.status_code == 200
|
||||
```
|
||||
- Automatically configured with an **in-memory SQLite database** and mocked environment variables. No additional setup needed in individual tests.
|
||||
- Skip client creation by marking the test with `@pytest.mark.noclient`.
|
||||
|
||||
### `ComponentTestBase` Family
|
||||
Located in `src/backend/tests/base.py`.
|
||||
|
||||
| Base Class | Creates `client`? | Typical Use | Notes |
|
||||
|------------|------------------|-------------|-------|
|
||||
| `ComponentTestBase` | No | Core logic for component version testing | Requires you to provide fixtures described below. |
|
||||
| `ComponentTestBaseWithClient` | Yes (`@pytest.mark.usefixtures("client")`) | Components that need API access during `run()` | Inherit when the component interacts with backend services. |
|
||||
| `ComponentTestBaseWithoutClient` | No | Pure-logic components with no API calls | Lightweight alternative. |
|
||||
|
||||
Required fixtures for subclasses:
|
||||
1. `component_class` → the component **class** under test.
|
||||
2. `default_kwargs` → dict of kwargs to instantiate the component (can be empty).
|
||||
3. `file_names_mapping` → list of `VersionComponentMapping` dicts mapping historical **Langflow versions** to module/file names.
|
||||
|
||||
Example skeleton:
|
||||
```python
|
||||
from tests.base import ComponentTestBaseWithClient, VersionComponentMapping, DID_NOT_EXIST
|
||||
from langflow.components.my_namespace import MyComponent
|
||||
|
||||
class TestMyComponent(ComponentTestBaseWithClient):
|
||||
@pytest.fixture
|
||||
def component_class(self):
|
||||
return MyComponent
|
||||
|
||||
@pytest.fixture
|
||||
def default_kwargs(self):
|
||||
return {"foo": "bar"}
|
||||
|
||||
@pytest.fixture
|
||||
def file_names_mapping(self):
|
||||
return [
|
||||
VersionComponentMapping(version="1.1.1", module="my_module", file_name="my_component.py"),
|
||||
# Older versions can be mapped or DID_NOT_EXIST
|
||||
VersionComponentMapping(version="1.0.19", module="my_module", file_name=DID_NOT_EXIST),
|
||||
]
|
||||
```
|
||||
|
||||
`ComponentTestBase` automatically provides:
|
||||
- `test_latest_version` → Instantiates component via `component_class` and asserts `run()` doesn't return `None`.
|
||||
- `test_all_versions_have_a_file_name_defined` → Ensures mapping completeness vs `SUPPORTED_VERSIONS` constant (`src/backend/tests/constants.py`).
|
||||
- `test_component_versions` (parametrised) → Builds component from source for each supported version and asserts successful execution.
|
||||
|
||||
When adding a new component test, **inherit from the correct base class and provide the three fixtures**. This greatly reduces boilerplate and enforces version compatibility.
|
||||
|
||||
---
|
||||
|
||||
## 3. Component Testing Requirements
|
||||
|
||||
### Minimum Testing Requirements
|
||||
- **Unit Tests:** Create comprehensive unit tests for all new components
|
||||
- **Manual Test Documentation:** If unit tests are incomplete, create a Markdown file with manual testing steps
|
||||
- Location: Same directory as unit tests
|
||||
- Filename: Same as component but with `.md` extension
|
||||
- Content: Detailed manual testing steps and expected outcomes
|
||||
|
||||
### Testing Best Practices
|
||||
- Test both sync and async code paths
|
||||
- Mock external dependencies appropriately
|
||||
- Test error handling and edge cases
|
||||
- Validate input/output behavior
|
||||
- Test component initialization and configuration
|
||||
|
||||
---
|
||||
|
||||
## 4. Async Testing Patterns
|
||||
|
||||
### Async Component Testing
|
||||
```python
|
||||
import pytest
|
||||
import asyncio
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_component():
|
||||
# Test async component methods
|
||||
result = await component.async_method()
|
||||
assert result is not None
|
||||
```
|
||||
|
||||
### Testing Background Tasks
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_background_task_completion():
|
||||
# Ensure background tasks complete properly
|
||||
task = asyncio.create_task(component.background_operation())
|
||||
result = await asyncio.wait_for(task, timeout=5.0)
|
||||
assert result.success
|
||||
```
|
||||
|
||||
### Testing Queue Operations
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_queue_operations():
|
||||
# Test queue put/get operations without blocking
|
||||
queue = asyncio.Queue()
|
||||
|
||||
# Non-blocking put
|
||||
queue.put_nowait(test_data)
|
||||
|
||||
# Verify queue processing
|
||||
result = await asyncio.wait_for(queue.get(), timeout=1.0)
|
||||
assert result == test_data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Special Testing Considerations
|
||||
|
||||
### Blockbuster Plugin
|
||||
- Use `no_blockbuster` pytest marker to skip blockbuster plugin in tests
|
||||
- Example: `@pytest.mark.no_blockbuster`
|
||||
|
||||
### Database Tests
|
||||
- `test_database.py` may fail in batch runs but pass individually
|
||||
- Run problematic tests sequentially: `uv run pytest src/backend/tests/unit/test_database.py`
|
||||
- Consider this behavior when writing database-related tests
|
||||
|
||||
### Context Variables in Async Tests
|
||||
- Be aware of ContextVar propagation in async tests
|
||||
- Test both direct event loop execution and `asyncio.to_thread` scenarios
|
||||
- Ensure proper context isolation between test cases
|
||||
|
||||
---
|
||||
|
||||
## 6. Test Execution
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
make unit_tests # Run all backend unit tests
|
||||
make integration_tests # Run integration tests (requires additional setup)
|
||||
make coverage # Run tests with coverage (requires additional setup)
|
||||
make tests_frontend # Run frontend tests (requires additional setup)
|
||||
```
|
||||
|
||||
### Individual Test Execution
|
||||
```bash
|
||||
# Run specific test file
|
||||
uv run pytest src/backend/tests/unit/test_specific_component.py
|
||||
|
||||
# Run specific test method
|
||||
uv run pytest src/backend/tests/unit/test_component.py::test_specific_method
|
||||
|
||||
# Run with verbose output
|
||||
uv run pytest -v src/backend/tests/unit/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Robust Testing Patterns
|
||||
|
||||
### Error Handling Tests
|
||||
```python
|
||||
def test_component_error_handling():
|
||||
"""Test that component handles errors gracefully."""
|
||||
with pytest.raises(ExpectedError):
|
||||
component.method_that_should_fail(invalid_input)
|
||||
|
||||
# Test error message quality
|
||||
try:
|
||||
component.risky_operation()
|
||||
except ComponentError as e:
|
||||
assert "helpful error message" in str(e)
|
||||
```
|
||||
|
||||
### Resource Cleanup Tests
|
||||
```python
|
||||
@pytest.fixture
|
||||
async def component_with_cleanup():
|
||||
"""Fixture ensuring proper resource cleanup."""
|
||||
component = MyComponent()
|
||||
await component.initialize()
|
||||
try:
|
||||
yield component
|
||||
finally:
|
||||
await component.cleanup()
|
||||
```
|
||||
|
||||
### Timeout and Performance Tests
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_operation_timeout():
|
||||
"""Test that operations respect timeout constraints."""
|
||||
start_time = time.time()
|
||||
|
||||
with pytest.raises(asyncio.TimeoutError):
|
||||
await asyncio.wait_for(
|
||||
component.long_running_operation(),
|
||||
timeout=1.0
|
||||
)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
assert elapsed < 1.5 # Allow some tolerance
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Documentation in Tests
|
||||
|
||||
### Test Documentation Standards
|
||||
- Each test should have a clear docstring explaining its purpose
|
||||
- Complex test setups should be commented
|
||||
- Mock usage should be documented
|
||||
- Expected behaviors should be explicitly stated
|
||||
|
||||
### Example Well-Documented Test
|
||||
```python
|
||||
async def test_component_background_processing():
|
||||
"""
|
||||
Test that component processes items in background without blocking.
|
||||
|
||||
This test verifies:
|
||||
1. Items are added to processing queue immediately
|
||||
2. Background processing completes successfully
|
||||
3. No deadlocks occur during shutdown
|
||||
4. All tasks are properly cleaned up
|
||||
"""
|
||||
# Setup component with background processing
|
||||
component = BackgroundComponent()
|
||||
await component.start()
|
||||
|
||||
try:
|
||||
# Add items for processing
|
||||
for i in range(10):
|
||||
await component.add_item(f"item_{i}")
|
||||
|
||||
# Verify items are queued (non-blocking)
|
||||
assert component.queue_size() == 10
|
||||
|
||||
# Wait for processing to complete
|
||||
await component.wait_for_completion(timeout=5.0)
|
||||
|
||||
# Verify all items processed
|
||||
assert component.processed_count() == 10
|
||||
|
||||
finally:
|
||||
# Ensure clean shutdown
|
||||
await component.stop()
|
||||
assert component.is_stopped()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
- [ ] Unit tests created for all new components
|
||||
- [ ] Async patterns tested appropriately
|
||||
- [ ] Error handling and edge cases covered
|
||||
- [ ] Manual testing documentation created (if unit tests incomplete)
|
||||
- [ ] Background tasks and queues tested for proper cleanup
|
||||
- [ ] Database tests considered for sequential execution if needed
|
||||
- [ ] Appropriate pytest markers used (`no_blockbuster`, etc.)
|
||||
- [ ] Tests are well-documented with clear docstrings
|
||||
- [ ] Resource cleanup properly handled in fixtures
|
||||
- [ ] Timeout and performance constraints validated
|
||||
|
||||
---
|
||||
|
||||
## 9. Langflow-Specific Testing Patterns
|
||||
|
||||
### Message Testing
|
||||
Test Langflow's `Message` objects and chat functionality:
|
||||
|
||||
```python
|
||||
from langflow.schema.message import Message
|
||||
from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER
|
||||
|
||||
async def test_message_response(self, component_class, default_kwargs):
|
||||
"""Test Message object creation and properties."""
|
||||
component = component_class(**default_kwargs)
|
||||
message = await component.message_response()
|
||||
|
||||
assert isinstance(message, Message)
|
||||
assert message.text == default_kwargs["input_value"]
|
||||
assert message.sender == default_kwargs["sender"]
|
||||
assert message.sender_name == default_kwargs["sender_name"]
|
||||
assert message.session_id == default_kwargs["session_id"]
|
||||
assert message.files == default_kwargs["files"]
|
||||
# Test message properties structure
|
||||
assert message.properties.model_dump() == {
|
||||
"background_color": default_kwargs["background_color"],
|
||||
"text_color": default_kwargs["text_color"],
|
||||
"icon": default_kwargs["chat_icon"],
|
||||
# ... other expected properties
|
||||
}
|
||||
```
|
||||
|
||||
### Flow Testing with JSON Data
|
||||
Use predefined JSON flows and utility functions:
|
||||
|
||||
```python
|
||||
from tests.unit.build_utils import create_flow, build_flow, get_build_events, consume_and_assert_stream
|
||||
|
||||
async def test_flow_execution(client, json_memory_chatbot_no_llm, logged_in_headers):
|
||||
"""Test flow creation, building, and execution."""
|
||||
# Create flow from JSON data
|
||||
flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
|
||||
# Start the build process
|
||||
build_response = await build_flow(client, flow_id, logged_in_headers)
|
||||
job_id = build_response["job_id"]
|
||||
|
||||
# Get and validate event stream
|
||||
events_response = await get_build_events(client, job_id, logged_in_headers)
|
||||
assert events_response.status_code == codes.OK
|
||||
|
||||
# Process events and validate structure
|
||||
await consume_and_assert_stream(events_response, job_id)
|
||||
```
|
||||
|
||||
### Component Testing with Real Services
|
||||
Test components that need external APIs with proper markers:
|
||||
|
||||
```python
|
||||
@pytest.mark.api_key_required
|
||||
@pytest.mark.no_blockbuster
|
||||
async def test_component_with_external_api(self):
|
||||
"""Test component with external API integration."""
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
component = MyAPIComponent(
|
||||
api_key=api_key,
|
||||
model_name="gpt-4o",
|
||||
input_value="test input",
|
||||
session_id=str(uuid4()),
|
||||
)
|
||||
|
||||
response = await component.message_response()
|
||||
assert response.data.get("text") is not None
|
||||
```
|
||||
|
||||
### Mocking Language Models
|
||||
Use `MockLanguageModel` for testing without external APIs:
|
||||
|
||||
```python
|
||||
from tests.unit.mock_language_model import MockLanguageModel
|
||||
|
||||
@pytest.fixture
|
||||
def default_kwargs(self):
|
||||
return {
|
||||
"agent_llm": MockLanguageModel(),
|
||||
"input_value": "test message",
|
||||
"session_id": str(uuid4()),
|
||||
# ... other component-specific kwargs
|
||||
}
|
||||
```
|
||||
|
||||
### File Handling in Tests
|
||||
Use `anyio` and `aiofiles` for async file operations:
|
||||
|
||||
```python
|
||||
import aiofiles
|
||||
import anyio
|
||||
|
||||
async def test_file_operations(self, component_class, tmp_path):
|
||||
"""Test component file handling."""
|
||||
# Create temporary test file
|
||||
test_file = anyio.Path(tmp_path) / "test.txt"
|
||||
await test_file.write_text("Test content", encoding="utf-8")
|
||||
|
||||
# Test with file input
|
||||
component = component_class(files=[str(test_file)])
|
||||
result = await component.run()
|
||||
|
||||
# Verify file was processed
|
||||
assert len(result.files) == 1
|
||||
assert await test_file.exists()
|
||||
```
|
||||
|
||||
### API Endpoint Testing
|
||||
Test Langflow's REST API endpoints:
|
||||
|
||||
```python
|
||||
async def test_flows_endpoint(client, logged_in_headers):
|
||||
"""Test flow creation via API."""
|
||||
flow_data = {
|
||||
"name": "Test Flow",
|
||||
"description": "Test description",
|
||||
"data": {"nodes": [], "edges": []}
|
||||
}
|
||||
|
||||
response = await client.post(
|
||||
"api/v1/flows/",
|
||||
json=flow_data,
|
||||
headers=logged_in_headers
|
||||
)
|
||||
assert response.status_code == codes.CREATED
|
||||
|
||||
flow = response.json()
|
||||
assert flow["name"] == "Test Flow"
|
||||
assert "id" in flow
|
||||
```
|
||||
|
||||
### Build Config Testing
|
||||
Test component configuration updates:
|
||||
|
||||
```python
|
||||
async def test_build_config_updates(self, component_class, default_kwargs):
|
||||
"""Test dynamic build configuration updates."""
|
||||
component = await self.component_setup(component_class, default_kwargs)
|
||||
frontend_node = component.to_frontend_node()
|
||||
build_config = frontend_node["data"]["node"]["template"]
|
||||
|
||||
# Test configuration update
|
||||
component.set(llm_provider="OpenAI")
|
||||
updated_config = await component.update_build_config(
|
||||
build_config, "OpenAI", "llm_provider"
|
||||
)
|
||||
|
||||
assert updated_config["llm_provider"]["value"] == "OpenAI"
|
||||
assert "gpt-4o" in updated_config["model_name"]["options"]
|
||||
```
|
||||
|
||||
### Testing Event Streams
|
||||
Test real-time event streaming endpoints:
|
||||
|
||||
```python
|
||||
async def consume_and_validate_events(response, expected_job_id):
|
||||
"""Consume NDJSON event stream and validate structure."""
|
||||
count = 0
|
||||
first_event_seen = False
|
||||
end_event_seen = False
|
||||
|
||||
async for line in response.aiter_lines():
|
||||
if not line:
|
||||
continue
|
||||
|
||||
parsed = json.loads(line)
|
||||
|
||||
# Validate job_id in events
|
||||
if "job_id" in parsed:
|
||||
assert parsed["job_id"] == expected_job_id
|
||||
|
||||
# First event should be vertices_sorted
|
||||
if not first_event_seen:
|
||||
assert parsed["event"] == "vertices_sorted"
|
||||
first_event_seen = True
|
||||
|
||||
# Track end event
|
||||
elif parsed["event"] == "end":
|
||||
end_event_seen = True
|
||||
|
||||
count += 1
|
||||
|
||||
assert first_event_seen and end_event_seen
|
||||
return count
|
||||
```
|
||||
|
||||
### Testing Component Versioning
|
||||
Test backward compatibility across Langflow versions:
|
||||
|
||||
```python
|
||||
@pytest.fixture
|
||||
def file_names_mapping(self):
|
||||
"""Map component files across Langflow versions."""
|
||||
return [
|
||||
VersionComponentMapping(
|
||||
version="1.1.1",
|
||||
module="inputs",
|
||||
file_name="chat"
|
||||
),
|
||||
VersionComponentMapping(
|
||||
version="1.1.0",
|
||||
module="inputs",
|
||||
file_name="chat"
|
||||
),
|
||||
VersionComponentMapping(
|
||||
version="1.0.19",
|
||||
module="inputs",
|
||||
file_name="ChatInput"
|
||||
),
|
||||
]
|
||||
```
|
||||
|
||||
### Webhook Testing
|
||||
Test webhook endpoints with proper payload handling:
|
||||
|
||||
```python
|
||||
async def test_webhook_endpoint(client, added_webhook_test):
|
||||
"""Test webhook flow execution."""
|
||||
endpoint_name = added_webhook_test["endpoint_name"]
|
||||
endpoint = f"api/v1/webhook/{endpoint_name}"
|
||||
|
||||
payload = {"path": "/tmp/test_file.txt"}
|
||||
|
||||
response = await client.post(endpoint, json=payload)
|
||||
assert response.status_code == 202
|
||||
|
||||
# Verify webhook processed the payload
|
||||
# ... additional validation logic
|
||||
```
|
||||
|
||||
### Monkeypatch for Testing Errors
|
||||
Test error handling by mocking internal functions:
|
||||
|
||||
```python
|
||||
async def test_cancellation_error(client, flow_id, logged_in_headers, monkeypatch):
|
||||
"""Test error handling during flow cancellation."""
|
||||
import langflow.api.v1.chat
|
||||
|
||||
async def mock_cancel_with_error(*args, **kwargs):
|
||||
raise RuntimeError("Cancellation failed")
|
||||
|
||||
monkeypatch.setattr(
|
||||
langflow.api.v1.chat,
|
||||
"cancel_flow_build",
|
||||
mock_cancel_with_error
|
||||
)
|
||||
|
||||
response = await client.post(
|
||||
f"api/v1/build/{job_id}/cancel",
|
||||
headers=logged_in_headers
|
||||
)
|
||||
assert response.status_code == codes.INTERNAL_SERVER_ERROR
|
||||
assert "Cancellation failed" in response.json()["detail"]
|
||||
```
|
||||
|
||||
|
||||
@@ -1,203 +1,147 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides guidance to AI coding agents when working with code in this repository.
|
||||
Guidance for AI coding agents working in this repository.
|
||||
|
||||
## Project Overview
|
||||
> **Before you write code, read [docs/agents/PHILOSOPHY.md](./docs/agents/PHILOSOPHY.md).** Most off-narrative work comes from skipping it.
|
||||
|
||||
Langflow is a visual workflow builder for AI-powered agents. It has a Python/FastAPI backend, React/TypeScript frontend, and a lightweight executor CLI (lfx).
|
||||
## What Langflow is
|
||||
|
||||
Langflow is a **visual flow builder first**. Users drag components onto a canvas, wire them together, and either run the resulting flow in the Playground, deploy it as an API, or expose it as an MCP server. Saved flows are persistent JSON artifacts running in production — backwards compatibility for components is non-negotiable.
|
||||
|
||||
The repo is three Python packages and one frontend:
|
||||
|
||||
- **`lfx`** (`src/lfx/`): the executor core. Component base classes, the graph engine, and built-in components live here. Should not depend on `langflow` or `langflow-base` — see [docs/agents/ARCHITECTURE.md](./docs/agents/ARCHITECTURE.md) for the ~14 known violations to fix-not-extend.
|
||||
- **`langflow-base`** (`src/backend/base/langflow/`): the platform. FastAPI routes, auth, persistence, alembic, services. May import `lfx`.
|
||||
- **`langflow`**: the integration distribution that ships everything together.
|
||||
- **`frontend`** (`src/frontend/`): React 19 + TypeScript + Vite + Zustand + `@xyflow/react`. Talks to the backend over HTTP/WebSocket only.
|
||||
|
||||
Dependencies flow one way: `frontend → langflow → langflow-base → lfx`. See [docs/agents/ARCHITECTURE.md](./docs/agents/ARCHITECTURE.md).
|
||||
|
||||
## Non-negotiable tenets
|
||||
|
||||
These are extracted from [PHILOSOPHY.md](./docs/agents/PHILOSOPHY.md). The full file has the rest; these are the ones every change must respect.
|
||||
|
||||
1. **Flows are user artifacts.** Component class names, `name` attributes, input/output names, and input types are frozen once shipped. Mark old components `legacy=True` instead of editing.
|
||||
2. **Every backend feature must land on the canvas.** If it can't be a component or a property of one, it's an SDK feature and belongs in `lfx`, not `langflow-base`.
|
||||
3. **Components are the unit of work.** Don't add a route, store, or service unless a component or UI page consumes it.
|
||||
4. **Visible data flow beats clever magic.** Pass data through inputs and outputs. No hidden globals or side-channel state.
|
||||
5. **Composition over capability.** One job per component. Split before you add a tenth input.
|
||||
6. **It is not a fix without evidence.** Adding error handling, retries, type widening, or skipping a flaky test does not constitute a fix.
|
||||
|
||||
## Documentation map
|
||||
|
||||
Read the file that matches your task before you write code.
|
||||
|
||||
| Topic | File | When to read |
|
||||
|---|---|---|
|
||||
| Project story, design tenets | [docs/agents/PHILOSOPHY.md](./docs/agents/PHILOSOPHY.md) | Before any non-trivial change |
|
||||
| Package boundaries, where code goes, API versioning | [docs/agents/ARCHITECTURE.md](./docs/agents/ARCHITECTURE.md) | Before adding a file or endpoint |
|
||||
| Component dev: scope, breaking changes, conventions, icons | [docs/agents/COMPONENTS.md](./docs/agents/COMPONENTS.md) | Before adding or editing a component |
|
||||
| User-facing contracts: flow JSON, REST API, MCP, env vars | [docs/agents/CONTRACTS.md](./docs/agents/CONTRACTS.md) | Before changing anything user-visible |
|
||||
| Test patterns, fixtures, mocking policy | [docs/agents/TESTING.md](./docs/agents/TESTING.md) | Before writing or changing tests |
|
||||
| Don't/do rules, "fixes that aren't," before-claiming-done checklist | [docs/agents/ANTI-PATTERNS.md](./docs/agents/ANTI-PATTERNS.md) | Before claiming work is done |
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Python:** 3.10-3.13
|
||||
- **uv:** >=0.4 (Python package manager)
|
||||
- **Node.js:** >=20.19.0 (v22.12 LTS recommended)
|
||||
- **Python:** 3.10–3.13
|
||||
- **uv:** ≥0.4 (always use `uv run` for Python commands)
|
||||
- **Node.js:** ≥20.19.0 (v22.12 LTS recommended)
|
||||
- **npm:** v10.9+
|
||||
- **make:** For build coordination
|
||||
- **make:** for build coordination
|
||||
|
||||
## Common Commands
|
||||
## Common commands
|
||||
|
||||
### Development setup
|
||||
|
||||
### Development Setup
|
||||
```bash
|
||||
make init # Install all dependencies + pre-commit hooks
|
||||
make run_cli # Build and run Langflow (http://localhost:7860)
|
||||
make run_clic # Clean build and run (use when frontend issues occur)
|
||||
```
|
||||
|
||||
### Development Mode (Hot Reload)
|
||||
### Development mode (hot reload)
|
||||
|
||||
```bash
|
||||
make backend # FastAPI on port 7860 (terminal 1)
|
||||
make frontend # Vite dev server on port 3000 (terminal 2)
|
||||
```
|
||||
|
||||
For component development, enable dynamic loading:
|
||||
For component development with dynamic loading:
|
||||
|
||||
```bash
|
||||
LFX_DEV=1 make backend # Load all components dynamically
|
||||
LFX_DEV=mistral,openai make backend # Load only specific modules
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
### Code quality
|
||||
|
||||
```bash
|
||||
make format_backend # Format Python (ruff) - run FIRST before lint
|
||||
make format_backend # Format Python (ruff) — run FIRST before lint
|
||||
make format_frontend # Format TypeScript (biome)
|
||||
make format # Both
|
||||
make lint # mypy type checking
|
||||
```
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
make unit_tests # Backend unit tests (pytest, parallel)
|
||||
make unit_tests async=false # Sequential tests
|
||||
uv run pytest path/to/test.py # Single test file
|
||||
uv run pytest path/to/test.py::test_name # Single test
|
||||
|
||||
make test_frontend # Jest unit tests
|
||||
make tests_frontend # Playwright e2e tests
|
||||
```bash
|
||||
make unit_tests # Backend unit tests (pytest, parallel)
|
||||
make unit_tests async=false # Sequential
|
||||
uv run pytest path/to/test.py # Single test file
|
||||
uv run pytest path/to/test.py::test_name # Single test
|
||||
|
||||
make test_frontend # Jest unit tests
|
||||
make tests_frontend # Playwright e2e tests
|
||||
|
||||
# lfx tests specifically — run uv sync inside src/lfx (not src/lfx/src/lfx)
|
||||
cd src/lfx && uv sync && uv run pytest
|
||||
|
||||
# Sub-package tests (langflow-base, lfx) — sync that package's dev group first,
|
||||
# otherwise dev-only deps like fakeredis stay uninstalled.
|
||||
uv sync --group dev --package langflow-base
|
||||
```
|
||||
|
||||
### Database Migrations
|
||||
See [docs/agents/TESTING.md](./docs/agents/TESTING.md) for fixtures, base classes, and the graph testing pattern.
|
||||
|
||||
### Database migrations
|
||||
|
||||
```bash
|
||||
make alembic-revision message="Description" # Create migration
|
||||
make alembic-upgrade # Apply migrations
|
||||
make alembic-downgrade # Rollback one version
|
||||
make alembic-upgrade # Apply migrations
|
||||
make alembic-downgrade # Rollback one version
|
||||
```
|
||||
|
||||
## Architecture
|
||||
Never edit a past migration. Run `test_database.py` sequentially after any DB change.
|
||||
|
||||
### Monorepo Structure
|
||||
```
|
||||
src/
|
||||
├── backend/
|
||||
│ ├── base/langflow/ # Core backend package (langflow-base)
|
||||
│ │ ├── api/ # FastAPI routes (v1/, v2/)
|
||||
│ │ ├── components/ # Built-in Langflow components
|
||||
│ │ ├── services/ # Service layer (auth, database, cache, etc.)
|
||||
│ │ ├── graph/ # Flow graph execution engine
|
||||
│ │ └── custom/ # Custom component framework
|
||||
│ └── tests/ # Backend tests
|
||||
├── frontend/ # React/TypeScript UI
|
||||
│ └── src/
|
||||
│ ├── components/ # UI components
|
||||
│ ├── stores/ # Zustand state management
|
||||
│ └── icons/ # Component icons
|
||||
└── lfx/ # Lightweight executor CLI
|
||||
```
|
||||
### Version management
|
||||
|
||||
### Key Packages
|
||||
- **langflow**: Main package with all integrations
|
||||
- **langflow-base**: Core framework (api, services, graph engine)
|
||||
- **lfx**: Standalone CLI for running flows (`lfx serve`, `lfx run`)
|
||||
|
||||
### Service Layer
|
||||
Backend services in `src/backend/base/langflow/services/`:
|
||||
- `auth/` - Authentication
|
||||
- `database/` - SQLAlchemy models and migrations
|
||||
- `cache/` - Caching layer
|
||||
- `storage/` - File storage
|
||||
- `tracing/` - Observability integrations
|
||||
|
||||
## Component Development
|
||||
|
||||
Components live in `src/backend/base/langflow/components/`. To add a new component:
|
||||
|
||||
1. Create component class inheriting from `Component`
|
||||
2. Define `display_name`, `description`, `icon`, `inputs`, `outputs`
|
||||
3. Add to `__init__.py` (alphabetical order)
|
||||
4. Run with `LFX_DEV=1 make backend` for hot reload
|
||||
|
||||
**IMPORTANT:** Changing a component's class name is a breaking change and should never be done. The class name serves as an identifier used to match components in saved flows and to flag them for updates in the UI. Renaming it will break existing flows that use that component.
|
||||
|
||||
### Component Structure
|
||||
```python
|
||||
from langflow.custom import Component
|
||||
from langflow.io import MessageTextInput, Output
|
||||
|
||||
class MyComponent(Component):
|
||||
display_name = "My Component"
|
||||
description = "What it does"
|
||||
icon = "component-icon" # Lucide icon name or custom
|
||||
|
||||
inputs = [
|
||||
MessageTextInput(name="input_value", display_name="Input"),
|
||||
]
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="process"),
|
||||
]
|
||||
|
||||
def process(self) -> Message:
|
||||
# Component logic
|
||||
return Message(text=self.input_value)
|
||||
```
|
||||
|
||||
### Component Testing
|
||||
Tests go in `src/backend/tests/unit/components/`. Use base classes:
|
||||
- `ComponentTestBaseWithClient` - Components needing API access
|
||||
- `ComponentTestBaseWithoutClient` - Pure logic components
|
||||
|
||||
Required fixtures: `component_class`, `default_kwargs`, `file_names_mapping`
|
||||
|
||||
## Frontend Development
|
||||
|
||||
- **React 19** + TypeScript + Vite
|
||||
- **Zustand** for state management
|
||||
- **@xyflow/react** for graph visualization
|
||||
- **Tailwind CSS** for styling
|
||||
|
||||
### Custom Icons
|
||||
1. Create SVG component in `src/frontend/src/icons/YourIcon/`
|
||||
2. Export with `forwardRef` and `isDark` prop support
|
||||
3. Add to `lazyIconImports.ts`
|
||||
4. Set `icon = "YourIcon"` in Python component
|
||||
|
||||
## Testing Notes
|
||||
|
||||
- `@pytest.mark.api_key_required` - Tests requiring external API keys
|
||||
- `@pytest.mark.no_blockbuster` - Skip blockbuster plugin
|
||||
- Database tests may fail in batch but pass individually
|
||||
- Pre-commit hooks require `uv run git commit`
|
||||
- Always use `uv run` when running Python commands
|
||||
- When running tests inside a sub-package (e.g. `langflow-base`, `lfx`), sync that package's dev group first: `uv sync --group dev --package langflow-base`. The default `uv sync` only resolves the top-level workspace and may leave dev-only test deps (e.g. `fakeredis`) uninstalled.
|
||||
|
||||
### Graph Testing Pattern
|
||||
|
||||
Proper Graph tests follow this pattern:
|
||||
1. Build graph with connected components
|
||||
2. Connect them via `.set()` calls
|
||||
3. Call `async_start` and iterate over the results
|
||||
4. Validate the results
|
||||
|
||||
### Testing Best Practices
|
||||
|
||||
- Avoid mocking in tests when possible
|
||||
- Prefer real integrations for more reliable tests
|
||||
|
||||
## Version Management
|
||||
```bash
|
||||
make patch v=1.5.0 # Update version across all packages
|
||||
```
|
||||
|
||||
This updates: `pyproject.toml`, `src/backend/base/pyproject.toml`, `src/frontend/package.json`
|
||||
## Pre-commit workflow
|
||||
|
||||
## Pre-commit Workflow
|
||||
Pre-commit hooks run ruff and biome automatically on `git commit`, so manual formatting isn't required. To avoid an extra commit cycle:
|
||||
|
||||
Pre-commit hooks run ruff and biome automatically on `git commit`, so manual
|
||||
formatting is not required. To avoid an extra commit cycle when you have many
|
||||
changes:
|
||||
|
||||
1. Run `make format_backend` once before staging - fixes most ruff issues up front.
|
||||
1. Run `make format_backend` once before staging — fixes most ruff issues up front.
|
||||
2. Run `uv run git commit` (the `uv run` ensures pre-commit finds the right Python).
|
||||
3. If you touched backend code, run `make unit_tests` locally for faster feedback than CI.
|
||||
|
||||
## Pull Request Guidelines
|
||||
## Pull request guidelines
|
||||
|
||||
- Follow [semantic commit conventions](https://www.conventionalcommits.org/)
|
||||
- Reference any issues fixed (e.g., `Fixes #1234`)
|
||||
- Ensure all tests pass before submitting
|
||||
- Follow [semantic commit conventions](https://www.conventionalcommits.org/).
|
||||
- Reference issues fixed (`Fixes #1234`).
|
||||
- Target the active `release-X.Y.Z` branch, not `main`. See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
- Don't push or open PRs without explicit user direction.
|
||||
- No "Generated with Claude Code" / `Co-Authored-By: Claude` trailers.
|
||||
- No test-plan checklists or Jira links in PR descriptions.
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation uses Docusaurus and lives in `docs/`:
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
yarn install
|
||||
yarn start # Dev server on port 3000 (prompts for 3001 if 3000 is in use)
|
||||
yarn start # Dev server on port 3000 (3001 if 3000 is in use)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Anti-Patterns and Scar Tissue
|
||||
|
||||
These are battle scars. Every rule here cites a real recurring failure: a commit, a revert, or an explicit project lesson. Read this file when you're about to "just" change something — most of the don't/do rules below started as a one-line "small fix" that broke production.
|
||||
|
||||
## Don't / Do (with why)
|
||||
|
||||
1. **Don't add or rename a field on a component without regenerating starter projects AND the component index.**
|
||||
*Why:* commits `1a9f4548c`, `5987421bd`, `bbe1dad7c`, `69d29a4c6`, `67af71450`, `4f9beebc0` are all `fix:` commits cleaning up the same "I added a field and shipped" mistake. CI (`9dad1965c`) now syncs the index on label addition — that's a backstop, not a substitute.
|
||||
|
||||
2. **Don't rename a component class, display name, or module path.** If unavoidable, add the new component alongside the old one, set `legacy = True` + `replacement = ["<category>.<NewClassName>"]` on the old class, and update every supported version's `file_names_mapping` in tests.
|
||||
*Why:* class names key saved flows. There is no class-rename map in `setup.py` — the `type_migrations` map there only handles output type strings (`Data` → `JSON`, `DataFrame` → `Table`). See `2a7c56e84` (`IngestionDescriberComponent` → `FileDescriptionGeneratorComponent`) and `f5a0d52197` (starter project rename) for the cleanup churn. See [CONTRACTS.md](./CONTRACTS.md) for the full list.
|
||||
|
||||
3. **Don't mock at a different boundary than production calls.** If production uses `subprocess.Popen`, mock `Popen` — not `run`.
|
||||
*Why:* `a747135d1`, `bc433c375` — mocked tests passed; production failed. **Better: don't mock at all** — project policy is "Avoid mocking in tests." Use real integrations or `MockLanguageModel` for pure-logic LLM paths.
|
||||
|
||||
4. **Don't write a Graph test by poking internals.**
|
||||
*Why:* the canonical pattern is build → connect via `.set()` → call `async_start` → iterate results → validate. Tests that bypass this don't exercise the graph the way users do.
|
||||
|
||||
5. **Don't add a retry, broaden an except, or widen a type to make a symptom go away.**
|
||||
*Why:* "It is not a fix if you didn't have evidence of the change fixing something." See revert chain `9cdbf1b23`, `35aad2f2e`, `e3b90b7351` — fixes that masked rather than solved, then had to be reverted.
|
||||
|
||||
6. **Don't bump a dependency speculatively.** Confirm the conflict reproduces locally and the new version actually resolves it.
|
||||
*Why:* `cf659f0d0`, `ef6a303406`, `38d142a72`, `9029c4b61` — repeated dep-conflict cleanup.
|
||||
|
||||
7. **Don't invent model IDs, MCP tool names, or API surface.** Read the registry, the batch dispatcher, and `models.py` first.
|
||||
*Why:* `9f1402ed9` (`remove fictional gpt-5.3 ids`), `cecc4a6c2` (`expose layout tool as layout_flow to match batch dispatch`).
|
||||
|
||||
8. **Don't use raw `langflow run` / `python` when a `uv` command exists.** Always `uv run`. For lfx tests specifically: `uv sync` inside `src/lfx` (not `src/lfx/src/lfx`) to avoid pulling in langflow.
|
||||
*Why:* lfx must be testable without langflow installed; mixing them masks dependency leaks.
|
||||
|
||||
9. **Don't add a database migration without running `make alembic-upgrade` end to end** and `uv run pytest src/backend/tests/unit/test_database.py` sequentially.
|
||||
*Why:* `d8b9cc38f`, `c30150a2d`, `6585fe661`. `test_database.py` may pass in batch and fail individually — agents have stopped running it.
|
||||
|
||||
10. **Don't claim a race condition is fixed without an integration test that reproduced it.** Agent double-execution and queue leaks keep coming back.
|
||||
*Why:* `8a993ac90`, `42e84d0fd`, `749865b31`, `f30b291f8`.
|
||||
|
||||
11. **Don't add `tool_mode=True` to outputs that confuse agents** (e.g., dataframe surfaces).
|
||||
*Why:* `cbc54108` (`remove tool_mode=True for as_dataframe`), `35aad2f2e` (revert of the original disable). Output tool exposure is a user contract — don't toggle it back and forth.
|
||||
|
||||
12. **Don't widen the scope of a PR.** Closely related follow-ups belong in the existing open PR, not a new stacked PR.
|
||||
|
||||
13. **Don't ship without running the tests locally.** Typing the test command in the chat is not running it.
|
||||
|
||||
14. **Don't make incremental "wip" commits.** One coherent commit per logical change, after everything is done and tested.
|
||||
|
||||
15. **Don't push or open PRs without explicit user confirmation.** Pushing during active CI iteration on an already-open PR is fine; opening a new PR or pushing new branches is not.
|
||||
|
||||
## Things that look like a fix but aren't
|
||||
|
||||
- Adding `try/except Exception: pass` around a flaky call.
|
||||
- Adding a `time.sleep` to dodge a race instead of awaiting a condition.
|
||||
- Bumping a dependency version "just in case."
|
||||
- Marking a failing test `@pytest.mark.skip` without a linked issue.
|
||||
- Switching `# type: ignore` instead of fixing the type.
|
||||
- Reverting symptoms (a UI flag, a tool flag) instead of finding the root.
|
||||
- "Fixing" a test by changing its assertion to match wrong output.
|
||||
|
||||
## Before claiming done
|
||||
|
||||
- [ ] `make format_backend` and `make lint` clean.
|
||||
- [ ] Tests for the changed surface ran and passed locally — not just typed out.
|
||||
- [ ] If a component field/output changed: starter projects regenerated and `component_index.json` rebuilt.
|
||||
- [ ] If a class/module renamed: old class kept with `legacy=True` + `replacement=[...]`; `file_names_mapping` updated for every supported version.
|
||||
- [ ] No incremental "wip" commits. One coherent commit per logical change.
|
||||
- [ ] No "Generated with Claude Code" / `Co-Authored-By: Claude` trailers.
|
||||
- [ ] No `--no-verify` unless explicitly authorized.
|
||||
- [ ] PR not pushed and PR not opened until the user explicitly asks.
|
||||
- [ ] PR description has no test-plan checklist, no Jira links/IDs.
|
||||
|
||||
## Where to look for context — don't invent it
|
||||
|
||||
- **Starter projects:** `src/backend/base/langflow/initial_setup/starter_projects/`. Any field/output change must regenerate these.
|
||||
- **Component index:** `src/lfx/src/lfx/_assets/component_index.json` (rebuilt via the index sync workflow added in `9dad1965c`).
|
||||
- **Output type migrations:** `src/backend/base/langflow/initial_setup/setup.py` `type_migrations` map — handles renames of *output type strings* (e.g., `Data` → `JSON`). It does NOT remap component class names; for class renames use `legacy=True` + `replacement=[...]`.
|
||||
- **Version mapping for component tests:** `src/backend/tests/constants.py::SUPPORTED_VERSIONS` and each test's `file_names_mapping`.
|
||||
- **Release notes / changelog:** `docs/docs/Support/release-notes/` — check before claiming behavior is "new"; many features predate the current task.
|
||||
- **MCP tool catalog:** dispatched via the batch tool — names must match (`cecc4a6c2` is the canonical "I guessed the tool name" failure).
|
||||
- **Models / providers:** real model IDs come from the provider's listing endpoint or our adapter, never from training-data memory (`9f1402ed9`).
|
||||
- **Migrations:** `src/backend/base/langflow/alembic/versions/` — always read the latest two before adding a new one.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Architecture Boundaries
|
||||
|
||||
Langflow is three Python packages and one frontend, layered with one-way dependencies. Most "off-narrative" code is a boundary violation. Read this before adding a new file.
|
||||
|
||||
## Package dependency graph (one-way only)
|
||||
|
||||
```
|
||||
frontend (TS) ──HTTP──▶ langflow (api routers, integrations, distribution)
|
||||
│
|
||||
▼ may import
|
||||
langflow-base (services, graph, db, alembic)
|
||||
│
|
||||
▼ may import
|
||||
lfx (executor core, base primitives, components)
|
||||
│
|
||||
▼ may import
|
||||
langchain-core, pydantic, third-party SDKs
|
||||
```
|
||||
|
||||
### Dependency rules
|
||||
|
||||
- **`lfx` MUST NOT import `langflow.*` or `langflow-base.*`.** If lfx code needs a service (auth, db, flow lookup), define an interface inside `lfx` and inject the implementation from `langflow`. The repo currently has ~13 upward `from langflow.*` imports inside `src/lfx/src/lfx/components/...` (auth, db, helpers) — these are **known violations**. Do not add more; prefer fixing them.
|
||||
- **`langflow-base` MAY import `lfx`.** It MUST NOT import vendor-specific component modules from `langflow.components.<vendor>`.
|
||||
- **`frontend` talks to `langflow` only via HTTP/WebSocket.** No shared filesystem state.
|
||||
|
||||
## "Where does this code go?" decision tree
|
||||
|
||||
Walk top-down. Stop at the first match.
|
||||
|
||||
1. Is it framework-agnostic flow execution, base component classes, or `Component` primitives?
|
||||
→ `src/lfx/src/lfx/` (`base/` for shared primitives, `components/` for built-ins shipped with lfx).
|
||||
2. Is it a FastAPI route, auth, db model, alembic migration, or a lifecycle-managed singleton?
|
||||
→ `src/backend/base/langflow/` (`api/`, `services/X/`, `alembic/versions/`).
|
||||
3. Is it a vendor integration (OpenAI, Pinecone, Notion, …) — a `Component` subclass that wraps a third-party SDK?
|
||||
→ `src/lfx/src/lfx/components/<category>/` and update its `__init__.py` alphabetically. Never rename the class.
|
||||
4. Is it UI, state, or icons?
|
||||
→ `src/frontend/src/`. If it consumes a new API field, also update `src/frontend/src/types/`.
|
||||
5. Is it CLI behavior for `lfx run` / `lfx serve`?
|
||||
→ `src/lfx/src/lfx/cli/`.
|
||||
6. Is it a SQLAlchemy/SQLModel model change?
|
||||
→ `services/database/models/` AND `make alembic-revision message="..."` AND apply with `make alembic-upgrade`.
|
||||
7. Is it a flow JSON schema change?
|
||||
→ STOP. Existing saved flows must keep loading. Add a version mapping; do not mutate the existing shape. See [CONTRACTS.md](./CONTRACTS.md).
|
||||
8. Is it shared by both `lfx` and `langflow-base`?
|
||||
→ `src/lfx/src/lfx/base/`, never `langflow/base/`.
|
||||
|
||||
## Dependency direction — bad/good examples
|
||||
|
||||
- **Bad:** `from langflow.services.deps import session_scope` inside `src/lfx/...`.
|
||||
**Good:** Define `lfx.interfaces.SessionProvider`, accept it as a constructor arg; `langflow` wires the concrete `session_scope` at startup.
|
||||
- **Bad:** `from langflow.components.openai import ...` inside `langflow-base` core (`api/`, `services/`, `graph/`).
|
||||
**Good:** Components are loaded dynamically via the component registry; core code references `Component` only.
|
||||
- **Bad:** A new `MyHelperService` that's just functions.
|
||||
**Good:** Utility functions go in `langflow/helpers/` or `lfx/utils/`. A service inherits from `services/base.Service` and is registered via `services/factory.py`.
|
||||
|
||||
## API change protocol
|
||||
|
||||
- **`api/v1/`** is the live, stable surface (~25 routers). Existing v1 endpoints MUST stay backwards-compatible: only additive fields, never rename or remove.
|
||||
- **`api/v2/`** is the **active redesign surface** (`files`, `mcp`, `registration`, `workflow`) — both are mounted at runtime in `api/router.py`. v2 is **not** "future"; the old cursor rule was wrong.
|
||||
- Add a new endpoint to v2 only if it (a) replaces a v1 endpoint with a breaking shape change, or (b) belongs to one of the four v2 domains. Otherwise extend v1 additively.
|
||||
- A breaking change to a v1 endpoint is forbidden. Add a v2 sibling and leave v1 in place.
|
||||
|
||||
## Cross-cutting change protocol
|
||||
|
||||
A change that touches a request/response shape MUST update three places in the same PR:
|
||||
|
||||
1. The pydantic model in `langflow/api/v{1,2}/schemas.py` (or the route's local schema).
|
||||
2. The TypeScript type in `src/frontend/src/types/` consumed by the affected page/store. There is no OpenAPI generator — the types are hand-maintained, so the frontend silently breaks at runtime if you skip this.
|
||||
3. If the field is persisted: a new alembic revision (`make alembic-revision message=...`) AND a flow-JSON version mapping if the shape lives inside saved flows.
|
||||
|
||||
If you cannot do all three in one PR, do not start.
|
||||
|
||||
## Service vs utility vs component
|
||||
|
||||
- **Service** (`services/<name>/`): lifecycle-managed singleton, inherits `services.base.Service`, registered through `services/factory.py`, accessed via `services/deps.py`. Use when the thing has state, startup/shutdown, or shared connections (db, cache, queue).
|
||||
- **Utility** (`helpers/`, `utils/`, or `lfx/utils/`): pure or near-pure functions. Use when there is no shared state and no lifecycle.
|
||||
- **Component** (`src/lfx/src/lfx/components/<category>/`): user-visible node in the graph, subclass of `Component`, with `display_name`, `inputs`, `outputs`. Use only when the user must wire it on the canvas. Do not add a Component to expose internal plumbing.
|
||||
|
||||
## `lfx/base/` vs `langflow/base/`
|
||||
|
||||
Both exist. Both have `agents/`, `data/`, `models/`, `prompts/`. New shared primitives go in **`src/lfx/src/lfx/base/`**. The `langflow/base/` tree is legacy; do not add to it.
|
||||
@@ -0,0 +1,138 @@
|
||||
# Component Development
|
||||
|
||||
Components are the unit of work in Langflow. They render as nodes on the canvas, persist into flow JSON, and become tools for Agents. Get the contract right and the rest of the system follows; get it wrong and you orphan every saved flow that referenced your component.
|
||||
|
||||
Components live in **`src/lfx/src/lfx/components/<category>/`**. The mirror tree under `src/backend/base/langflow/components/` is legacy stubs — do not add files there.
|
||||
|
||||
## Before you add a component (decision checklist)
|
||||
|
||||
Stop and check, in order. If any answer is "yes," do not create a new component.
|
||||
|
||||
1. **Does one already exist?** Search:
|
||||
```bash
|
||||
rg -l 'display_name = ".*<keyword>.*"' src/lfx/src/lfx/components/
|
||||
```
|
||||
and check the category folder. The Calculator regret (two of them, see `tools/calculator.py` `legacy = True, replacement = ["helpers.CalculatorComponent"]`) is what happens when this step is skipped.
|
||||
2. **Is this a thin wrapper around an existing tool/SDK that an Agent could call directly via `tool_mode=True`?** If yes, add `tool_mode=True` to the relevant input on the existing component instead of building a new one.
|
||||
3. **Does it belong in a vendor bundle** (e.g., `openai/`, `anthropic/`, `datastax/`) rather than in generic `tools/` or `helpers/`? Vendor logic goes in vendor folders so bundles can ship/version independently.
|
||||
4. **Is this an lfx-runtime concern** (graph execution, schema, IO primitives) or a **Langflow-app concern** (auth, DB, tracing)? Components are lfx; never import `langflow.services.*` from a component.
|
||||
5. **Does an existing base class cover this category?** Use it:
|
||||
- LLMs → `LCModelComponent` (see `openai/openai_chat_model.py`)
|
||||
- Tools → `LCToolComponent` (see `tools/calculator.py`)
|
||||
- Vector stores → `LCVectorStoreComponent` + `@check_cached_vector_store` (see `vectorstores/local_db.py`)
|
||||
- Chat IO → `ChatComponent` (see `input_output/chat.py`)
|
||||
|
||||
## Scope rules (one component, one job)
|
||||
|
||||
- One job per component. If the description has " and " in it, split it.
|
||||
- **Inputs budget:** aim for ≤8 visible inputs; push the rest behind `advanced=True`. If you need a mode switch (Ingest/Retrieve etc.), use `TabInput` + `update_build_config` to hide irrelevant fields — see `vectorstores/local_db.py`.
|
||||
- **Outputs:** one primary `Output(method=...)` is the default. Add more only if downstream nodes genuinely need different shapes (Message vs DataFrame vs Data). Don't add an output you don't use.
|
||||
- Mark inputs an Agent should be able to fill with `tool_mode=True` (e.g., `local_db.py`) — this is how a component becomes an agent tool.
|
||||
|
||||
## Breaking-change list (locked once shipped)
|
||||
|
||||
Saved flows reference components by string identifiers. Once a component is merged, the following are **frozen** — change them and existing user flows break silently:
|
||||
|
||||
- The **class name** (already in our rules).
|
||||
- The **`name = "..."` class attribute**. **This is the flow-JSON identifier**, not the class name. See `openai_chat_model.py`, `chat.py`.
|
||||
- Each input **`name=...`**. Renaming `input_value` → `prompt` orphans every edge pointing at it.
|
||||
- Each output **`name=...`** and its declared return type. Downstream nodes match on both.
|
||||
- **Default values** and default behavior of existing inputs (a flow saved with the default re-loads with the new default).
|
||||
|
||||
To remove or rework a component, **do not edit it in place**. Add the replacement under the right category, then on the old one set:
|
||||
|
||||
```python
|
||||
legacy = True
|
||||
replacement = ["<category>.<NewClassName>"]
|
||||
```
|
||||
|
||||
(see `tools/calculator.py`, `flow_controls/sub_flow.py`). The UI shows "Updates Available" and migrates users; the old class stays importable.
|
||||
|
||||
See [CONTRACTS.md](./CONTRACTS.md) for the full user-facing surface this protects.
|
||||
|
||||
## Conventions agents miss
|
||||
|
||||
These appear all over the codebase but rarely in old docs.
|
||||
|
||||
- **`name`** (class attr): flow-JSON identifier. Always set it explicitly; do not rely on `__class__.__name__`. Example: `chat.py` `name = "ChatInput"`.
|
||||
- **`legacy = True` + `replacement = [...]`**: deprecation pair. Always together. Example: `tools/calculator.py`.
|
||||
- **`tool_mode=True`** on an input: exposes the component as an Agent tool with that input as the tool argument. Example: `vectorstores/local_db.py`.
|
||||
- **`real_time_refresh=True` + `update_build_config(self, build_config, value, name)`**: dynamic form. Use for mode switches and dependent-field hide/show. Examples: `openai/openai_chat_model.py`, `vectorstores/local_db.py`.
|
||||
- **`metadata = {"keywords": [...]}`**: extra search terms for the component picker. Example: `data_source/sql_executor.py`.
|
||||
- **`minimized = True`**: render collapsed by default. Example: `input_output/chat.py`.
|
||||
- **`documentation = "https://docs.langflow.org/..."`**: deep-link from the node UI. Example: `input_output/chat.py`.
|
||||
|
||||
## Placement rules
|
||||
|
||||
- **Vendor-specific code** → vendor folder (`openai/`, `anthropic/`, `datastax/`, `cohere/`, …). Not `tools/`, not `models/`.
|
||||
- **Generic, vendor-agnostic helpers** → `helpers/`, `processing/`, `logic/`, `flow_controls/`.
|
||||
- **IO primitives** → `input_output/`.
|
||||
- Add the import to the category `__init__.py` in **alphabetical order**.
|
||||
- **Do not invent a new top-level category.** If you think you need one, that is a signal to discuss with the team first — categories are visible in the UI sidebar and are part of the product surface.
|
||||
|
||||
## Icons
|
||||
|
||||
Every component needs an icon. Use a Lucide icon when one fits; create a custom SVG only for vendor logos.
|
||||
|
||||
### Lucide icon (default)
|
||||
|
||||
```python
|
||||
icon = "calculator" # any Lucide icon name, lowercase
|
||||
```
|
||||
|
||||
See https://lucide.dev/icons for the catalog.
|
||||
|
||||
### Custom vendor icon
|
||||
|
||||
For brand logos you need a frontend SVG component. The Python `icon` string and the frontend mapping key must match **exactly** (case-sensitive).
|
||||
|
||||
1. **Python:** set `icon = "AstraDB"` on the component.
|
||||
2. **Frontend SVG component** at `src/frontend/src/icons/AstraDB/AstraDB.jsx`:
|
||||
```jsx
|
||||
const AstraSVG = (props) => (
|
||||
<svg {...props}>
|
||||
<path fill={props.isDark ? "#ffffff" : "#0A0A0A"} d="..." />
|
||||
</svg>
|
||||
);
|
||||
```
|
||||
3. **Wrapper** at `src/frontend/src/icons/AstraDB/index.tsx`:
|
||||
```tsx
|
||||
import React, { forwardRef } from "react";
|
||||
import AstraSVG from "./AstraDB";
|
||||
|
||||
export const AstraDBIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
|
||||
(props, ref) => <AstraSVG ref={ref} isDark={isDark} {...props} />
|
||||
);
|
||||
```
|
||||
4. **Lazy import** in `src/frontend/src/icons/lazyIconImports.ts`:
|
||||
```ts
|
||||
AstraDB: () =>
|
||||
import("@/icons/AstraDB").then((mod) => ({ default: mod.AstraDBIcon })),
|
||||
```
|
||||
5. Verify in the UI in **both light and dark mode**.
|
||||
|
||||
### Icon checklist
|
||||
|
||||
- [ ] Python `icon = "..."` set.
|
||||
- [ ] If custom: SVG component with `isDark` prop, wrapper with `forwardRef`, entry in `lazyIconImports.ts`.
|
||||
- [ ] Light and dark mode both verified.
|
||||
|
||||
## Component testing
|
||||
|
||||
See [TESTING.md](./TESTING.md) for the full testing contract. Quick reference:
|
||||
|
||||
- Inherit from `ComponentTestBaseWithClient` (needs API) or `ComponentTestBaseWithoutClient` (pure logic).
|
||||
- Provide three fixtures: `component_class`, `default_kwargs`, `file_names_mapping`.
|
||||
- Use `MockLanguageModel` for pure-logic LLM paths; use `@pytest.mark.api_key_required` for real-API tests.
|
||||
- For graph behavior, use the Graph test pattern: build, `.set()`, `async_start`, validate. Do not poke graph internals.
|
||||
|
||||
## Canonical examples
|
||||
|
||||
When in doubt, read these files before starting:
|
||||
|
||||
- LLM with dynamic provider switching: `src/lfx/src/lfx/components/openai/openai_chat_model.py`
|
||||
- Tool with `tool_mode` and legacy/replacement: `src/lfx/src/lfx/components/tools/calculator.py`
|
||||
- Vector store with cache + tab inputs: `src/lfx/src/lfx/components/vectorstores/local_db.py`
|
||||
- Chat input with `minimized`: `src/lfx/src/lfx/components/input_output/chat.py`
|
||||
- Component with searchable metadata: `src/lfx/src/lfx/components/data_source/sql_executor.py`
|
||||
- Legacy + replacement pattern: `src/lfx/src/lfx/components/flow_controls/sub_flow.py`
|
||||
@@ -0,0 +1,57 @@
|
||||
# User-Facing Contracts
|
||||
|
||||
These are the surfaces users depend on. Breaking them silently is the most common way an agent ships work that "doesn't connect to the story." Read this before changing anything below.
|
||||
|
||||
## The contract surface
|
||||
|
||||
| # | Contract | Source of truth | Rule |
|
||||
|---|----------|-----------------|------|
|
||||
| 1 | Component class `name` attribute | `class XComponent: name = "X"` | NEVER rename. Used to match nodes in saved flow JSON. |
|
||||
| 2 | Component class identifier (Python class name) | `class XComponent` | NEVER rename. Combined with `name` for resolution. |
|
||||
| 3 | Component file path + module | `src/lfx/src/lfx/components/<cat>/<file>.py` | Renames are breaking. The supported workflow is to add the new component alongside the old one and set `legacy = True` + `replacement = ["<category>.<NewClassName>"]` on the old class. Update every component's `file_names_mapping` in tests for past `SUPPORTED_VERSIONS` (`src/backend/tests/constants.py`). Regenerate starter projects and rebuild the component index. |
|
||||
| 4 | Input `name=` and `Output(name=...)` on every component | The component file itself | NEVER rename or remove. Saved flows reference these as keys. Adding new optional inputs is safe; removing or renaming breaks every saved flow that used them. |
|
||||
| 5 | Output order and `Output.types` | Component `outputs = [...]` list | Reordering changes default selection in old flows. Tightening a type is a breaking change; widening is safe. |
|
||||
| 6 | Default values for inputs | `default=` / `value=` in input spec | Changing a default silently changes behavior for users who relied on the default. Treat as a breaking change unless the old default was a bug. |
|
||||
| 7 | Flow JSON schema | `src/backend/base/langflow/initial_setup/starter_projects/*.json` (canonical examples) | Top-level `data.nodes[*].data.node` shape and `data.edges` shape are public. New fields must be optional with defaults. |
|
||||
| 8 | Public REST API | `docs/docs/API-Reference/api-flows-run.mdx`, `api-build.mdx`, `api-files.mdx`, `api-projects.mdx`, `api-logs.mdx`, `api-monitor.mdx`, `api-users.mdx`. Endpoints with `include_in_schema=False` are internal. | Documented endpoints are stable. `POST /api/v1/run/{flow_id_or_name}` and `POST /api/v1/webhook/{flow_id_or_name}` are user contracts — payload shape and status codes are frozen. |
|
||||
| 9 | MCP tool exposure | `src/backend/base/langflow/api/v1/mcp.py`, `mcp_projects.py`. Tool-mode toggled by `Output.tool_mode` / outputs named `component_as_tool`. | Removing `tool_mode=True` from an existing component output, or changing its name, breaks every Agent flow using it as a tool. |
|
||||
| 10 | `Message` / `Data` / `DataFrame` schema | `src/lfx/src/lfx/schema/message.py` (`text`, `sender`, `sender_name`, `session_id`, `flow_id`, `timestamp`, `properties`, `content_blocks`, `category`, `files`, `error`, `edit`, `duration`, `session_metadata`) | Inter-component wire format. Add fields with defaults only. Renaming or retyping any listed field breaks every running flow. |
|
||||
| 11 | Environment variables | `LANGFLOW_*` (server) defined in `src/backend/base/langflow/services/settings/base.py` and `feature_flags.py`. `LFX_*` (executor) defined in `src/lfx/src/lfx/services/settings/base.py`, except a few read directly via `os.getenv` (e.g., `LFX_DEV` in `src/lfx/src/lfx/interface/components.py`). | Public deployment contract. Renaming requires deprecation cycle reading both names. |
|
||||
| 12 | Database schema | `src/backend/base/langflow/services/database/models/` + `alembic/versions/`. | NEVER edit a model without `make alembic-revision`. Never edit a past migration. Custom-component Python source is stored in user DBs — refactoring an import path used by `from langflow.X import Y` breaks loading those rows. |
|
||||
| 13 | Starter project JSON | `src/backend/base/langflow/initial_setup/starter_projects/*.json` | Each references real components by `name`/`type`. Renaming a component, removing an input, or changing an output type breaks loading. After ANY component change, re-load the affected starter project. |
|
||||
| 14 | Webhook payload shape | `POST /api/v1/webhook/{flow_id_or_name}` | External systems POST here. Response status (202 Accepted) and the `dict` body shape are frozen. |
|
||||
| 15 | Component index | `src/lfx/src/lfx/_assets/component_index.json` | Generated artifact consumed by the frontend. Any field/output addition requires regeneration; CI enforces this on label add. |
|
||||
|
||||
## Before-you-change matrix
|
||||
|
||||
| If you are about to... | Check / update |
|
||||
|---|---|
|
||||
| Rename a component file | Add the new file alongside; set `legacy = True` + `replacement = [...]` on the old class; update every test's `file_names_mapping`; grep `starter_projects/*.json` for the old type |
|
||||
| Rename an input `name=` | Don't. Add a new input alongside and deprecate the old via `legacy=True` on the component if necessary; grep `starter_projects/*.json` for the old name |
|
||||
| Add or rename an output | Regenerate starter projects; rebuild component index |
|
||||
| Change a default value | Treat as breaking. Grep starter projects + tests for reliance |
|
||||
| Remove `tool_mode=True` from an output | Search agent flows / starter projects using it as a tool; this is a user-visible regression |
|
||||
| Add a `Message` field | Must be `Optional` with default; never reorder existing |
|
||||
| Add a REST endpoint | If user-facing, add to `docs/docs/API-Reference/`; if internal, set `include_in_schema=False` |
|
||||
| Modify a DB model | `make alembic-revision message="..."`; never edit past revisions |
|
||||
| Change a `LANGFLOW_*` / `LFX_*` env var | Read old name as fallback for at least one minor version; document in release notes |
|
||||
| Change a request/response schema | Update pydantic schema + `src/frontend/src/types/` + alembic if persisted, all in one PR |
|
||||
|
||||
## Breaking changes that look harmless
|
||||
|
||||
These all look like cleanup. They are user-visible regressions.
|
||||
|
||||
- **Renaming `input_value` → `text`** on a component: 100% of saved flows referencing that input lose their wiring.
|
||||
- **Reordering `outputs = [a, b]` to `[b, a]`**: old flows with the default selection now route from the wrong output.
|
||||
- **Tightening `Output(types=["Message", "Data"])` → `["Message"]`**: edges that resolved as `Data` go invalid on load.
|
||||
- **Changing a `default="gpt-4o-mini"` to `default="gpt-5"`**: silently re-bills users.
|
||||
- **Moving `langflow/components/foo/bar.py` → `langflow/components/foo2/bar.py`**: custom components stored in user DBs that `from langflow.components.foo.bar import ...` fail on next load. `file_names_mapping` test passes because it only validates listed historical versions, not user code.
|
||||
- **Dropping `tool_mode=True` from a vector store's `as_dataframe`**: every Agent that called it as a tool now sees the tool disappear.
|
||||
- **Adding a required field to `Message`**: every queued message in a running deployment fails to deserialize on next read.
|
||||
- **Renaming a starter project file**: deep links from docs and tutorials 404.
|
||||
|
||||
## Why this matters
|
||||
|
||||
Langflow flows are **persisted user artifacts** running in production. The system that loads them is forgiving by design: it tolerates new fields, falls back on missing ones, applies type migrations on load. That tolerance is paid for by every contract in the table above. Break one and the system can no longer route around it — the flow stops working.
|
||||
|
||||
Treat this file as a checklist. If your change touches any row, the corresponding rule applies; no exceptions without an explicit deprecation plan.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Project Philosophy
|
||||
|
||||
Read this file before scoping any non-trivial work. Most off-narrative work comes from skipping it.
|
||||
|
||||
Langflow is a **visual flow builder first**. Every change should make sense to someone whose only interface is dragging nodes onto a canvas. Use these principles to scope work; if a proposed change fails one of them, stop and reconsider.
|
||||
|
||||
## Tenets
|
||||
|
||||
1. **Flows are user artifacts, not implementation details.** Flow JSON lives in users' databases and runs in production. Backwards compatibility for components is non-negotiable: never rename a class name, `name` attribute, input name, or output name; never remove an input or output; never tighten an output type; never remove methods from base classes like `LCModelComponent`. To change a component, add the new one alongside, set `legacy=True` + `replacement=[...]` on the old, and update `file_names_mapping` in tests so saved flows still load. See [CONTRACTS.md](./CONTRACTS.md) for the full surface.
|
||||
|
||||
2. **Every backend feature must land on the canvas.** If a capability cannot be expressed as a component (or a property of one) that a non-Python user can wire up, it is an SDK feature and belongs in `lfx`, not `langflow-base`. Backend changes that have no visual surface are almost always off-narrative.
|
||||
|
||||
3. **Components are the unit of work, not endpoints or services.** Before adding a route, store, or service, ask: which component does this serve? If the answer is "none yet," the route is premature. New REST endpoints without a component or UI consumer are off-narrative.
|
||||
|
||||
4. **`lfx` is the runtime, `langflow-base` is the platform, `langflow` is the distribution.** Components and the graph engine live in `lfx` (`src/lfx/src/lfx/components/`). API, auth, persistence, and multi-user concerns live in `langflow-base`. The `langflow` package is the integration that ships everything together. New code goes in the lowest layer that can host it. Boundaries are enforced one-way: `langflow` → `langflow-base` → `lfx`. See [ARCHITECTURE.md](./ARCHITECTURE.md).
|
||||
|
||||
5. **Don't add a config flag for something a builder-user would set on a node.** If a setting affects flow behavior, it's a component input. If it affects deployment, it's an env var. Hidden global flags that change component semantics break the visible-data-flow contract.
|
||||
|
||||
6. **Visible data flow beats clever magic.** Implicit context, hidden globals, and side-channel state make flows unreadable on the canvas. Pass data through inputs and outputs. If you find yourself reaching for a global, you're probably building something that doesn't belong in a node.
|
||||
|
||||
7. **Composition over capability.** Prefer adding small, single-purpose components users can wire together over one large component with many modes. The `If-Else` / `Current Date` style (one job, clear name, obvious icon) is the target shape.
|
||||
|
||||
8. **Every component needs `display_name`, `description`, `icon`, and a sensible category.** A component that doesn't render legibly in the sidebar shouldn't ship. The icon is part of the API, not decoration — pick a Lucide icon that matches the verb.
|
||||
|
||||
9. **The Playground is the test harness builder-users see.** A component is "done" when it works end-to-end in a flow on the canvas, not when its unit test passes. Use the Graph test pattern (build, `.set()`, `async_start`, validate) before claiming a feature works.
|
||||
|
||||
10. **Two audiences, one product: visual builders and Python devs.** When their needs conflict, the visual builder wins for `langflow-base` features and the Python dev wins for `lfx` SDK features. Don't compromise the canvas to make the SDK prettier, and don't compromise the SDK to make the canvas easier.
|
||||
|
||||
11. **It is not a fix if you didn't have evidence of the change fixing something.** Adding error handling, retries, type widening, or skipping a flaky test does not constitute a fix. Reproduce the failure, demonstrate the fix removes it, then ship. See [ANTI-PATTERNS.md](./ANTI-PATTERNS.md).
|
||||
|
||||
## How to apply
|
||||
|
||||
When scoping a task, walk through the tenets and name the ones the work serves. If a change fails tenet 1, 2, or 3, stop and surface the conflict before writing code. If you can't tell which tenet a change serves, it's probably off-narrative — ask.
|
||||
@@ -0,0 +1,163 @@
|
||||
# Testing
|
||||
|
||||
Conventions for backend tests. Frontend testing follows the standard Jest/Playwright patterns documented in `src/frontend/`.
|
||||
|
||||
## Project policy: avoid mocking
|
||||
|
||||
Prefer real integrations. The pattern of "mocked test passes, production fails" has cost us multiple release cycles — see [ANTI-PATTERNS.md](./ANTI-PATTERNS.md) for the relevant commit history. Use mocks only when:
|
||||
|
||||
- The dependency is an LLM and the test exercises pure logic → use `MockLanguageModel` from `tests/unit/mock_language_model.py`.
|
||||
- The dependency is genuinely unreliable and orthogonal to what's being tested.
|
||||
|
||||
Otherwise: hit the real thing, mark the test with `@pytest.mark.api_key_required` if it needs credentials, and let CI gate it.
|
||||
|
||||
## Built-in fixtures
|
||||
|
||||
### `client` (FastAPI test client)
|
||||
|
||||
Defined in `src/backend/tests/conftest.py`. Async `httpx.AsyncClient` connected to the full app via `ASGITransport` + `LifespanManager`. Auto-configured with in-memory SQLite and mocked env vars. Skip with `@pytest.mark.noclient`.
|
||||
|
||||
```python
|
||||
async def test_login_endpoint(client):
|
||||
response = await client.post("api/v1/login", data={"username": "foo", "password": "bar"})
|
||||
assert response.status_code == 200
|
||||
```
|
||||
|
||||
For authenticated routes, also use the `logged_in_headers` fixture.
|
||||
|
||||
## Component test base classes
|
||||
|
||||
Located in `src/backend/tests/base.py`.
|
||||
|
||||
| Base class | Creates `client`? | Use for |
|
||||
|---|---|---|
|
||||
| `ComponentTestBase` | No | Component version testing core logic |
|
||||
| `ComponentTestBaseWithClient` | Yes | Components that hit backend services during `run()` |
|
||||
| `ComponentTestBaseWithoutClient` | No | Pure-logic components |
|
||||
|
||||
### Required fixtures
|
||||
|
||||
Every subclass provides three fixtures:
|
||||
|
||||
1. **`component_class`** — the component class under test.
|
||||
2. **`default_kwargs`** — dict of kwargs to instantiate the component (can be empty).
|
||||
3. **`file_names_mapping`** — list of `VersionComponentMapping` entries mapping each historical Langflow version (from `src/backend/tests/constants.py::SUPPORTED_VERSIONS`) to module/file names. Use `DID_NOT_EXIST` for versions before the component was added.
|
||||
|
||||
```python
|
||||
from tests.base import ComponentTestBaseWithClient, VersionComponentMapping, DID_NOT_EXIST
|
||||
from langflow.components.my_namespace import MyComponent
|
||||
|
||||
class TestMyComponent(ComponentTestBaseWithClient):
|
||||
@pytest.fixture
|
||||
def component_class(self):
|
||||
return MyComponent
|
||||
|
||||
@pytest.fixture
|
||||
def default_kwargs(self):
|
||||
return {"foo": "bar"}
|
||||
|
||||
@pytest.fixture
|
||||
def file_names_mapping(self):
|
||||
return [
|
||||
VersionComponentMapping(version="1.1.1", module="my_module", file_name="my_component.py"),
|
||||
VersionComponentMapping(version="1.0.19", module="my_module", file_name=DID_NOT_EXIST),
|
||||
]
|
||||
```
|
||||
|
||||
The base class auto-provides:
|
||||
|
||||
- `test_latest_version` — instantiates and asserts `run()` doesn't return `None`.
|
||||
- `test_all_versions_have_a_file_name_defined` — ensures mapping completeness vs `SUPPORTED_VERSIONS`.
|
||||
- `test_component_versions` (parameterized) — builds the component from source for each supported version and asserts execution.
|
||||
|
||||
If you rename or move a component file, you **must** update `file_names_mapping` for every supported version, or saved flows on those versions will fail to load. See [CONTRACTS.md](./CONTRACTS.md) row 3.
|
||||
|
||||
## Graph testing pattern
|
||||
|
||||
The canonical pattern for tests that exercise the graph engine:
|
||||
|
||||
1. Build the graph with connected components.
|
||||
2. Connect them via `.set()` calls.
|
||||
3. Call `async_start` and iterate over the results.
|
||||
4. Validate the results.
|
||||
|
||||
Don't poke graph internals. If a test needs to reach into private state, the test is wrong or the API is wrong — fix the right one.
|
||||
|
||||
## Async patterns
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_component():
|
||||
result = await component.async_method()
|
||||
assert result is not None
|
||||
```
|
||||
|
||||
**Awaiting conditions, not sleeping:** never use `time.sleep` or `asyncio.sleep` to mask a race. Wait on a condition (`asyncio.wait_for`, an event, a queue read with timeout). Sleep-based tests are flaky by construction.
|
||||
|
||||
## Pytest markers
|
||||
|
||||
- `@pytest.mark.api_key_required` — needs an external API key; CI skips when absent.
|
||||
- `@pytest.mark.no_blockbuster` — skip blockbuster plugin.
|
||||
- `@pytest.mark.noclient` — skip the `client` fixture.
|
||||
- `@pytest.mark.asyncio` — async test (also `pytest-asyncio` auto-mode in some configs).
|
||||
|
||||
## Database tests
|
||||
|
||||
`test_database.py` may fail in batch and pass individually. If you touch DB models or migrations, run it sequentially as part of your verification:
|
||||
|
||||
```bash
|
||||
uv run pytest src/backend/tests/unit/test_database.py
|
||||
```
|
||||
|
||||
Never edit a past alembic migration. Run `make alembic-upgrade` end-to-end before claiming a migration works.
|
||||
|
||||
## API endpoint tests
|
||||
|
||||
```python
|
||||
async def test_flows_endpoint(client, logged_in_headers):
|
||||
flow_data = {"name": "Test", "data": {"nodes": [], "edges": []}}
|
||||
response = await client.post("api/v1/flows/", json=flow_data, headers=logged_in_headers)
|
||||
assert response.status_code == 201
|
||||
```
|
||||
|
||||
For event-stream endpoints, consume the NDJSON stream and validate event order:
|
||||
|
||||
```python
|
||||
async for line in response.aiter_lines():
|
||||
if not line:
|
||||
continue
|
||||
parsed = json.loads(line)
|
||||
# First event should be vertices_sorted; last should be end.
|
||||
```
|
||||
|
||||
## Flow testing with starter JSON
|
||||
|
||||
Use `tests/unit/build_utils.py` helpers:
|
||||
|
||||
```python
|
||||
from tests.unit.build_utils import create_flow, build_flow, get_build_events
|
||||
|
||||
flow_id = await create_flow(client, json_flow, logged_in_headers)
|
||||
build_response = await build_flow(client, flow_id, logged_in_headers)
|
||||
events_response = await get_build_events(client, job_id, logged_in_headers)
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
||||
```bash
|
||||
make unit_tests # All backend unit tests, parallel
|
||||
make unit_tests async=false # Sequential
|
||||
uv run pytest path/to/test.py # Single file
|
||||
uv run pytest path/to/test.py::test_name # Single test
|
||||
|
||||
# lfx tests specifically — must be run after `uv sync` inside src/lfx
|
||||
cd src/lfx && uv sync && uv run pytest
|
||||
```
|
||||
|
||||
## Verification checklist before claiming "tests pass"
|
||||
|
||||
- [ ] You actually ran the command, not just composed it.
|
||||
- [ ] You ran `test_database.py` sequentially if you touched DB code.
|
||||
- [ ] You ran the specific test file for any component you changed.
|
||||
- [ ] No skipped tests without a linked issue.
|
||||
- [ ] No mocks added at the wrong boundary.
|
||||
Reference in New Issue
Block a user