feat: Add suggestion message to API exception response (#3149)

* feat: suggest updating outdated components in API exception handling

Suggest updating the outdated components in the API exception handling. This commit adds a suggestion message to the API exception response when there are outdated components in the flow. The suggestion message provides the number of outdated components and recommends updating them. The suggestion is generated based on the list of outdated components obtained from the flow data.

* feat: refactor code

* [autofix.ci] apply automated fixes

* Update src/backend/base/langflow/exceptions/api.py

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>

* Update src/backend/base/langflow/api/utils.py

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>

* Update src/backend/base/langflow/exceptions/api.py

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>

* update function name

* [autofix.ci] apply automated fixes

* refactor: fix import casing in langflow.api.utils and langflow.exceptions.api

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>

* refactor: remove unused code and update exception handling in langflow.api.utils and langflow.exceptions.api

* [autofix.ci] apply automated fixes

* refactor: update exception handling and class  in langflow.api.utils and langflow.exceptions.api

* [autofix.ci] apply automated fixes

* update function name and refactor none flow logic

* [autofix.ci] apply automated fixes

* refactor: fix typo in get_suggestion_message function name

* refactor: improve get_suggestion_message function in langflow.api.utils

* refactor: add unit tests for get_suggestion_message and get_outdated_components functions

* refactor: add unit tests for APIException in langflow.exceptions.api

* refactor: improve test coverage for APIException and related functions

* [autofix.ci] apply automated fixes

* update file name

* refactor: update build_exception_body method in APIException to handle Exception type

* refactor: handle None flow data in get_components_versions

* [autofix.ci] apply automated fixes

* refactor: update useDeleteBuilds function signature in _builds API query

* fix: Fix test changing screen before request ended

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
anovazzi1 2024-08-07 12:26:24 -03:00 committed by GitHub
commit 48ffdbf760
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 172 additions and 3 deletions

View file

@ -215,3 +215,15 @@ def parse_exception(exc):
if hasattr(exc, "body"):
return exc.body["message"]
return str(exc)
def get_suggestion_message(outdated_components: list[str]) -> str:
"""Get the suggestion message for the outdated components."""
count = len(outdated_components)
if count == 0:
return "The flow contains no outdated components."
elif count == 1:
return f"The flow contains 1 outdated component. We recommend updating the following component: {outdated_components[0]}."
else:
components = ", ".join(outdated_components)
return f"The flow contains {count} outdated components. We recommend updating the following components: {components}."

View file

@ -23,7 +23,7 @@ from langflow.api.v1.schemas import (
)
from langflow.custom.custom_component.component import Component
from langflow.custom.utils import build_custom_component_template, get_instance_name
from langflow.exceptions.api import InvalidChatInputException
from langflow.exceptions.api import APIException, InvalidChatInputException
from langflow.graph.graph.base import Graph
from langflow.graph.schema import RunOutputs
from langflow.helpers.flow import get_flow_by_id_or_endpoint_name
@ -260,7 +260,7 @@ async def simplified_run_flow(
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
else:
logger.exception(exc)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
raise APIException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=exc, flow=flow) from exc
except InvalidChatInputException as exc:
logger.error(exc)
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
@ -275,7 +275,8 @@ async def simplified_run_flow(
runErrorMessage=str(exc),
),
)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
logger.exception(exc)
raise APIException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=exc, flow=flow) from exc
@router.post("/webhook/{flow_id_or_name}", response_model=dict, status_code=HTTPStatus.ACCEPTED)

View file

@ -1,2 +1,34 @@
from fastapi import HTTPException
from langflow.api.utils import get_suggestion_message
from langflow.services.database.models.flow.model import Flow
from langflow.services.database.models.flow.utils import get_outdated_components
from pydantic import BaseModel
class InvalidChatInputException(Exception):
pass
# create a pidantic documentation for this class
class ExceptionBody(BaseModel):
message: str | list[str]
traceback: str | list[str] | None = None
description: str | list[str] | None = None
code: str | None = None
suggestion: str | list[str] | None = None
class APIException(HTTPException):
def __init__(self, exception: Exception, flow: Flow | None = None, status_code: int = 500):
body = self.build_exception_body(exception, flow)
super().__init__(status_code=status_code, detail=body.model_dump_json())
@staticmethod
def build_exception_body(exc: str | list[str] | Exception, flow: Flow | None) -> ExceptionBody:
body = {"message": str(exc)}
if flow:
outdated_components = get_outdated_components(flow)
if outdated_components:
body["suggestion"] = get_suggestion_message(outdated_components)
excep = ExceptionBody(**body)
return excep

