langflow/src/backend/tests/unit/build_utils.py
Gabriel Luiz Freitas Almeida 1530e61669
feat: add functionality to stream or poll events from the build process (#5940)
* refactor: simplify TaskService by removing Celery integration

* refactor: enhance AnyIO backend task management and error handling

* refactor: restructure flow building process and enhance event handling

* feat: implement QueueService for managing job queues and tasks

* feat: Add QueueService for managing job queues and task lifecycle

* feat!: Enhance flow building with QueueService integration for job management

* revert changes to async session

* feat: Integrate QueueService into lifespan management for task handling

* refactor: Enhance QueueService with robust lifecycle management and cleanup mechanisms

* refactor: Simplify docstring for get_queue_service function

* refactor: Update import statements in queue factory for clarity

* test: Improve chat endpoint tests with comprehensive build flow scenarios

* refactor: Streamline flow build event generation and queue management

* refactor: Improve flow build process with modular event handling and URL parameter management

* test: Enhance loop component tests with flow build and event handling utilities

* fix: Improve transaction logging with null flow_id handling and debug logging

* fix: Remove unnecessary db.refresh() in transaction logging

* feat: Add polling mode for build events with optional streaming

* feat: Add event delivery configuration option to settings

* feat: Implement polling for build events with configuration-driven streaming

* refactor: Enhance queue cleanup with improved logging and error handling

* test: Improve build event stream assertion with detailed error reporting

* test: Add benchmark test for build flow polling mechanism

* refactor: Remove redundant end event in flow event generation

* test: Update test_component_tool_with_api_key to use async flow

* Convert test method to async
* Update graph start method to use async_start()
* Add client parameter to test method
* Modify tool retrieval to use async method

* test: Add session ID generation in agent component test

* fix: Enhance error handling in LCAgentComponent for ExceptionWithMessageError

Add null checks to prevent potential AttributeError when handling agent message deletion

* test: Refactor test assertion for model name options in agent component

Simplify the assertion for checking "sonnet" in model name options by extracting the options to a variable first

* refactor: Improve type hints and imports in AnyIOTaskResult

* Add `__future__` import for type annotations
* Use conditional import for `Callable` and `TracebackType`
* Explicitly type `_traceback` attribute with `TracebackType | None`

* fix: Add null checks for event task in build events streaming

* refactor: Extract build and disconnect handling into separate modules

This commit separates the build and disconnect handling logic from the chat API module into dedicated files:

- Created `langflow/api/build.py` to house the flow generation and vertex building logic
- Created `langflow/api/disconnect.py` to manage the custom streaming response with disconnect handling
- Removed duplicate code from `langflow/api/v1/chat.py`
- Improved code organization and modularity

* refactor: Extract flow build and event handling methods in build API

This commit introduces two new methods in the build API to improve code organization and reusability:

- `start_flow_build()`: Centralizes the logic for creating a queue and starting a flow build task
- `get_flow_events_response()`: Handles retrieving flow events for both streaming and polling modes

The changes simplify the chat API endpoint implementations and reduce code duplication

* refactor: Deprecate /task endpoint and add informative error message

This commit marks the /task endpoint as deprecated and raises an HTTP 400 error with a clear message indicating the endpoint will be removed in a future version. The changes improve API clarity and guide users towards the recommended /run endpoint.

* refactor: Update log_transaction function return type annotation

Modify the return type hint for log_transaction to allow for potential None return, improving type safety and clarity in the transactions logging method.

* feat: Add configurable event delivery streaming option

Introduce support for configurable event delivery mode (streaming/polling) across frontend components:

- Add `shouldStreamEvents()` method in NodeStatus and IOModal
- Update flowStore to accept a `stream` parameter
- Modify buildFlowVerticesWithFallback to use the stream parameter directly
- Remove hardcoded polling logic in buildUtils

* fix: Add null check for flow_id in log_transaction function

Prevent logging transactions without a flow_id by returning None, ensuring data integrity and avoiding potential database errors

* chore: Update changes-filter to include chat API path

* fix: Add error handling and null checks in flow transaction deletion

Improve robustness of transaction and vertex build deletion by:
- Adding a null check for flow_id in delete_transactions_by_flow_id
- Wrapping deletion operations in try-except blocks to prevent failures
- Logging debug messages for any deletion errors

* fix: Remove redundant commit in vertex builds deletion

Remove unnecessary db.commit() from delete_vertex_builds_by_flow_id to prevent duplicate commits and simplify database transaction handling

* fix: Improve transaction logging with debug message and return value

Move debug logging from CRUD layer to utils to provide more context about logged transactions and ensure proper error handling

* fix: Improve error handling and transaction cleanup in active_user fixture

Enhance user and transaction cleanup process in test fixture by:
- Adding separate try-except blocks for transaction/vertex build deletion and user deletion
- Adding debug logging for potential errors during cleanup
- Ensuring proper session commits for each operation

* propagate parent task CancelledError instead of supressing it

Co-authored-by: Christophe Bornet <cbornet@hotmail.com>

* refactor: Rename queue service to job queue service

Restructure job queue management by:
- Renaming QueueService to JobQueueService
- Moving queue-related files to a new job_queue directory
- Updating import paths and service type references
- Enhancing job queue service with more robust async job management

* fix: Correct ServiceType enum reference for job queue service

Update the service type constant to match the recently renamed JobQueueService, ensuring consistent service type referencing across the application.

* refactor: Enhance JobQueueService with comprehensive logging and documentation

Improve the JobQueueService implementation by:
- Adding detailed docstrings with clear explanations of methods and attributes
- Implementing comprehensive logging throughout the service
- Enhancing error handling and logging for queue and task management
- Providing more context in log messages for debugging and monitoring

* docs: Improve stop method docstring for JobQueueService

Enhance the documentation for the stop method by:
- Providing a more detailed and precise description of the shutdown process
- Clarifying the steps involved in gracefully stopping the service
- Improving the explanation of resource cleanup and task cancellation

* fix: Properly handle task cancellation and propagate exceptions in JobQueueService

* fix: Enhance error handling and raise appropriate exceptions in JobQueueService methods

* improve docstring

Co-authored-by: Christophe Bornet <cbornet@hotmail.com>

* fix: Remove redundant exception raises in JobQueueService methods and improve cleanup logic

* fix: Improve logging during job cleanup and handle exceptions more appropriately

* feat: add utility to run tests with multiple event delivery modes

* feat: integrate withEventDeliveryModes utility in multiple test files

* refactor: replace status code assertions with httpx codes for clarity

* remove noqa comment and change argument name

Co-authored-by: Christophe Bornet <cbornet@hotmail.com>

* refactor: streamline event polling logic in get_flow_events_response

* refactor: use getattr for safer session attribute access

Co-author: @cbornet

* feat: add is_started method to JobQueueService

* refactor: modify JobQueueService start method and main.py queue service initialization

* feat: add ready state and teardown method to JobQueueService

* refactor: simplify job queue cleanup logic in JobQueueService

* refactor: improve error logging in active_user fixture

* refactor: improve AnyIO task management with TaskGroup and CancelScope

* refactor: Implement LimitedBackgroundTasks for controlled vertex build logging (#6312)

* feat: implement LimitedBackgroundTasks for controlled vertex build logging

* refactor: replace BackgroundTasks with LimitedBackgroundTasks in build_flow endpoint

* refactor: improve task cancellation error handling in JobQueueService

* refactor: Rename LimitedBackgroundTasks to LimitVertexBuildBackgroundTasks

* feat: Add EventDeliveryType enum for event delivery methods

* feat: Add polling constants for endpoint and streaming status

* refactor: Update buildFlowVerticesWithFallback to use polling constants

* refactor: Update event delivery handling to use EventDeliveryType enum

* [autofix.ci] apply automated fixes

* fix: Improve error handling for cancelled build tasks

Refactor generate_flow_events to properly handle and propagate CancelledError
- Remove unnecessary task creation and manual cancellation
- Directly await _build_vertex instead of creating a separate task
- Improve exception logging for cancelled tasks
- Ensure CancelledError is raised instead of being silently handled

* fix: trigger event_manager.on_end after error handling in generate_flow_events

---------

Co-authored-by: Christophe Bornet <cbornet@hotmail.com>
Co-authored-by: anovazzi1 <otavio2204@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-02-18 14:24:39 +00:00

75 lines
3 KiB
Python

import json
from typing import Any
from uuid import UUID
from httpx import AsyncClient, codes
async def create_flow(client: AsyncClient, flow_data: str, headers: dict[str, str]) -> UUID:
"""Create a flow and return its ID."""
response = await client.post("api/v1/flows/", json=json.loads(flow_data), headers=headers)
assert response.status_code == codes.CREATED
return UUID(response.json()["id"])
async def build_flow(
client: AsyncClient, flow_id: UUID, headers: dict[str, str], json: dict[str, Any] | None = None
) -> dict[str, Any]:
"""Start a flow build and return the job_id."""
if json is None:
json = {}
response = await client.post(f"api/v1/build/{flow_id}/flow", json=json, headers=headers)
assert response.status_code == codes.OK
return response.json()
async def get_build_events(client: AsyncClient, job_id: str, headers: dict[str, str]):
"""Get events for a build job."""
return await client.get(f"api/v1/build/{job_id}/events", headers=headers)
async def consume_and_assert_stream(response, job_id):
"""Consume the event stream and assert the expected event structure."""
count = 0
lines = []
async for line in response.aiter_lines():
# Skip empty lines (ndjson uses double newlines)
if not line:
continue
lines.append(line)
parsed = json.loads(line)
if "job_id" in parsed:
assert parsed["job_id"] == job_id
continue
if count == 0:
# First event should be vertices_sorted
assert parsed["event"] == "vertices_sorted", (
"Invalid first event. Expected 'vertices_sorted'. Full event stream:\n" + "\n".join(lines)
)
ids = parsed["data"]["ids"]
ids.sort()
assert ids == ["ChatInput-CIGht"], "Invalid ids in first event. Full event stream:\n" + "\n".join(lines)
to_run = parsed["data"]["to_run"]
to_run.sort()
assert to_run == ["ChatInput-CIGht", "ChatOutput-QA7ej", "Memory-amN4Z", "Prompt-iWbCC"], (
"Invalid to_run list in first event. Full event stream:\n" + "\n".join(lines)
)
elif count > 0 and count < 5:
# Next events should be end_vertex events
assert parsed["event"] == "end_vertex", (
f"Invalid event at position {count}. Expected 'end_vertex'. Full event stream:\n" + "\n".join(lines)
)
assert parsed["data"]["build_data"] is not None, (
f"Missing build_data at position {count}. Full event stream:\n" + "\n".join(lines)
)
elif count == 5:
# Final event should be end
assert parsed["event"] == "end", "Invalid final event. Expected 'end'. Full event stream:\n" + "\n".join(
lines
)
else:
raise ValueError(f"Unexpected event at position {count}. Full event stream:\n" + "\n".join(lines))
count += 1