-
Notifications
You must be signed in to change notification settings - Fork 8.2k
feat: add option to skip message persistence in Component #8350
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8dd2736
fd6e667
5ee198a
7d66e73
68d7a6f
580a53f
cfd126e
a22ef24
fb5866b
008c31e
0dd0d47
8c92df9
b4caafd
3466414
7bb9c9c
f43d68e
4c81a6a
b873f0a
477b011
ad9007a
2ff2e04
92a4bab
0df5e94
2cb40e3
1aceeb5
7dd90df
8e8bdfc
49cc70f
7379597
39c5662
3aa24cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ | |
| from langflow.schema.data import Data | ||
| from langflow.schema.message import ErrorMessage, Message | ||
| from langflow.schema.properties import Source | ||
| from langflow.services.deps import get_db_service | ||
| from langflow.services.tracing.schema import Log | ||
| from langflow.template.field.base import UNDEFINED, Input, Output | ||
| from langflow.template.frontend_node.custom_components import ComponentFrontendNode | ||
|
|
@@ -162,6 +163,7 @@ def __init__(self, **kwargs) -> None: | |
| # Final setup | ||
| self._set_output_types(list(self._outputs_map.values())) | ||
| self.set_class_code() | ||
| self._database_available: bool | None = None | ||
|
|
||
| def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source: | ||
| source_dict = {} | ||
|
|
@@ -1460,6 +1462,16 @@ def is_connected_to_chat_output(self) -> bool: | |
|
|
||
| return has_chat_output(self.graph.get_vertex_neighbors(self._vertex)) | ||
|
|
||
| def _is_database_available(self) -> bool: | ||
| """Check if the database is available.""" | ||
| if ( | ||
| hasattr(self, "graph") | ||
| and hasattr(self.graph, "component_config") | ||
| and not self.graph.component_config.get("persist_messages", True) | ||
| ): | ||
| return False | ||
| return get_db_service().database_available | ||
|
|
||
| def _should_skip_message(self, message: Message) -> bool: | ||
| """Check if the message should be skipped based on vertex configuration and message type.""" | ||
| return ( | ||
|
|
@@ -1479,25 +1491,32 @@ async def send_message(self, message: Message, id_: str | None = None): | |
| message.session_id = session_id | ||
| if hasattr(message, "flow_id") and isinstance(message.flow_id, str): | ||
| message.flow_id = UUID(message.flow_id) | ||
| stored_message = await self._store_message(message) | ||
| if self._is_database_available(): | ||
| stored_message = await self._store_message(message) | ||
| self._stored_message_id = stored_message.id | ||
| else: | ||
| stored_message = message | ||
|
|
||
| self._stored_message_id = stored_message.id | ||
| try: | ||
| complete_message = "" | ||
| if ( | ||
| self._should_stream_message(stored_message, message) | ||
| self._should_stream_message(message) | ||
| and message is not None | ||
| and isinstance(message.text, AsyncIterator | Iterator) | ||
| ): | ||
| complete_message = await self._stream_message(message.text, stored_message) | ||
| stored_message.text = complete_message | ||
| stored_message = await self._update_stored_message(stored_message) | ||
| if self._is_database_available(): | ||
| stored_message = await self._update_stored_message(stored_message) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this mutate the First time looking at this area, so I'm a bit confused to the message assignment -- we've got |
||
| else: | ||
| stored_message = message | ||
| else: | ||
| # Only send message event for non-streaming messages | ||
| await self._send_message_event(stored_message, id_=id_) | ||
| except Exception: | ||
| # remove the message from the database | ||
| await delete_message(stored_message.id) | ||
| if self._is_database_available(): | ||
| await delete_message(stored_message.id) | ||
| raise | ||
| self.status = stored_message | ||
| return stored_message | ||
|
|
@@ -1539,12 +1558,9 @@ def _send_event(): | |
|
|
||
| await asyncio.to_thread(_send_event) | ||
|
|
||
| def _should_stream_message(self, stored_message: Message, original_message: Message) -> bool: | ||
| def _should_stream_message(self, original_message: Message) -> bool: | ||
| return bool( | ||
| hasattr(self, "_event_manager") | ||
| and self._event_manager | ||
| and stored_message.id | ||
| and not isinstance(original_message.text, str) | ||
| hasattr(self, "_event_manager") and self._event_manager and not isinstance(original_message.text, str) | ||
| ) | ||
|
|
||
| async def _update_stored_message(self, message: Message) -> Message: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4786,4 +4786,4 @@ | |
| "rag", | ||
| "q-a" | ||
| ] | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ class DatabaseService(Service): | |
| name = "database_service" | ||
|
|
||
| def __init__(self, settings_service: SettingsService): | ||
| self.engine: AsyncEngine | None = None | ||
| self._logged_pragma = False | ||
| self.settings_service = settings_service | ||
| if settings_service.settings.database_url is None: | ||
|
|
@@ -56,6 +57,7 @@ def __init__(self, settings_service: SettingsService): | |
| # register the event listener for sqlite as part of this class. | ||
| # Using decorator will make the method not able to use self | ||
| event.listen(Engine, "connect", self.on_connection) | ||
|
|
||
| if self.settings_service.settings.database_connection_retry: | ||
| self.engine = self._create_engine_with_retry() | ||
| else: | ||
|
|
@@ -68,6 +70,23 @@ def __init__(self, settings_service: SettingsService): | |
| else: | ||
| self.alembic_log_path = Path(langflow_dir) / alembic_log_file | ||
|
|
||
| self._database_available = False | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Perhaps this is in a follow up PR) Do you anticipate adding a new config to indicate "No database", or does not setting
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be easier for sure. My solution is a hacky one. Perhaps checking for the var first, then checking for the I'll try to refactor the settings soon and then we can have settings specifically for the initialization, allowing us to skip steps if we so desire |
||
|
|
||
| # Check if there's a message table in the database | ||
| async def _is_database_available(self) -> bool: | ||
| async with self.with_session() as session, session.bind.connect() as conn: | ||
| # Use run_sync to inspect the connection | ||
| def check_tables(conn): | ||
| inspector = inspect(conn) | ||
| return "message" in inspector.get_table_names() | ||
|
|
||
| self._database_available = await conn.run_sync(check_tables) | ||
| return self._database_available | ||
|
|
||
| @property | ||
| def database_available(self) -> bool: | ||
| return self._database_available | ||
|
|
||
| async def initialize_alembic_log_file(self): | ||
| # Ensure the directory and file for the alembic log file exists | ||
| await anyio.Path(self.alembic_log_path.parent).mkdir(parents=True, exist_ok=True) | ||
|
|
@@ -369,6 +388,7 @@ async def run_migrations(self, *, fix=False) -> None: | |
| logger.debug("Alembic not initialized") | ||
| should_initialize_alembic = True | ||
| await asyncio.to_thread(self._run_migrations, should_initialize_alembic, fix) | ||
| self._database_available = await self._is_database_available() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason this is checked here rather than in init?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function checks if the |
||
|
|
||
| @staticmethod | ||
| def try_downgrade_upgrade_until_success(alembic_cfg, retries=5) -> None: | ||
|
|
@@ -476,4 +496,6 @@ async def teardown(self) -> None: | |
| await teardown_superuser(settings_service, session) | ||
| except Exception: # noqa: BLE001 | ||
| logger.exception("Error tearing down database") | ||
| await self.engine.dispose() | ||
| if self.engine is not None: | ||
| await self.engine.dispose() | ||
| self.engine = None | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this is being used anymore