chore: port Remove the diskcache dependency

port of https://github.com/langflow-ai/langflow/pull/12953
This commit is contained in:
Adam Aghili
2026-05-26 00:00:12 -04:00
committed by Adam-Aghili
parent e192f91eeb
commit 0a5f25f32b
11 changed files with 202 additions and 423 deletions

View File

@ -14,26 +14,26 @@ Langflow's default storage option is a [SQLite](https://www.sqlite.org/) databas
The default storage path depends on your operating system and installation method:
- **Langflow Desktop**:
- **macOS**: `/Users/<username>/.langflow/data/database.db`
- **Windows**: `C:\Users\<name>\AppData\Roaming\com.LangflowDesktop\data\database.db`
- **macOS**: `/Users/<username>/.langflow/data/database.db`
- **Windows**: `C:\Users\<name>\AppData\Roaming\com.LangflowDesktop\data\database.db`
- **Langflow OSS**
- **macOS/Windows/Linux/WSL with `uv pip install`**: `<path_to_venv>/lib/python3.12/site-packages/langflow/langflow.db` (Python version can vary. Database isn't shared between virtual environments because it is tied to the venv path.)
- **macOS/Windows/Linux/WSL with `git clone`**: `<path_to_clone>/src/backend/base/langflow/langflow.db`
- **macOS/Windows/Linux/WSL with `uv pip install`**: `<path_to_venv>/lib/python3.12/site-packages/langflow/langflow.db` (Python version can vary. Database isn't shared between virtual environments because it is tied to the venv path.)
- **macOS/Windows/Linux/WSL with `git clone`**: `<path_to_clone>/src/backend/base/langflow/langflow.db`
Langflow offers a few alternatives to the default database path:
* **Config directory**: Set `LANGFLOW_SAVE_DB_IN_CONFIG_DIR=True` to store the database in your Langflow config directory as set in [`LANGFLOW_CONFIG_DIR`](/logging).
- **Config directory**: Set `LANGFLOW_SAVE_DB_IN_CONFIG_DIR=True` to store the database in your Langflow config directory as set in [`LANGFLOW_CONFIG_DIR`](/logging).
* **External PostgreSQL database**: You can use an external PostgreSQL database for all of your Langflow storage.
For more information, see [Configure external memory](#configure-external-memory)
- **External PostgreSQL database**: You can use an external PostgreSQL database for all of your Langflow storage.
For more information, see [Configure external memory](#configure-external-memory)
External storage can be useful if you want to preserve the data after uninstalling Langflow or to share the same database between multiple virtual environments.
External storage can be useful if you want to preserve the data after uninstalling Langflow or to share the same database between multiple virtual environments.
* **Separate chat memory**: You can selectively use external storage for chat memory only, separate from other Langflow storage.
For more information, see [Store chat memory](#store-chat-memory).
- **Separate chat memory**: You can selectively use external storage for chat memory only, separate from other Langflow storage.
For more information, see [Store chat memory](#store-chat-memory).
* **No database**: To disable all database operations and run a no-op session, set `LANGFLOW_USE_NOOP_DATABASE=True` in your [Langflow environment variables](/environment-variables).
This is useful for testing when you don't want to persist any data.
- **No database**: To disable all database operations and run a no-op session, set `LANGFLOW_USE_NOOP_DATABASE=True` in your [Langflow environment variables](/environment-variables).
This is useful for testing when you don't want to persist any data.
## Langflow database tables
@ -78,31 +78,30 @@ LANGFLOW_DATABASE_URL=postgresql://user:password@localhost:5432/langflow
To fine-tune your database connection pool and timeout settings, you can set the following additional environment variables:
* `LANGFLOW_DATABASE_CONNECTION_RETRY`: Whether to retry lost connections to your Langflow database. If `true`, Langflow tries to connect to the database again if the connection fails. Default: `false`.
- `LANGFLOW_DATABASE_CONNECTION_RETRY`: Whether to retry lost connections to your Langflow database. If `true`, Langflow tries to connect to the database again if the connection fails. Default: `false`.
* `LANGFLOW_DB_CONNECT_TIMEOUT`: The number of seconds to wait before giving up on a lock to be released or establishing a connection to the database. This may be separate from the `pool_timeout` in `LANGFLOW_DB_CONNECTION_SETTINGS`. Default: 30.
- `LANGFLOW_DB_CONNECT_TIMEOUT`: The number of seconds to wait before giving up on a lock to be released or establishing a connection to the database. This may be separate from the `pool_timeout` in `LANGFLOW_DB_CONNECTION_SETTINGS`. Default: 30.
* `LANGFLOW_MIGRATION_LOCK_NAMESPACE`: Optional namespace identifier for PostgreSQL advisory lock during migrations. If not provided, a hash of the database URL will be used. Useful when multiple Langflow instances share the same database and need coordinated migration locking
- `LANGFLOW_MIGRATION_LOCK_NAMESPACE`: Optional namespace identifier for PostgreSQL advisory lock during migrations. If not provided, a hash of the database URL will be used. Useful when multiple Langflow instances share the same database and need coordinated migration locking
* `LANGFLOW_DB_CONNECTION_SETTINGS`: A JSON dictionary containing the following database connection pool settings:
- `LANGFLOW_DB_CONNECTION_SETTINGS`: A JSON dictionary containing the following database connection pool settings:
- `pool_size`: The base number of connections to keep open in the connection pool. Default: 20.
- `max_overflow`: Maximum number of connections that can be created in excess of `pool_size` if needed. Default: 30.
- `pool_timeout`: Number of seconds to wait for a connection from the pool before timing out. Default: 30.
- `pool_pre_ping`: If `true`, the pool tests connections for liveness upon each checkout. Default: `true`.
- `pool_recycle`: Number of seconds after which a connection is automatically recycled. Default: 1800 (30 minutes).
- `echo`: If `true`, SQL queries are logged for debugging purposes. Default: `false`.
- `pool_size`: The base number of connections to keep open in the connection pool. Default: 20.
- `max_overflow`: Maximum number of connections that can be created in excess of `pool_size` if needed. Default: 30.
- `pool_timeout`: Number of seconds to wait for a connection from the pool before timing out. Default: 30.
- `pool_pre_ping`: If `true`, the pool tests connections for liveness upon each checkout. Default: `true`.
- `pool_recycle`: Number of seconds after which a connection is automatically recycled. Default: 1800 (30 minutes).
- `echo`: If `true`, SQL queries are logged for debugging purposes. Default: `false`.
For example:
For example:
```text
LANGFLOW_DB_CONNECTION_SETTINGS='{"pool_size": 20, "max_overflow": 30, "pool_timeout": 30, "pool_pre_ping": true, "pool_recycle": 1800, "echo": false}'
```
```text
LANGFLOW_DB_CONNECTION_SETTINGS='{"pool_size": 20, "max_overflow": 30, "pool_timeout": 30, "pool_pre_ping": true, "pool_recycle": 1800, "echo": false}'
```
Don't use the deprecated environment variables `LANGFLOW_DB_POOL_SIZE` or `LANGFLOW_DB_MAX_OVERFLOW`.
Instead, use `pool_size` and `max_overflow` in `LANGFLOW_DB_CONNECTION_SETTINGS`.
Don't use the deprecated environment variables `LANGFLOW_DB_POOL_SIZE` or `LANGFLOW_DB_MAX_OVERFLOW`.
Instead, use `pool_size` and `max_overflow` in `LANGFLOW_DB_CONNECTION_SETTINGS`.
* `LANGFLOW_MIGRATION_LOCK_NAMESPACE`: Optional namespace for PostgreSQL advisory locks used during database migrations. This is useful when running multiple Langflow instances that share the same PostgreSQL database. Each instance should use a unique namespace to avoid conflicts. If not set, Langflow uses a default namespace. This setting only applies when using PostgreSQL as your database backend.
- `LANGFLOW_MIGRATION_LOCK_NAMESPACE`: Optional namespace for PostgreSQL advisory locks used during database migrations. This is useful when running multiple Langflow instances that share the same PostgreSQL database. Each instance should use a unique namespace to avoid conflicts. If not set, Langflow uses a default namespace. This setting only applies when using PostgreSQL as your database backend.
## Configure cache memory
@ -117,15 +116,15 @@ Langflow officially supports only the default asynchronous, in-memory cache, whi
Other cache options, such as Redis, are experimental and can change without notice.
If you want to use a non-default cache setting, you can use the following environment variables:
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `LANGFLOW_CACHE_TYPE` | String | `async` | Set the cache type for Langflow's internal caching system. Possible values: `async`, `redis`, `memory`, `disk`. If you set the type to `redis`, then you must also set the `LANGFLOW_REDIS_*` environment variables. |
| `LANGFLOW_LANGCHAIN_CACHE` | String | `InMemoryCache` | Set the cache storage type for the LangChain caching system (a Langflow dependency), either `InMemoryCache` or `SQLiteCache`. |
| `LANGFLOW_REDIS_HOST` | String | `localhost` | Redis server hostname if `LANGFLOW_CACHE_TYPE=redis`. |
| `LANGFLOW_REDIS_PORT` | Integer | `6379` | Redis server port if `LANGFLOW_CACHE_TYPE=redis`. |
| `LANGFLOW_REDIS_DB` | Integer | `0` | Redis database number if `LANGFLOW_CACHE_TYPE=redis`. |
| `LANGFLOW_REDIS_CACHE_EXPIRE` | Integer | `3600` | Cache expiration time in seconds if `LANGFLOW_CACHE_TYPE=redis`. |
| `LANGFLOW_REDIS_PASSWORD` | String | Not set | Optional password for Redis authentication if `LANGFLOW_CACHE_TYPE=redis`. |
| Variable | Type | Default | Description |
| ----------------------------- | ------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LANGFLOW_CACHE_TYPE` | String | `async` | Set the cache type for Langflow's internal caching system. Possible values: `async`, `redis`, `memory`. If you set the type to `redis`, then you must also set the `LANGFLOW_REDIS_*` environment variables. The `disk` backend was removed in 1.9.4 — switch to `async` for in-memory or `redis` for cross-worker. |
| `LANGFLOW_LANGCHAIN_CACHE` | String | `InMemoryCache` | Set the cache storage type for the LangChain caching system (a Langflow dependency), either `InMemoryCache` or `SQLiteCache`. |
| `LANGFLOW_REDIS_HOST` | String | `localhost` | Redis server hostname if `LANGFLOW_CACHE_TYPE=redis`. |
| `LANGFLOW_REDIS_PORT` | Integer | `6379` | Redis server port if `LANGFLOW_CACHE_TYPE=redis`. |
| `LANGFLOW_REDIS_DB` | Integer | `0` | Redis database number if `LANGFLOW_CACHE_TYPE=redis`. |
| `LANGFLOW_REDIS_CACHE_EXPIRE` | Integer | `3600` | Cache expiration time in seconds if `LANGFLOW_CACHE_TYPE=redis`. |
| `LANGFLOW_REDIS_PASSWORD` | String | Not set | Optional password for Redis authentication if `LANGFLOW_CACHE_TYPE=redis`. |
## Store chat memory
@ -161,27 +160,27 @@ For example, if you use user IDs as session IDs, then each user's chat history i
Where and how chat memory is stored depends on the components used in your flow:
* **Agent component**: This component has built-in chat memory that is enabled by default.
This memory allows the agent to retrieve and reference messages from previous conversations associated with the same session ID.
All messages are stored in [Langflow storage](#storage-options-and-paths), and the component provides minimal memory configuration options, such as the number of messages to retrieve.
- **Agent component**: This component has built-in chat memory that is enabled by default.
This memory allows the agent to retrieve and reference messages from previous conversations associated with the same session ID.
All messages are stored in [Langflow storage](#storage-options-and-paths), and the component provides minimal memory configuration options, such as the number of messages to retrieve.
The **Agent** component's built-in chat memory is sufficient for most use cases.
The **Agent** component's built-in chat memory is sufficient for most use cases.
If you want to use external chat memory storage, retrieve memories outside the context of a chat, or use chat memory with a language model component (not an agent), you must use the **Message History** component (with or without a third-party chat memory component).
If you want to use external chat memory storage, retrieve memories outside the context of a chat, or use chat memory with a language model component (not an agent), you must use the **Message History** component (with or without a third-party chat memory component).
* **Message History component**: By default, this component stores and retrieves memories from Langflow storage, unless you attach a third-party chat memory component. It provides a few more options for sorting and filtering memories, although most of these options are also built-in to the **Agent** component as configurable or fixed parameters.
- **Message History component**: By default, this component stores and retrieves memories from Langflow storage, unless you attach a third-party chat memory component. It provides a few more options for sorting and filtering memories, although most of these options are also built-in to the **Agent** component as configurable or fixed parameters.
You can use the **Message History** component with or without a language model or agent.
For example, if you need to retrieve data from memories outside of chat, you can use the **Message History** component to fetch that data directly from your chat memory database without feeding it into a chat.
You can use the **Message History** component with or without a language model or agent.
For example, if you need to retrieve data from memories outside of chat, you can use the **Message History** component to fetch that data directly from your chat memory database without feeding it into a chat.
* **Third-party chat memory components**: Use one of these components only if you need to store or retrieve chat memories from a dedicated external chat memory database.
Typically, this is necessary only if you have specific storage needs that aren't met by Langflow storage.
For example, if you want to manage chat memory data by directly working with the database, or if you want to use a different database than the default Langflow storage.
- **Third-party chat memory components**: Use one of these components only if you need to store or retrieve chat memories from a dedicated external chat memory database.
Typically, this is necessary only if you have specific storage needs that aren't met by Langflow storage.
For example, if you want to manage chat memory data by directly working with the database, or if you want to use a different database than the default Langflow storage.
For more information and examples, see [**Message History** component](/message-history) and [Agent memory](/agents#agent-memory).
## See also
* [Langflow file management](/concepts-file-management)
* [Langflow logs](/logging)
* [Langflow environment variables](/environment-variables)
- [Langflow file management](/concepts-file-management)
- [Langflow logs](/logging)
- [Langflow environment variables](/environment-variables)

View File

@ -115,7 +115,6 @@ cassio = [
"cassio>=0.1.7"
]
local = [
"llama-cpp-python~=0.2.0",
"sentence-transformers>=2.3.1",
"ctransformers>=0.2.10"
]

View File

@ -1,95 +0,0 @@
import asyncio
import pickle
import time
from typing import Generic
from diskcache import Cache
from lfx.log.logger import logger
from lfx.services.cache.utils import CACHE_MISS
from langflow.services.cache.base import AsyncBaseCacheService, AsyncLockType
class AsyncDiskCache(AsyncBaseCacheService, Generic[AsyncLockType]):
def __init__(self, cache_dir, max_size=None, expiration_time=3600) -> None:
self.cache = Cache(cache_dir)
# Let's clear the cache for now to maintain a similar
# behavior as the in-memory cache
# Later we should implement endpoints for the frontend to grab
# output logs from the cache
if len(self.cache) > 0:
self.cache.clear()
self.lock = asyncio.Lock()
self.max_size = max_size
self.expiration_time = expiration_time
async def get(self, key, lock: asyncio.Lock | None = None):
if not lock:
async with self.lock:
return await asyncio.to_thread(self._get, key)
else:
return await asyncio.to_thread(self._get, key)
def _get(self, key):
item = self.cache.get(key, default=None)
if item:
if time.time() - item["time"] < self.expiration_time:
self.cache.touch(key) # Refresh the expiry time
return pickle.loads(item["value"]) if isinstance(item["value"], bytes) else item["value"]
logger.info(f"Cache item for key '{key}' has expired and will be deleted.")
self.cache.delete(key) # Log before deleting the expired item
return CACHE_MISS
async def set(self, key, value, lock: asyncio.Lock | None = None) -> None:
if not lock:
async with self.lock:
await self._set(key, value)
else:
await self._set(key, value)
async def _set(self, key, value) -> None:
if self.max_size and len(self.cache) >= self.max_size:
await asyncio.to_thread(self.cache.cull)
item = {"value": pickle.dumps(value) if not isinstance(value, str | bytes) else value, "time": time.time()}
await asyncio.to_thread(self.cache.set, key, item)
async def delete(self, key, lock: asyncio.Lock | None = None) -> None:
if not lock:
async with self.lock:
await self._delete(key)
else:
await self._delete(key)
async def _delete(self, key) -> None:
await asyncio.to_thread(self.cache.delete, key)
async def clear(self, lock: asyncio.Lock | None = None) -> None:
if not lock:
async with self.lock:
await self._clear()
else:
await self._clear()
async def _clear(self) -> None:
await asyncio.to_thread(self.cache.clear)
async def upsert(self, key, value, lock: asyncio.Lock | None = None) -> None:
if not lock:
async with self.lock:
await self._upsert(key, value)
else:
await self._upsert(key, value)
async def _upsert(self, key, value) -> None:
existing_value = await asyncio.to_thread(self._get, key)
if existing_value is not CACHE_MISS and isinstance(existing_value, dict) and isinstance(value, dict):
existing_value.update(value)
value = existing_value
await self.set(key, value)
async def contains(self, key) -> bool:
return await asyncio.to_thread(self.cache.__contains__, key)
async def teardown(self) -> None:
# Clean up the cache directory
self.cache.clear(retry=True)

View File

@ -5,7 +5,6 @@ from typing import TYPE_CHECKING
from lfx.log.logger import logger
from typing_extensions import override
from langflow.services.cache.disk import AsyncDiskCache
from langflow.services.cache.service import AsyncInMemoryCache, CacheService, RedisCache, ThreadingInMemoryCache
from langflow.services.factory import ServiceFactory
@ -36,9 +35,4 @@ class CacheServiceFactory(ServiceFactory):
return ThreadingInMemoryCache(expiration_time=settings_service.settings.cache_expire)
if settings_service.settings.cache_type == "async":
return AsyncInMemoryCache(expiration_time=settings_service.settings.cache_expire)
if settings_service.settings.cache_type == "disk":
return AsyncDiskCache(
cache_dir=settings_service.settings.config_dir,
expiration_time=settings_service.settings.cache_expire,
)
return None

View File

@ -1,13 +1,20 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing_extensions import override
from langflow.services.factory import ServiceFactory
from langflow.services.flow_events.service import FlowEventsService
if TYPE_CHECKING:
from lfx.services.settings.service import SettingsService
class FlowEventsServiceFactory(ServiceFactory):
def __init__(self) -> None:
super().__init__(FlowEventsService)
@override
def create(self):
return FlowEventsService()
def create(self, settings_service: SettingsService):
return FlowEventsService(cache_dir=settings_service.settings.cache_dir)

View File

@ -1,14 +1,13 @@
from __future__ import annotations
import json
import sqlite3
import tempfile
import threading
import time
from dataclasses import asdict, dataclass
from dataclasses import dataclass
from pathlib import Path
from typing import Literal, get_args
from diskcache import Cache
from langflow.services.base import Service
FLOW_EVENT_TYPES = Literal[
@ -29,12 +28,25 @@ class FlowEvent:
summary: str = ""
class FlowEventsService(Service):
"""Disk-backed event queue keyed by flow_id.
_SCHEMA = """
CREATE TABLE IF NOT EXISTS flow_events (
flow_id TEXT NOT NULL,
ts REAL NOT NULL,
type TEXT NOT NULL,
summary TEXT NOT NULL DEFAULT '',
expires_at REAL NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_flow_events_flow_ts ON flow_events(flow_id, ts);
CREATE INDEX IF NOT EXISTS idx_flow_events_expires ON flow_events(expires_at);
"""
Uses diskcache for cross-worker visibility (multiple uvicorn/gunicorn workers
share the same SQLite-backed cache directory). TTL-based cleanup is handled
by diskcache's built-in expiry.
class FlowEventsService(Service):
"""SQLite-backed event queue keyed by flow_id.
Uses Python's stdlib sqlite3 in WAL mode for cross-worker visibility (multiple
uvicorn/gunicorn workers share the same on-disk database file). TTL-based
cleanup is performed lazily on each write and read.
Limitations:
- Events are ephemeral: lost on disk cleanup or container restart.
@ -49,28 +61,67 @@ class FlowEventsService(Service):
SETTLE_TIMEOUT: float = 10.0
MAX_EVENTS_PER_FLOW: int = 1000
_VALID_EVENT_TYPES: frozenset[str] = frozenset(get_args(FLOW_EVENT_TYPES))
def __init__(self, cache_dir: str | Path | None = None) -> None:
if cache_dir is None:
cache_dir = Path(tempfile.gettempdir()) / "langflow_flow_events"
self._cache = Cache(str(cache_dir))
_VALID_EVENT_TYPES: frozenset[str] = frozenset(get_args(FLOW_EVENT_TYPES))
cache_dir = Path(cache_dir)
cache_dir.mkdir(parents=True, exist_ok=True)
self._db_path = cache_dir / "flow_events.sqlite"
# isolation_level=None puts pysqlite in autocommit mode so we manage
# transactions explicitly via BEGIN/COMMIT.
self._conn = sqlite3.connect(
str(self._db_path),
isolation_level=None,
check_same_thread=False,
timeout=5.0,
)
# Serialize use of the single connection across asyncio tasks / FastAPI threads
# in this worker. WAL handles cross-worker concurrency.
self._lock = threading.Lock()
with self._lock:
self._conn.execute("PRAGMA journal_mode=WAL")
self._conn.execute("PRAGMA synchronous=NORMAL")
self._conn.execute("PRAGMA busy_timeout=5000")
self._conn.executescript(_SCHEMA)
def append(self, flow_id: str, event_type: str, summary: str = "") -> FlowEvent:
if event_type not in self._VALID_EVENT_TYPES:
msg = f"Invalid event type: {event_type!r}. Must be one of {sorted(self._VALID_EVENT_TYPES)}"
raise ValueError(msg)
event = FlowEvent(type=event_type, timestamp=time.time(), summary=summary)
key = f"flow_events:{flow_id}"
now = time.time()
event = FlowEvent(type=event_type, timestamp=now, summary=summary)
expires_at = now + self.TTL_SECONDS
with self._cache.transact():
raw = self._cache.get(key, default=None)
events: list[dict] = json.loads(raw) if raw else []
events.append(asdict(event))
# Trim to max size
if len(events) > self.MAX_EVENTS_PER_FLOW:
events = events[-self.MAX_EVENTS_PER_FLOW :]
self._cache.set(key, json.dumps(events), expire=self.TTL_SECONDS)
with self._lock:
self._conn.execute("BEGIN IMMEDIATE")
try:
# Opportunistic TTL cleanup across all flows.
self._conn.execute("DELETE FROM flow_events WHERE expires_at < ?", (now,))
self._conn.execute(
"INSERT INTO flow_events (flow_id, ts, type, summary, expires_at) VALUES (?, ?, ?, ?, ?)",
(flow_id, now, event_type, summary, expires_at),
)
# Bound per-flow size: keep only the most recent MAX_EVENTS_PER_FLOW rows.
# Order by (ts, rowid) so events appended in the same microsecond have a
# stable, insertion-aware ordering when picking which rows to drop.
self._conn.execute(
"""
DELETE FROM flow_events
WHERE rowid IN (
SELECT rowid FROM flow_events
WHERE flow_id = ?
ORDER BY ts DESC, rowid DESC
LIMIT -1 OFFSET ?
)
""",
(flow_id, self.MAX_EVENTS_PER_FLOW),
)
self._conn.execute("COMMIT")
except Exception:
self._conn.execute("ROLLBACK")
raise
return event
@ -82,9 +133,19 @@ class FlowEventsService(Service):
- A flow_settled event exists after `since`, OR
- The most recent event is older than SETTLE_TIMEOUT seconds.
"""
key = f"flow_events:{flow_id}"
raw = self._cache.get(key, default=None)
all_events = [FlowEvent(**e) for e in json.loads(raw)] if raw else []
now = time.time()
with self._lock:
rows = self._conn.execute(
"""
SELECT type, ts, summary
FROM flow_events
WHERE flow_id = ? AND expires_at >= ?
ORDER BY ts ASC, rowid ASC
""",
(flow_id, now),
).fetchall()
all_events = [FlowEvent(type=r[0], timestamp=r[1], summary=r[2]) for r in rows]
after = [e for e in all_events if e.timestamp > since]
@ -100,4 +161,5 @@ class FlowEventsService(Service):
return after, settled
async def teardown(self) -> None:
self._cache.close()
with self._lock:
self._conn.close()

View File

@ -77,7 +77,6 @@ dependencies = [
"filelock>=3.20.1,<4.0.0",
"grandalf>=0.8.0,<1.0.0",
"spider-client>=0.0.27,<1.0.0",
"diskcache>=5.6.3,<6.0.0",
"clickhouse-connect==0.7.19",
"assemblyai>=0.33.0,<1.0.0",
"fastapi-pagination>=0.13.1,<1.0.0",
@ -182,7 +181,6 @@ postgresql = [
]
local = [
"llama-cpp-python>=0.2.0",
"sentence-transformers>=2.0.0",
"ctransformers>=0.2"
]
@ -234,7 +232,6 @@ sambanova = ["langchain-sambanova~=1.0.0"]
groq = ["langchain-groq~=1.1.1"]
# Individual local LLM providers
llama-cpp = ["llama-cpp-python~=0.2.0"]
sentence-transformers = ["sentence-transformers>=2.3.1"]
ctransformers = ["ctransformers>=0.2.10"]
@ -252,7 +249,6 @@ pytube = ["pytube==15.0.0"]
# Individual agent frameworks
# crewai is commented out due to httpx version conflict
# crewai = ["crewai>=0.126.0"]
dspy = ["dspy-ai==2.5.41"]
smolagents = ["smolagents>=1.8.0"]
# Individual tools
@ -410,7 +406,6 @@ complete = [
"langflow-base[docx]",
"langflow-base[pytube]",
# "langflow-base[crewai]", # commented out due to httpx version conflict
"langflow-base[dspy]",
"langflow-base[smolagents]",
"langflow-base[beautifulsoup]",
"langflow-base[serpapi]",

123
src/backend/base/uv.lock generated
View File

@ -718,14 +718,16 @@ wheels = [
[[package]]
name = "asyncer"
version = "0.0.8"
version = "0.0.17"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "sniffio" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ff/67/7ea59c3e69eaeee42e7fc91a5be67ca5849c8979acac2b920249760c6af2/asyncer-0.0.8.tar.gz", hash = "sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c", size = 18217, upload-time = "2024-08-24T23:15:36.449Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d2/4c/62b6044679e08788322bbd0dee5b487a6f7f60bb4e2bd45617ff0d94d1e3/asyncer-0.0.17.tar.gz", hash = "sha256:8a41e185e7ec2ecd583c269d72907a0f9f832e744b6c7474aeb21e349c4becf4", size = 19516, upload-time = "2026-02-21T16:35:54.068Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/04/15b6ca6b7842eda2748bda0a0af73f2d054e9344320f8bba01f994294bcb/asyncer-0.0.8-py3-none-any.whl", hash = "sha256:5920d48fc99c8f8f0f1576e1882f5022885589c5fcbc46ce4224ec3e53776eeb", size = 9209, upload-time = "2024-08-24T23:15:35.317Z" },
{ url = "https://files.pythonhosted.org/packages/2a/c5/b72735a095b4b3170b34150e89314a40dd0450d0eb6746b331cb664479d1/asyncer-0.0.17-py3-none-any.whl", hash = "sha256:b0055950e094fb84fd8d21611c7e7b6f5715ddcb57c522c058f64c20badd1438", size = 9252, upload-time = "2026-02-21T16:35:55.022Z" },
]
[[package]]
@ -2761,15 +2763,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" },
]
[[package]]
name = "diskcache"
version = "5.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" },
]
[[package]]
name = "distlib"
version = "0.4.0"
@ -2978,48 +2971,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" },
]
[[package]]
name = "dspy"
version = "3.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "asyncer" },
{ name = "cachetools" },
{ name = "cloudpickle" },
{ name = "diskcache" },
{ name = "gepa" },
{ name = "json-repair" },
{ name = "litellm" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "openai" },
{ name = "optuna" },
{ name = "orjson" },
{ name = "pydantic" },
{ name = "regex" },
{ name = "requests" },
{ name = "tenacity" },
{ name = "tqdm" },
{ name = "xxhash" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d9/bd/339e1d949aa24e50b6edca8574cf8afbff005a1dfadc2d5b82a0003fa7da/dspy-3.1.0.tar.gz", hash = "sha256:3c1660cb687411f064509cd7ac47ed0231761f50ed92823cbbb59b3af2885702", size = 242611, upload-time = "2026-01-06T18:50:18.655Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/34/64e900657cad3c4df8a417fccbc4370ca851864edbd0f468210f1b57d084/dspy-3.1.0-py3-none-any.whl", hash = "sha256:b9f4d42cad9ac32b13fdf085dd3246c8ee8339c759cb291c5e7b7f9f5fb5dd72", size = 291317, upload-time = "2026-01-06T18:50:17.559Z" },
]
[[package]]
name = "dspy-ai"
version = "2.5.41"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "dspy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d6/25/94659c581e69fe11237294aeadf55785abec09361aa2d3a63bd0abc39d82/dspy-ai-2.5.41.tar.gz", hash = "sha256:a2804434eb3354d6e41f59a448938886914fed0c502eb42503569776a3f2f002", size = 258174, upload-time = "2024-11-29T03:17:24.301Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/50/f715b62acfe29718c3575dde16eb35e3ab6a5642652b13fb229bd758c040/dspy_ai-2.5.41-py3-none-any.whl", hash = "sha256:4f366a6b610ee93fd04b92b0dff2f78b24e23ee74c71d20bcf9fedcbf0d7aa97", size = 339737, upload-time = "2024-11-29T03:17:22.8Z" },
]
[[package]]
name = "duckdb"
version = "1.5.3"
@ -4178,15 +4129,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/af/90/3bc780df088d439714af8295196a4332a26559ae66fd99865e36f92efa9e/geomet-1.1.0-py3-none-any.whl", hash = "sha256:4372fe4e286a34acc6f2e9308284850bd8c4aa5bc12065e2abbd4995900db12f", size = 31522, upload-time = "2023-11-14T15:43:35.305Z" },
]
[[package]]
name = "gepa"
version = "0.0.22"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ae/be/9a4c31c65c4910e73bd4a316df99347681bce4f2ef6d10877cc1ed8d57da/gepa-0.0.22.tar.gz", hash = "sha256:0b13a644efd2af52186e456eaad2ae28fcf88c6f796a9c999910c587863d4315", size = 116337, upload-time = "2025-11-10T21:39:27.044Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/0e/d41272b28f2163f5bf840b508cbaf4a0bc01eaeaa6e5d447558d1050eb3b/gepa-0.0.22-py3-none-any.whl", hash = "sha256:ff57ee0442399a5cc62d01411935476bc80cfa8f1c318bed82e3db4c2c834665", size = 119666, upload-time = "2025-11-10T21:39:26.028Z" },
]
[[package]]
name = "gitdb"
version = "4.0.12"
@ -6635,7 +6577,6 @@ dependencies = [
{ name = "clickhouse-connect" },
{ name = "cryptography" },
{ name = "defusedxml" },
{ name = "diskcache" },
{ name = "docstring-parser" },
{ name = "duckdb" },
{ name = "dynaconf" },
@ -6750,7 +6691,6 @@ all = [
{ name = "ddgs" },
{ name = "docling", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" },
{ name = "docling-core" },
{ name = "dspy-ai" },
{ name = "easyocr", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" },
{ name = "elasticsearch" },
{ name = "faiss-cpu", version = "1.9.0.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
@ -6920,7 +6860,6 @@ complete = [
{ name = "ddgs" },
{ name = "docling", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" },
{ name = "docling-core" },
{ name = "dspy-ai" },
{ name = "easyocr", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" },
{ name = "elasticsearch" },
{ name = "faiss-cpu", version = "1.9.0.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
@ -7038,9 +6977,6 @@ docling = [
docx = [
{ name = "python-docx" },
]
dspy = [
{ name = "dspy-ai" },
]
duckduckgo = [
{ name = "ddgs" },
]
@ -7132,12 +7068,8 @@ lark = [
litellm = [
{ name = "litellm" },
]
llama-cpp = [
{ name = "llama-cpp-python" },
]
local = [
{ name = "ctransformers" },
{ name = "llama-cpp-python" },
{ name = "sentence-transformers" },
]
markdown = [
@ -7361,11 +7293,9 @@ requires-dist = [
{ name = "datasets", marker = "extra == 'datasets'", specifier = ">2.14.7,<5.0.0" },
{ name = "ddgs", marker = "extra == 'duckduckgo'", specifier = ">=9.0.0" },
{ name = "defusedxml", specifier = ">=0.7.1,<1.0.0" },
{ name = "diskcache", specifier = ">=5.6.3,<6.0.0" },
{ name = "docling", marker = "(platform_machine != 'x86_64' and extra == 'docling') or (sys_platform != 'darwin' and extra == 'docling')", specifier = ">=2.36.1,<3.0.0" },
{ name = "docling-core", marker = "extra == 'docling'", specifier = ">=2.77.0,<3.0.0" },
{ name = "docstring-parser", specifier = ">=0.16,<1.0.0" },
{ name = "dspy-ai", marker = "extra == 'dspy'", specifier = "==2.5.41" },
{ name = "duckdb", specifier = ">=1.0.0,<2.0.0" },
{ name = "dynaconf", specifier = ">=3.2.13,<4.0.0" },
{ name = "easyocr", marker = "(platform_machine != 'x86_64' and extra == 'easyocr') or (sys_platform != 'darwin' and extra == 'easyocr')", specifier = ">=1.7.2,<2.0.0" },
@ -7466,7 +7396,6 @@ requires-dist = [
{ name = "langflow-base", extras = ["datasets"], marker = "extra == 'complete'" },
{ name = "langflow-base", extras = ["docling"], marker = "extra == 'complete'" },
{ name = "langflow-base", extras = ["docx"], marker = "extra == 'complete'" },
{ name = "langflow-base", extras = ["dspy"], marker = "extra == 'complete'" },
{ name = "langflow-base", extras = ["duckduckgo"], marker = "extra == 'complete'" },
{ name = "langflow-base", extras = ["easyocr"], marker = "extra == 'complete'" },
{ name = "langflow-base", extras = ["elasticsearch"], marker = "extra == 'complete'" },
@ -7550,8 +7479,6 @@ requires-dist = [
{ name = "lark", marker = "extra == 'lark'", specifier = "==1.2.2" },
{ name = "lfx", editable = "../../lfx" },
{ name = "litellm", marker = "extra == 'litellm'", specifier = ">=1.83.0" },
{ name = "llama-cpp-python", marker = "extra == 'llama-cpp'", specifier = "~=0.2.0" },
{ name = "llama-cpp-python", marker = "extra == 'local'", specifier = ">=0.2.0" },
{ name = "loguru", specifier = ">=0.7.1,<1.0.0" },
{ name = "markdown", marker = "extra == 'markdown'", specifier = ">=3.8.0" },
{ name = "markupsafe", marker = "extra == 'markup'", specifier = "==3.0.2" },
@ -7644,7 +7571,7 @@ requires-dist = [
{ name = "youtube-transcript-api", marker = "extra == 'youtube'", specifier = ">=1.0.0,<2.0.0" },
{ name = "zep-python", marker = "extra == 'zep'", specifier = "==2.0.2" },
]
provides-extras = ["audio", "postgresql", "local", "couchbase", "cassandra", "clickhouse", "mongodb", "supabase", "redis", "elasticsearch", "faiss", "qdrant", "qdrant-vectors", "weaviate", "chroma", "upstash", "pinecone", "milvus", "mongodb-vectors", "astradb", "openai", "anthropic", "cohere", "google", "ollama", "nvidia", "mistral", "sambanova", "groq", "llama-cpp", "sentence-transformers", "ctransformers", "langfuse", "langwatch", "langsmith", "arize", "pypdf", "docx", "pytube", "dspy", "smolagents", "beautifulsoup", "serpapi", "google-api", "wikipedia", "fake-useragent", "duckduckgo", "yfinance", "wolframalpha", "pyarrow", "fastavro", "gitpython", "nltk", "lark", "huggingface", "metaphor", "metal", "markup", "aws", "numexpr", "qianfan", "pgvector", "litellm", "zep", "youtube", "markdown", "kubernetes", "json-repair", "composio", "ragstack", "opensearch", "atlassian", "mem0", "needle", "sseclient", "openinference", "mcp", "ag2", "scrapegraph", "apify", "spider", "altk", "toolguard", "langchain-huggingface", "langchain-unstructured", "graph-retriever", "langchain-mcp-adapters", "opik", "traceloop", "openlayer", "docling", "easyocr", "cleanlab", "twelvelabs", "jigsawstack", "assemblyai", "datasets", "fastparquet", "vlmrun", "cuga", "mlx", "fastmcp", "aioboto3", "astrapy", "gassist", "ibm-watsonx-clients", "complete", "all"]
provides-extras = ["audio", "postgresql", "local", "couchbase", "cassandra", "clickhouse", "mongodb", "supabase", "redis", "elasticsearch", "faiss", "qdrant", "qdrant-vectors", "weaviate", "chroma", "upstash", "pinecone", "milvus", "mongodb-vectors", "astradb", "openai", "anthropic", "cohere", "google", "ollama", "nvidia", "mistral", "sambanova", "groq", "sentence-transformers", "ctransformers", "langfuse", "langwatch", "langsmith", "arize", "pypdf", "docx", "pytube", "smolagents", "beautifulsoup", "serpapi", "google-api", "wikipedia", "fake-useragent", "duckduckgo", "yfinance", "wolframalpha", "pyarrow", "fastavro", "gitpython", "nltk", "lark", "huggingface", "metaphor", "metal", "markup", "aws", "numexpr", "qianfan", "pgvector", "litellm", "zep", "youtube", "markdown", "kubernetes", "json-repair", "composio", "ragstack", "opensearch", "atlassian", "mem0", "needle", "sseclient", "openinference", "mcp", "ag2", "scrapegraph", "apify", "spider", "altk", "toolguard", "langchain-huggingface", "langchain-unstructured", "graph-retriever", "langchain-mcp-adapters", "opik", "traceloop", "openlayer", "docling", "easyocr", "cleanlab", "twelvelabs", "jigsawstack", "assemblyai", "datasets", "fastparquet", "vlmrun", "cuga", "mlx", "fastmcp", "aioboto3", "astrapy", "gassist", "ibm-watsonx-clients", "complete", "all"]
[package.metadata.requires-dev]
dev = [
@ -8093,7 +8020,7 @@ wheels = [
[[package]]
name = "litellm"
version = "1.86.0"
version = "1.86.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@ -8109,24 +8036,11 @@ dependencies = [
{ name = "tiktoken" },
{ name = "tokenizers" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/a7/26b8b04e4fcff26b60200ffe7458a255552ae51014468188f5db45674eb2/litellm-1.86.0.tar.gz", hash = "sha256:eccab86e0820b60b3f9484b233fb8d818b97afb19d5b4fa08d0d045621350ba4", size = 15379195, upload-time = "2026-05-24T02:42:10.865Z" }
sdist = { url = "https://files.pythonhosted.org/packages/2b/7c/a94c15e1146f76a6e0dfe0394f824cdfeb9dadb988c6f698a35f6c1b8d4f/litellm-1.86.1.tar.gz", hash = "sha256:616100384073f2ddec1a5391b34c806c7c99e6af4511741434809caa46075e13", size = 15379863, upload-time = "2026-05-26T03:51:58.624Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ff/0b/9a044c061a69e801de042e962c34f5bc2e094810e28b49ce0b3bedee9327/litellm-1.86.0-py3-none-any.whl", hash = "sha256:9d8171ca1a17705b7c7a6fdce8cfc07bbf641284b46c1b6047f83a779159990c", size = 17011225, upload-time = "2026-05-24T02:42:00.629Z" },
{ url = "https://files.pythonhosted.org/packages/76/47/e6e874857f45ff47762c6c29ec3dad9ea4757300e034400642cab90e4982/litellm-1.86.1-py3-none-any.whl", hash = "sha256:54e52372d326c642e9cc76d39afffb9b22387cbff3694c5e2758c9f860bca2e9", size = 17012257, upload-time = "2026-05-26T03:51:49.991Z" },
]
[[package]]
name = "llama-cpp-python"
version = "0.2.90"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "diskcache" },
{ name = "jinja2" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1f/19/89836022affc1bf470e2485e28872b489254a66fe587155edba731a07112/llama_cpp_python-0.2.90.tar.gz", hash = "sha256:419b041c62dbdb9f7e67883a6ef2f247d583d08417058776be0bff05b4ec9e3d", size = 63762953, upload-time = "2024-08-29T07:00:35.267Z" }
[[package]]
name = "llm-sandbox"
version = "0.3.39"
@ -10694,25 +10608,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/72/58dcedaca964398d64824b0d85311e89031cf13ec100ee0440c65d87e5cc/opik-2.0.47-py3-none-any.whl", hash = "sha256:aa1a1aadbff5c804355a6645e55b78739fe44d6d055d74676495a42d756c4451", size = 1623856, upload-time = "2026-05-25T04:44:03.994Z" },
]
[[package]]
name = "optuna"
version = "4.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "alembic" },
{ name = "colorlog" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "packaging" },
{ name = "pyyaml" },
{ name = "sqlalchemy" },
{ name = "tqdm" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bf/9b/62f120fb2ecbc4338bee70c5a3671c8e561714f3aa1a046b897ff142050e/optuna-4.8.0.tar.gz", hash = "sha256:6f7043e9f8ecb5e607af86a7eb00fb5ec2be26c3b08c201209a73d36aff37a38", size = 482603, upload-time = "2026-03-16T04:59:58.659Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ac/24/7c731839566d30dc70556d9824ef17692d896c15e3df627bce8c16f753e1/optuna-4.8.0-py3-none-any.whl", hash = "sha256:c57a7682679c36bfc9bca0da430698179e513874074b71bebedb0334964ab930", size = 419456, upload-time = "2026-03-16T04:59:56.977Z" },
]
[[package]]
name = "orjson"
version = "3.11.9"

View File

@ -1,8 +1,15 @@
import multiprocessing
import time
from langflow.services.flow_events.service import FlowEventsService
def _worker_append(cache_dir, flow_id, event_type, summary):
"""Top-level helper used by multiprocessing tests; must be picklable."""
svc = FlowEventsService(cache_dir=cache_dir)
svc.append(flow_id, event_type, summary)
def test_append_and_get_events(tmp_path):
svc = FlowEventsService(cache_dir=tmp_path / "cache")
svc.append("flow-1", "component_added", "Added OpenAI")
@ -60,8 +67,7 @@ def test_ttl_cleanup(tmp_path):
time.sleep(0.15)
# Force re-append to reset TTL for the key, then read
# Actually just read -- the key should have expired via diskcache expire
# The row should be filtered out by the expires_at >= now() predicate in get_since.
events, _ = svc.get_since("flow-1", 0.0)
assert len(events) == 0
@ -202,3 +208,27 @@ def test_cross_worker_ttl_expiry(tmp_path):
worker_b = FlowEventsService(cache_dir=shared_dir)
events, _ = worker_b.get_since("flow-1", 0.0)
assert len(events) == 0
def test_multi_process_visibility(tmp_path):
"""A separate OS process can write events that this process reads.
Proves the WAL-mode sqlite backend gives the same cross-worker contract that
the prior diskcache-based implementation did under multi-uvicorn/gunicorn.
"""
shared_dir = tmp_path / "shared_cache"
# Use spawn so the child does not inherit the parent's sqlite handle.
ctx = multiprocessing.get_context("spawn")
proc = ctx.Process(
target=_worker_append,
args=(str(shared_dir), "flow-mp", "component_added", "from-other-process"),
)
proc.start()
proc.join(timeout=10)
assert proc.exitcode == 0, "Child process failed to append event"
reader = FlowEventsService(cache_dir=shared_dir)
events, _ = reader.get_since("flow-mp", 0.0)
assert len(events) == 1
assert events[0].summary == "from-other-process"

View File

@ -180,10 +180,10 @@ class Settings(BaseSettings):
Controlled by LANGFLOW_USE_NOOP_DATABASE env variable."""
# cache configuration
cache_type: Literal["async", "redis", "memory", "disk"] = "async"
"""The cache type can be 'async' or 'redis'."""
cache_type: Literal["async", "redis", "memory"] = "async"
"""The cache backend: 'async' (default in-memory), 'memory' (sync in-memory), or 'redis'."""
cache_expire: int = 3600
"""The cache expire in seconds."""
"""Directory used by FlowEventsService for cross-worker event storage. Defaults to a temp dir if not set."""
variable_store: str = "db"
"""The store can be 'db' or 'kubernetes'."""

125
uv.lock generated
View File

@ -746,14 +746,16 @@ wheels = [
[[package]]
name = "asyncer"
version = "0.0.8"
version = "0.0.17"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "sniffio" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ff/67/7ea59c3e69eaeee42e7fc91a5be67ca5849c8979acac2b920249760c6af2/asyncer-0.0.8.tar.gz", hash = "sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c", size = 18217, upload-time = "2024-08-24T23:15:36.449Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d2/4c/62b6044679e08788322bbd0dee5b487a6f7f60bb4e2bd45617ff0d94d1e3/asyncer-0.0.17.tar.gz", hash = "sha256:8a41e185e7ec2ecd583c269d72907a0f9f832e744b6c7474aeb21e349c4becf4", size = 19516, upload-time = "2026-02-21T16:35:54.068Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/04/15b6ca6b7842eda2748bda0a0af73f2d054e9344320f8bba01f994294bcb/asyncer-0.0.8-py3-none-any.whl", hash = "sha256:5920d48fc99c8f8f0f1576e1882f5022885589c5fcbc46ce4224ec3e53776eeb", size = 9209, upload-time = "2024-08-24T23:15:35.317Z" },
{ url = "https://files.pythonhosted.org/packages/2a/c5/b72735a095b4b3170b34150e89314a40dd0450d0eb6746b331cb664479d1/asyncer-0.0.17-py3-none-any.whl", hash = "sha256:b0055950e094fb84fd8d21611c7e7b6f5715ddcb57c522c058f64c20badd1438", size = 9252, upload-time = "2026-02-21T16:35:55.022Z" },
]
[[package]]
@ -2851,15 +2853,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" },
]
[[package]]
name = "diskcache"
version = "5.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" },
]
[[package]]
name = "distlib"
version = "0.4.0"
@ -3072,48 +3065,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" },
]
[[package]]
name = "dspy"
version = "3.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "asyncer" },
{ name = "cachetools" },
{ name = "cloudpickle" },
{ name = "diskcache" },
{ name = "gepa" },
{ name = "json-repair" },
{ name = "litellm" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "openai" },
{ name = "optuna" },
{ name = "orjson" },
{ name = "pydantic" },
{ name = "regex" },
{ name = "requests" },
{ name = "tenacity" },
{ name = "tqdm" },
{ name = "xxhash" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d9/bd/339e1d949aa24e50b6edca8574cf8afbff005a1dfadc2d5b82a0003fa7da/dspy-3.1.0.tar.gz", hash = "sha256:3c1660cb687411f064509cd7ac47ed0231761f50ed92823cbbb59b3af2885702", size = 242611, upload-time = "2026-01-06T18:50:18.655Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/34/64e900657cad3c4df8a417fccbc4370ca851864edbd0f468210f1b57d084/dspy-3.1.0-py3-none-any.whl", hash = "sha256:b9f4d42cad9ac32b13fdf085dd3246c8ee8339c759cb291c5e7b7f9f5fb5dd72", size = 291317, upload-time = "2026-01-06T18:50:17.559Z" },
]
[[package]]
name = "dspy-ai"
version = "2.5.41"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "dspy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d6/25/94659c581e69fe11237294aeadf55785abec09361aa2d3a63bd0abc39d82/dspy-ai-2.5.41.tar.gz", hash = "sha256:a2804434eb3354d6e41f59a448938886914fed0c502eb42503569776a3f2f002", size = 258174, upload-time = "2024-11-29T03:17:24.301Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/50/f715b62acfe29718c3575dde16eb35e3ab6a5642652b13fb229bd758c040/dspy_ai-2.5.41-py3-none-any.whl", hash = "sha256:4f366a6b610ee93fd04b92b0dff2f78b24e23ee74c71d20bcf9fedcbf0d7aa97", size = 339737, upload-time = "2024-11-29T03:17:22.8Z" },
]
[[package]]
name = "duckdb"
version = "1.5.3"
@ -4320,15 +4271,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/af/90/3bc780df088d439714af8295196a4332a26559ae66fd99865e36f92efa9e/geomet-1.1.0-py3-none-any.whl", hash = "sha256:4372fe4e286a34acc6f2e9308284850bd8c4aa5bc12065e2abbd4995900db12f", size = 31522, upload-time = "2023-11-14T15:43:35.305Z" },
]
[[package]]
name = "gepa"
version = "0.0.22"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ae/be/9a4c31c65c4910e73bd4a316df99347681bce4f2ef6d10877cc1ed8d57da/gepa-0.0.22.tar.gz", hash = "sha256:0b13a644efd2af52186e456eaad2ae28fcf88c6f796a9c999910c587863d4315", size = 116337, upload-time = "2025-11-10T21:39:27.044Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/0e/d41272b28f2163f5bf840b508cbaf4a0bc01eaeaa6e5d447558d1050eb3b/gepa-0.0.22-py3-none-any.whl", hash = "sha256:ff57ee0442399a5cc62d01411935476bc80cfa8f1c318bed82e3db4c2c834665", size = 119666, upload-time = "2025-11-10T21:39:26.028Z" },
]
[[package]]
name = "gevent"
version = "25.9.1"
@ -7002,7 +6944,6 @@ docling = [
]
local = [
{ name = "ctransformers" },
{ name = "llama-cpp-python" },
{ name = "sentence-transformers" },
]
nv-ingest = [
@ -7063,7 +7004,6 @@ requires-dist = [
{ name = "ctransformers", marker = "extra == 'local'", specifier = ">=0.2.10" },
{ name = "langchain-docling", marker = "extra == 'docling'", specifier = ">=1.1.0" },
{ name = "langflow-base", extras = ["complete"], editable = "src/backend/base" },
{ name = "llama-cpp-python", marker = "extra == 'local'", specifier = "~=0.2.0" },
{ name = "nv-ingest-api", marker = "python_full_version >= '3.12' and extra == 'nv-ingest'", specifier = ">=26.1.0,<27.0.0" },
{ name = "nv-ingest-client", marker = "python_full_version >= '3.12' and extra == 'nv-ingest'", specifier = ">=26.1.0,<27.0.0" },
{ name = "ocrmac", marker = "sys_platform == 'darwin' and extra == 'docling'", specifier = ">=1.0.0" },
@ -7137,7 +7077,6 @@ dependencies = [
{ name = "clickhouse-connect" },
{ name = "cryptography" },
{ name = "defusedxml" },
{ name = "diskcache" },
{ name = "docstring-parser" },
{ name = "duckdb" },
{ name = "dynaconf" },
@ -7252,7 +7191,6 @@ all = [
{ name = "ddgs" },
{ name = "docling", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" },
{ name = "docling-core" },
{ name = "dspy-ai" },
{ name = "easyocr", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" },
{ name = "elasticsearch" },
{ name = "faiss-cpu", version = "1.9.0.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
@ -7422,7 +7360,6 @@ complete = [
{ name = "ddgs" },
{ name = "docling", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" },
{ name = "docling-core" },
{ name = "dspy-ai" },
{ name = "easyocr", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" },
{ name = "elasticsearch" },
{ name = "faiss-cpu", version = "1.9.0.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
@ -7540,9 +7477,6 @@ docling = [
docx = [
{ name = "python-docx" },
]
dspy = [
{ name = "dspy-ai" },
]
duckduckgo = [
{ name = "ddgs" },
]
@ -7634,12 +7568,8 @@ lark = [
litellm = [
{ name = "litellm" },
]
llama-cpp = [
{ name = "llama-cpp-python" },
]
local = [
{ name = "ctransformers" },
{ name = "llama-cpp-python" },
{ name = "sentence-transformers" },
]
markdown = [
@ -7863,11 +7793,9 @@ requires-dist = [
{ name = "datasets", marker = "extra == 'datasets'", specifier = ">2.14.7,<5.0.0" },
{ name = "ddgs", marker = "extra == 'duckduckgo'", specifier = ">=9.0.0" },
{ name = "defusedxml", specifier = ">=0.7.1,<1.0.0" },
{ name = "diskcache", specifier = ">=5.6.3,<6.0.0" },
{ name = "docling", marker = "(platform_machine != 'x86_64' and extra == 'docling') or (sys_platform != 'darwin' and extra == 'docling')", specifier = ">=2.36.1,<3.0.0" },
{ name = "docling-core", marker = "extra == 'docling'", specifier = ">=2.77.0,<3.0.0" },
{ name = "docstring-parser", specifier = ">=0.16,<1.0.0" },
{ name = "dspy-ai", marker = "extra == 'dspy'", specifier = "==2.5.41" },
{ name = "duckdb", specifier = ">=1.0.0,<2.0.0" },
{ name = "dynaconf", specifier = ">=3.2.13,<4.0.0" },
{ name = "easyocr", marker = "(platform_machine != 'x86_64' and extra == 'easyocr') or (sys_platform != 'darwin' and extra == 'easyocr')", specifier = ">=1.7.2,<2.0.0" },
@ -7968,7 +7896,6 @@ requires-dist = [
{ name = "langflow-base", extras = ["datasets"], marker = "extra == 'complete'", editable = "src/backend/base" },
{ name = "langflow-base", extras = ["docling"], marker = "extra == 'complete'", editable = "src/backend/base" },
{ name = "langflow-base", extras = ["docx"], marker = "extra == 'complete'", editable = "src/backend/base" },
{ name = "langflow-base", extras = ["dspy"], marker = "extra == 'complete'", editable = "src/backend/base" },
{ name = "langflow-base", extras = ["duckduckgo"], marker = "extra == 'complete'", editable = "src/backend/base" },
{ name = "langflow-base", extras = ["easyocr"], marker = "extra == 'complete'", editable = "src/backend/base" },
{ name = "langflow-base", extras = ["elasticsearch"], marker = "extra == 'complete'", editable = "src/backend/base" },
@ -8052,8 +7979,6 @@ requires-dist = [
{ name = "lark", marker = "extra == 'lark'", specifier = "==1.2.2" },
{ name = "lfx", editable = "src/lfx" },
{ name = "litellm", marker = "extra == 'litellm'", specifier = ">=1.83.0" },
{ name = "llama-cpp-python", marker = "extra == 'llama-cpp'", specifier = "~=0.2.0" },
{ name = "llama-cpp-python", marker = "extra == 'local'", specifier = ">=0.2.0" },
{ name = "loguru", specifier = ">=0.7.1,<1.0.0" },
{ name = "markdown", marker = "extra == 'markdown'", specifier = ">=3.8.0" },
{ name = "markupsafe", marker = "extra == 'markup'", specifier = "==3.0.2" },
@ -8146,7 +8071,7 @@ requires-dist = [
{ name = "youtube-transcript-api", marker = "extra == 'youtube'", specifier = ">=1.0.0,<2.0.0" },
{ name = "zep-python", marker = "extra == 'zep'", specifier = "==2.0.2" },
]
provides-extras = ["audio", "postgresql", "local", "couchbase", "cassandra", "clickhouse", "mongodb", "supabase", "redis", "elasticsearch", "faiss", "qdrant", "qdrant-vectors", "weaviate", "chroma", "upstash", "pinecone", "milvus", "mongodb-vectors", "astradb", "openai", "anthropic", "cohere", "google", "ollama", "nvidia", "mistral", "sambanova", "groq", "llama-cpp", "sentence-transformers", "ctransformers", "langfuse", "langwatch", "langsmith", "arize", "pypdf", "docx", "pytube", "dspy", "smolagents", "beautifulsoup", "serpapi", "google-api", "wikipedia", "fake-useragent", "duckduckgo", "yfinance", "wolframalpha", "pyarrow", "fastavro", "gitpython", "nltk", "lark", "huggingface", "metaphor", "metal", "markup", "aws", "numexpr", "qianfan", "pgvector", "litellm", "zep", "youtube", "markdown", "kubernetes", "json-repair", "composio", "ragstack", "opensearch", "atlassian", "mem0", "needle", "sseclient", "openinference", "mcp", "ag2", "scrapegraph", "apify", "spider", "altk", "toolguard", "langchain-huggingface", "langchain-unstructured", "graph-retriever", "langchain-mcp-adapters", "opik", "traceloop", "openlayer", "docling", "easyocr", "cleanlab", "twelvelabs", "jigsawstack", "assemblyai", "datasets", "fastparquet", "vlmrun", "cuga", "mlx", "fastmcp", "aioboto3", "astrapy", "gassist", "ibm-watsonx-clients", "complete", "all"]
provides-extras = ["audio", "postgresql", "local", "couchbase", "cassandra", "clickhouse", "mongodb", "supabase", "redis", "elasticsearch", "faiss", "qdrant", "qdrant-vectors", "weaviate", "chroma", "upstash", "pinecone", "milvus", "mongodb-vectors", "astradb", "openai", "anthropic", "cohere", "google", "ollama", "nvidia", "mistral", "sambanova", "groq", "sentence-transformers", "ctransformers", "langfuse", "langwatch", "langsmith", "arize", "pypdf", "docx", "pytube", "smolagents", "beautifulsoup", "serpapi", "google-api", "wikipedia", "fake-useragent", "duckduckgo", "yfinance", "wolframalpha", "pyarrow", "fastavro", "gitpython", "nltk", "lark", "huggingface", "metaphor", "metal", "markup", "aws", "numexpr", "qianfan", "pgvector", "litellm", "zep", "youtube", "markdown", "kubernetes", "json-repair", "composio", "ragstack", "opensearch", "atlassian", "mem0", "needle", "sseclient", "openinference", "mcp", "ag2", "scrapegraph", "apify", "spider", "altk", "toolguard", "langchain-huggingface", "langchain-unstructured", "graph-retriever", "langchain-mcp-adapters", "opik", "traceloop", "openlayer", "docling", "easyocr", "cleanlab", "twelvelabs", "jigsawstack", "assemblyai", "datasets", "fastparquet", "vlmrun", "cuga", "mlx", "fastmcp", "aioboto3", "astrapy", "gassist", "ibm-watsonx-clients", "complete", "all"]
[package.metadata.requires-dev]
dev = [
@ -8650,7 +8575,7 @@ wheels = [
[[package]]
name = "litellm"
version = "1.86.0"
version = "1.86.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@ -8666,24 +8591,11 @@ dependencies = [
{ name = "tiktoken" },
{ name = "tokenizers" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/a7/26b8b04e4fcff26b60200ffe7458a255552ae51014468188f5db45674eb2/litellm-1.86.0.tar.gz", hash = "sha256:eccab86e0820b60b3f9484b233fb8d818b97afb19d5b4fa08d0d045621350ba4", size = 15379195, upload-time = "2026-05-24T02:42:10.865Z" }
sdist = { url = "https://files.pythonhosted.org/packages/2b/7c/a94c15e1146f76a6e0dfe0394f824cdfeb9dadb988c6f698a35f6c1b8d4f/litellm-1.86.1.tar.gz", hash = "sha256:616100384073f2ddec1a5391b34c806c7c99e6af4511741434809caa46075e13", size = 15379863, upload-time = "2026-05-26T03:51:58.624Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ff/0b/9a044c061a69e801de042e962c34f5bc2e094810e28b49ce0b3bedee9327/litellm-1.86.0-py3-none-any.whl", hash = "sha256:9d8171ca1a17705b7c7a6fdce8cfc07bbf641284b46c1b6047f83a779159990c", size = 17011225, upload-time = "2026-05-24T02:42:00.629Z" },
{ url = "https://files.pythonhosted.org/packages/76/47/e6e874857f45ff47762c6c29ec3dad9ea4757300e034400642cab90e4982/litellm-1.86.1-py3-none-any.whl", hash = "sha256:54e52372d326c642e9cc76d39afffb9b22387cbff3694c5e2758c9f860bca2e9", size = 17012257, upload-time = "2026-05-26T03:51:49.991Z" },
]
[[package]]
name = "llama-cpp-python"
version = "0.2.90"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "diskcache" },
{ name = "jinja2" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1f/19/89836022affc1bf470e2485e28872b489254a66fe587155edba731a07112/llama_cpp_python-0.2.90.tar.gz", hash = "sha256:419b041c62dbdb9f7e67883a6ef2f247d583d08417058776be0bff05b4ec9e3d", size = 63762953, upload-time = "2024-08-29T07:00:35.267Z" }
[[package]]
name = "llm-sandbox"
version = "0.3.39"
@ -11267,25 +11179,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/72/58dcedaca964398d64824b0d85311e89031cf13ec100ee0440c65d87e5cc/opik-2.0.47-py3-none-any.whl", hash = "sha256:aa1a1aadbff5c804355a6645e55b78739fe44d6d055d74676495a42d756c4451", size = 1623856, upload-time = "2026-05-25T04:44:03.994Z" },
]
[[package]]
name = "optuna"
version = "4.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "alembic" },
{ name = "colorlog" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "packaging" },
{ name = "pyyaml" },
{ name = "sqlalchemy" },
{ name = "tqdm" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bf/9b/62f120fb2ecbc4338bee70c5a3671c8e561714f3aa1a046b897ff142050e/optuna-4.8.0.tar.gz", hash = "sha256:6f7043e9f8ecb5e607af86a7eb00fb5ec2be26c3b08c201209a73d36aff37a38", size = 482603, upload-time = "2026-03-16T04:59:58.659Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ac/24/7c731839566d30dc70556d9824ef17692d896c15e3df627bce8c16f753e1/optuna-4.8.0-py3-none-any.whl", hash = "sha256:c57a7682679c36bfc9bca0da430698179e513874074b71bebedb0334964ab930", size = 419456, upload-time = "2026-03-16T04:59:56.977Z" },
]
[[package]]
name = "orjson"
version = "3.11.9"