Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ A powerful local AI workflow system with multi-model support and visual workflow
### Requirements
- Python 3.9+
- Ollama (for local models) - [Download here](https://ollama.com/download)
- (Optional) SQL dependencies for RDS memory: `sqlalchemy` and `pymysql` for MySQL support

### Installation
```bash
Expand All @@ -37,6 +38,11 @@ cd vertex
pip install -e .
```

```bash
# Optional SQL dependencies for RDS memory
pip install sqlalchemy pymysql
```

### Configuration
```bash
# Quick setup - Initialize configuration
Expand Down
6 changes: 6 additions & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ A powerful local AI workflow system with multi-model support and visual workflow
### Requirements
- Python 3.9+
- Ollama (for local models) - [Download here](https://ollama.com/download)
- (Optional) SQL dependencies for RDS memory: `sqlalchemy` and `pymysql` for MySQL support

### Installation
```bash
Expand All @@ -37,6 +38,11 @@ cd vertex
pip install -e .
```

```bash
# Optional SQL dependencies for RDS memory
pip install sqlalchemy pymysql
```

### Configuration
```bash
# Quick setup - Initialize configuration
Expand Down
6 changes: 6 additions & 0 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
### 环境要求
- Python 3.9+
- Ollama(本地模型)- [下载地址](https://ollama.com/download)
- (可选)RDS 内存后端需要的 SQL 依赖:`sqlalchemy`,若使用 MySQL 还需 `pymysql`

### 安装方式

Expand Down Expand Up @@ -58,6 +59,11 @@ cd vertex
pip install -e .
```

```bash
# 可选:安装 RDS 内存后端所需依赖
pip install sqlalchemy pymysql
```

### 配置
```bash
# 快速设置 - 初始化配置
Expand Down
10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ cloud-vector = [
"dashvector>=1.0.19",
]

# Memory backends
memory = [
"redis>=5.0.0",
"sqlalchemy>=2.0.0",
"pymysql>=1.1.0",
]

# 桌面端应用(可选)
desktop = [
"pywebview>=5.4",
Expand All @@ -113,6 +120,9 @@ all = [
"dashvector>=1.0.19",
"pywebview>=5.4",
"requests>=2.28.2",
"redis>=5.0.0",
"sqlalchemy>=2.0.0",
"pymysql>=1.1.0",
]

[project.scripts]
Expand Down
5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ nest-asyncio>=1.6.0
# 进程管理
psutil>=5.9.0

# 缓存和持久化存储
redis>=5.0.0
sqlalchemy>=2.0.0
pymysql>=1.1.0

# MCP (Model Context Protocol) 支持
aiohttp>=3.8.0 # 已包含在上面的网络依赖中

Expand Down
9 changes: 9 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@
"desktop": [
"pywebview>=5.4",
],
# 缓存和持久化存储
"memory": [
"redis>=5.0.0",
"sqlalchemy>=2.0.0",
"pymysql>=1.1.0",
],
# 完整功能(包含所有可选依赖)
"all": [
"sentence-transformers>=2.2.0",
Expand All @@ -84,6 +90,9 @@
"dashvector>=1.0.19",
"pywebview>=5.4",
"requests>=2.28.2",
"redis>=5.0.0",
"sqlalchemy>=2.0.0",
"pymysql>=1.1.0",
],
},
entry_points={
Expand Down
15 changes: 14 additions & 1 deletion vertex_flow/memory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,20 @@

from .factory import MemoryFactory, create_memory, create_memory_from_config
from .file_store import FileMemory
from .hybrid_store import HybridMemory
from .inmem_store import InnerMemory
from .memory import Memory
from .redis_store import RedisMemory
from .rds_store import RDSMemory

__all__ = ["Memory", "InnerMemory", "FileMemory", "MemoryFactory", "create_memory", "create_memory_from_config"]
__all__ = [
"Memory",
"InnerMemory",
"FileMemory",
"HybridMemory",
"RedisMemory",
"RDSMemory",
"MemoryFactory",
"create_memory",
"create_memory_from_config",
]
17 changes: 17 additions & 0 deletions vertex_flow/memory/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
from typing import Any, Dict, Optional

from .file_store import FileMemory
from .hybrid_store import HybridMemory
from .inmem_store import InnerMemory
from .memory import Memory
from .redis_store import RedisMemory
from .rds_store import RDSMemory


class MemoryFactory:
Expand All @@ -16,6 +19,9 @@ class MemoryFactory:
"memory": InnerMemory, # alias for backward compatibility
"inmem": InnerMemory, # alias for backward compatibility
"file": FileMemory,
"redis": RedisMemory,
"rds": RDSMemory,
"hybrid": HybridMemory,
}

@classmethod
Expand Down Expand Up @@ -122,6 +128,17 @@ def get_default_config(cls, memory_type: str = "inner") -> Dict[str, Any]:
return {"type": "inner", "hist_maxlen": 200, "cleanup_interval_sec": 300}
elif memory_type == "file":
return {"type": "file", "storage_dir": "./memory_data", "hist_maxlen": 200}
elif memory_type == "redis":
return {"type": "redis", "url": "redis://localhost:6379/0", "hist_maxlen": 200}
elif memory_type == "rds":
return {"type": "rds", "db_url": "sqlite:///:memory:", "hist_maxlen": 200}
elif memory_type == "hybrid":
return {
"type": "hybrid",
"redis_url": "redis://localhost:6379/0",
"db_url": "sqlite:///:memory:",
"hist_maxlen": 200,
}
else:
available_types = ", ".join(cls._memory_types.keys())
raise ValueError(f"Unsupported memory type: {memory_type}. " f"Available types: {available_types}")
Expand Down
87 changes: 87 additions & 0 deletions vertex_flow/memory/hybrid_store.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Hybrid memory store combining Redis for caching and RDS for persistence."""

from __future__ import annotations

from typing import Any, Optional

from .memory import Memory
from .rds_store import RDSMemory
from .redis_store import RedisMemory


class HybridMemory(Memory):
"""Memory implementation using Redis as cache and RDS as persistent storage."""

def __init__(
self,
redis_url: str = "redis://localhost:6379/0",
db_url: str = "sqlite:///:memory:",
hist_maxlen: int = 200,
prefix: str = "vf:",
redis_client=None,
) -> None:
self._redis = RedisMemory(url=redis_url, hist_maxlen=hist_maxlen, prefix=prefix, client=redis_client)
self._rds = RDSMemory(db_url=db_url, hist_maxlen=hist_maxlen)
self._hist_maxlen = hist_maxlen

# Deduplication -----------------------------------------------------------------
def seen(self, user_id: str, key: str, ttl_sec: int = 3600) -> bool:
result = self._rds.seen(user_id, key, ttl_sec)
self._redis.seen(user_id, key, ttl_sec)
return result

# History ----------------------------------------------------------------------
def append_history(self, user_id: str, role: str, mtype: str, content: dict, maxlen: int = 200) -> None:
self._rds.append_history(user_id, role, mtype, content, maxlen)
self._redis.append_history(user_id, role, mtype, content, maxlen)

def recent_history(self, user_id: str, n: int = 20) -> list[dict]:
history = self._redis.recent_history(user_id, n)
if history:
return history
history = self._rds.recent_history(user_id, n)
for msg in reversed(history):
self._redis.append_history(user_id, msg["role"], msg["type"], msg["content"], self._hist_maxlen)
return history

# Context ----------------------------------------------------------------------
def ctx_set(self, user_id: str, key: str, value: Any, ttl_sec: Optional[int] = None) -> None:
self._rds.ctx_set(user_id, key, value, ttl_sec)
self._redis.ctx_set(user_id, key, value, ttl_sec)

def ctx_get(self, user_id: str, key: str) -> Optional[Any]:
value = self._redis.ctx_get(user_id, key)
if value is not None:
return value
value = self._rds.ctx_get(user_id, key)
if value is not None:
self._redis.ctx_set(user_id, key, value)
return value

def ctx_del(self, user_id: str, key: str) -> None:
self._rds.ctx_del(user_id, key)
self._redis.ctx_del(user_id, key)

# Ephemeral --------------------------------------------------------------------
def set_ephemeral(self, user_id: str, key: str, value: Any, ttl_sec: int = 1800) -> None:
self._rds.set_ephemeral(user_id, key, value, ttl_sec)
self._redis.set_ephemeral(user_id, key, value, ttl_sec)

def get_ephemeral(self, user_id: str, key: str) -> Optional[Any]:
value = self._redis.get_ephemeral(user_id, key)
if value is not None:
return value
return self._rds.get_ephemeral(user_id, key)

def del_ephemeral(self, user_id: str, key: str) -> None:
self._rds.del_ephemeral(user_id, key)
self._redis.del_ephemeral(user_id, key)

# Rate limiting ----------------------------------------------------------------
def incr_rate(self, user_id: str, bucket: str, ttl_sec: int = 60) -> int:
count = self._rds.incr_rate(user_id, bucket, ttl_sec)
redis_key = self._redis._rate_key(user_id, bucket)
self._redis._client.set(redis_key, str(count))
if ttl_sec > 0:
self._redis._client.expire(redis_key, ttl_sec)
return count
Loading
Loading