Skip to content

Commit

Permalink
fix: abort connection on error preventing infinite build (#6050)
Browse files Browse the repository at this point in the history
* fix: abort connection on build errors

* fix: streamline error message formatting in ErrorMessage class

* test: remove timeout from waitForSelector in generalBugs-shard-10.spec.ts

---------

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
  • Loading branch information
anovazzi1 and ogabrielluiz authored Feb 3, 2025
1 parent d676aef commit d24e989
Show file tree
Hide file tree
Showing 6 changed files with 643 additions and 32 deletions.
1 change: 0 additions & 1 deletion src/backend/base/langflow/api/v1/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,6 @@ async def event_generator(event_manager: EventManager, client_consumed_queue: as
raise
event_manager.on_vertices_sorted(data={"ids": ids, "to_run": vertices_to_run})
await client_consumed_queue.get()

tasks = []
for vertex_id in ids:
task = asyncio.create_task(build_vertices(vertex_id, graph, client_consumed_queue, event_manager))
Expand Down
49 changes: 35 additions & 14 deletions src/backend/base/langflow/schema/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,18 +368,9 @@ def from_message(cls, message: Message, flow_id: str | None = None):
class ErrorMessage(Message):
"""A message class specifically for error messages with predefined error-specific attributes."""

def __init__(
self,
exception: BaseException,
session_id: str | None = None,
source: Source | None = None,
trace_name: str | None = None,
flow_id: UUID | str | None = None,
) -> None:
# This is done to avoid circular imports
if exception.__class__.__name__ == "ExceptionWithMessageError" and exception.__cause__ is not None:
exception = exception.__cause__
# Get the error reason
@staticmethod
def _format_markdown_reason(exception: BaseException) -> str:
"""Format the error reason with markdown formatting."""
reason = f"**{exception.__class__.__name__}**\n"
if hasattr(exception, "body") and isinstance(exception.body, dict) and "message" in exception.body:
reason += f" - **{exception.body.get('message')}**\n"
Expand All @@ -391,7 +382,37 @@ def __init__(
reason += f" - **Details:**\n\n```python\n{exception!s}\n```\n"
else:
reason += " - **An unknown error occurred.**\n"
return reason

@staticmethod
def _format_plain_reason(exception: BaseException) -> str:
"""Format the error reason without markdown."""
if hasattr(exception, "body") and isinstance(exception.body, dict) and "message" in exception.body:
reason = f"{exception.body.get('message')}\n"
elif hasattr(exception, "code"):
reason = f"Code: {exception.code}\n"
elif hasattr(exception, "args") and exception.args:
reason = f"{exception.args[0]}\n"
elif isinstance(exception, ValidationError):
reason = f"{exception!s}\n"
else:
reason = "An unknown error occurred.\n"
return reason

def __init__(
self,
exception: BaseException,
session_id: str | None = None,
source: Source | None = None,
trace_name: str | None = None,
flow_id: UUID | str | None = None,
) -> None:
# This is done to avoid circular imports
if exception.__class__.__name__ == "ExceptionWithMessageError" and exception.__cause__ is not None:
exception = exception.__cause__

plain_reason = self._format_plain_reason(exception)
markdown_reason = self._format_markdown_reason(exception)
# Get the sender ID
if trace_name:
match = re.search(r"\((.*?)\)", trace_name)
Expand All @@ -402,7 +423,7 @@ def __init__(
session_id=session_id,
sender=source.display_name if source else None,
sender_name=source.display_name if source else None,
text=reason,
text=plain_reason,
properties=Properties(
text_color="red",
background_color="red",
Expand All @@ -422,7 +443,7 @@ def __init__(
type="error",
component=source.display_name if source else None,
field=str(exception.field) if hasattr(exception, "field") else None,
reason=reason,
reason=markdown_reason,
solution=str(exception.solution) if hasattr(exception, "solution") else None,
traceback=traceback.format_exc(),
)
Expand Down
14 changes: 6 additions & 8 deletions src/frontend/src/stores/flowStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
false,
);
set({ isBuilding: false });
get().revertBuiltStatusFromBuilding();
get().setLockChat(false);
useAlertStore.getState().setErrorData({
title: "Build stopped",
});
},
isPending: true,
setHasIO: (hasIO) => {
Expand Down Expand Up @@ -757,14 +762,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
get().setLockChat(false);
},
onBuildUpdate: handleBuildUpdate,
onBuildStopped: () => {
get().setIsBuilding(false);
setErrorData({
title: "Build stopped",
});
get().revertBuiltStatusFromBuilding();
get().setLockChat(false);
},
onBuildError: (title: string, list: string[], elementList) => {
const idList =
(elementList
Expand All @@ -783,6 +780,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
setErrorData({ list, title });
get().setIsBuilding(false);
get().setLockChat(false);
get().buildController.abort();
},
onBuildStart: (elementList) => {
const idList = elementList
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/src/utils/buildUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ export async function buildFlowVertices({
onBuildStart(ids.map((id) => ({ id: id, reference: id })));
ids.forEach((id) => verticesStartTimeMs.set(id, Date.now()));
};

console.log("type", type);
console.log("data", data);
switch (type) {
case "vertices_sorted": {
const verticesToRun = data.to_run;
Expand Down Expand Up @@ -319,7 +320,7 @@ export async function buildFlowVertices({
case "error": {
if (data?.category === "error") {
useMessagesStore.getState().addMessage(data);
if (data?.properties?.source?.id === null) {
if (!data?.properties?.source?.id) {
onBuildError!("Error Building Flow", [data.text]);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ test(

await page.getByTestId("button_run_chat output").click();

await page.waitForSelector("text=built successfully", { timeout: 30000 });
await page.waitForSelector("text=built successfully");

await page.getByTestId("playground-btn-flow-io").click();

Expand Down
Loading

0 comments on commit d24e989

Please sign in to comment.