View file

@ -1,6 +1,7 @@
from typing import Optional
from fastapi import Depends
from langflow.utils.version import get_version_info
from sqlmodel import Session
from sqlalchemy import delete
@ -43,3 +44,26 @@ def get_all_webhook_components_in_flow(flow_data: dict | None):
if not flow_data:
return []
return [node for node in flow_data.get("nodes", []) if "Webhook" in node.get("id")]
def get_components_versions(flow: Flow):
versions: dict[str, str] = {}
if flow.data is None:
return versions
nodes = flow.data.get("nodes", [])
for node in nodes:
data = node.get("data", {})
data_node = data.get("node", {})
if "lf_version" in data_node:
versions[node["id"]] = data_node["lf_version"]
return versions
def get_outdated_components(flow: Flow):
component_versions = get_components_versions(flow)
lf_version = get_version_info()["version"]
outdated_components = []
for key, value in component_versions.items():
if value != lf_version:
outdated_components.append(key)
return outdated_components

View file

@ -0,0 +1,41 @@
from langflow.api.utils import get_suggestion_message
from unittest.mock import patch
from langflow.services.database.models.flow.utils import get_outdated_components
from langflow.utils.version import get_version_info
def test_get_suggestion_message():
# Test case 1: No outdated components
assert get_suggestion_message([]) == "The flow contains no outdated components."
# Test case 2: One outdated component
assert (
get_suggestion_message(["component1"])
== "The flow contains 1 outdated component. We recommend updating the following component: component1."
)
# Test case 3: Multiple outdated components
outdated_components = ["component1", "component2", "component3"]
expected_message = "The flow contains 3 outdated components. We recommend updating the following components: component1, component2, component3."
assert get_suggestion_message(outdated_components) == expected_message
def test_get_outdated_components():
# Mock data
flow = "mock_flow"
version = get_version_info()["version"]
mock_component_versions = {
"component1": version,
"component2": version,
"component3": "2.0",
}
# Expected result
expected_outdated_components = ["component3"]
with patch(
"langflow.services.database.models.flow.utils.get_components_versions", return_value=mock_component_versions
):
# Call the function with the mock flow
result = get_outdated_components(flow)
# Assert the result is as expected
assert result == expected_outdated_components

View file

@ -0,0 +1,58 @@
from unittest.mock import patch, Mock
from langflow.services.database.models.flow.model import Flow
def test_api_exception():
from langflow.exceptions.api import APIException, ExceptionBody
mock_exception = Exception("Test exception")
mock_flow = Mock(spec=Flow)
mock_outdated_components = ["component1", "component2"]
mock_suggestion_message = "Update component1, component2"
mock_component_versions = {
"component1": "1.0",
"component2": "1.0",
}
# Expected result
with patch(
"langflow.services.database.models.flow.utils.get_outdated_components", return_value=mock_outdated_components
):
with patch("langflow.api.utils.get_suggestion_message", return_value=mock_suggestion_message):
with patch(
"langflow.services.database.models.flow.utils.get_components_versions",
return_value=mock_component_versions,
):
# Create an APIException instance
api_exception = APIException(mock_exception, mock_flow)
# Expected body
expected_body = ExceptionBody(
message="Test exception",
suggestion="The flow contains 2 outdated components. We recommend updating the following components: component1, component2.",
)
# Assert the status code
assert api_exception.status_code == 500
# Assert the detail
assert api_exception.detail == expected_body.model_dump_json()
def test_api_exception_no_flow():
from langflow.exceptions.api import APIException, ExceptionBody
# Mock data
mock_exception = Exception("Test exception")
# Create an APIException instance without a flow
api_exception = APIException(mock_exception)
# Expected body
expected_body = ExceptionBody(message="Test exception")
# Assert the status code
assert api_exception.status_code == 500
# Assert the detail
assert api_exception.detail == expected_body.model_dump_json()

View file

@ -38,6 +38,7 @@ test("should delete a flow", async ({ page }) => {
await page.getByTestId("install-Website Content QA").click();
await page.getByText("Flow Installed Successfully.").nth(0).click();
await page.waitForSelector("text=My Collection", { timeout: 30000 });
await page.getByText("My Collection").nth(0).click();