msgflux 0.1.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- msgflux/__init__.py +37 -0
- msgflux/_private/__init__.py +0 -0
- msgflux/_private/client.py +91 -0
- msgflux/_private/core.py +14 -0
- msgflux/_private/executor.py +77 -0
- msgflux/cache.py +50 -0
- msgflux/data/__init__.py +0 -0
- msgflux/data/audio.py +1 -0
- msgflux/data/databases/__init__.py +0 -0
- msgflux/data/databases/base.py +32 -0
- msgflux/data/databases/database.py +125 -0
- msgflux/data/databases/providers/__init__.py +0 -0
- msgflux/data/databases/providers/cachetools.py +64 -0
- msgflux/data/databases/providers/diskcache.py +60 -0
- msgflux/data/databases/providers/faiss.py +217 -0
- msgflux/data/databases/types.py +20 -0
- msgflux/data/parsers/__init__.py +0 -0
- msgflux/data/parsers/base.py +11 -0
- msgflux/data/parsers/parser.py +88 -0
- msgflux/data/parsers/providers/__init__.py +0 -0
- msgflux/data/parsers/providers/openpyxl.py +166 -0
- msgflux/data/parsers/providers/pypdf.py +49 -0
- msgflux/data/parsers/providers/python_pptx.py +167 -0
- msgflux/data/parsers/types.py +23 -0
- msgflux/data/retrievers/__init__.py +0 -0
- msgflux/data/retrievers/base.py +12 -0
- msgflux/data/retrievers/providers/__init__.py +0 -0
- msgflux/data/retrievers/providers/bm25.py +188 -0
- msgflux/data/retrievers/providers/rank_bm25.py +187 -0
- msgflux/data/retrievers/retriever.py +80 -0
- msgflux/data/retrievers/types.py +15 -0
- msgflux/dotdict.py +169 -0
- msgflux/dsl/__init__.py +0 -0
- msgflux/dsl/inline.py +524 -0
- msgflux/dsl/signature.py +400 -0
- msgflux/envs.py +106 -0
- msgflux/exceptions.py +31 -0
- msgflux/generation/__init__.py +0 -0
- msgflux/generation/reasoning/__init__.py +6 -0
- msgflux/generation/reasoning/cot.py +70 -0
- msgflux/generation/reasoning/react.py +47 -0
- msgflux/generation/reasoning/self_consistency.py +42 -0
- msgflux/generation/reasoning/tot.py +57 -0
- msgflux/generation/templates.py +87 -0
- msgflux/logger.py +148 -0
- msgflux/message.py +43 -0
- msgflux/models/__init__.py +0 -0
- msgflux/models/base.py +31 -0
- msgflux/models/gateway.py +242 -0
- msgflux/models/httpx.py +39 -0
- msgflux/models/model.py +170 -0
- msgflux/models/providers/__init__.py +0 -0
- msgflux/models/providers/base.py +123 -0
- msgflux/models/providers/ctranslate2.py +144 -0
- msgflux/models/providers/jinaai.py +237 -0
- msgflux/models/providers/mistral.py +1 -0
- msgflux/models/providers/ollama.py +34 -0
- msgflux/models/providers/openai.py +955 -0
- msgflux/models/providers/openrouter.py +20 -0
- msgflux/models/providers/pylate.py +0 -0
- msgflux/models/providers/sambanova.py +32 -0
- msgflux/models/providers/timm.py +64 -0
- msgflux/models/providers/together.py +45 -0
- msgflux/models/providers/torchaudio.py +74 -0
- msgflux/models/providers/torchvision.py +120 -0
- msgflux/models/providers/vllm.py +86 -0
- msgflux/models/response.py +76 -0
- msgflux/models/tool_call_agg.py +93 -0
- msgflux/models/types.py +180 -0
- msgflux/nn/__init__.py +7 -0
- msgflux/nn/functional.py +472 -0
- msgflux/nn/modules/__init__.py +23 -0
- msgflux/nn/modules/agent.py +1005 -0
- msgflux/nn/modules/container.py +601 -0
- msgflux/nn/modules/designer.py +273 -0
- msgflux/nn/modules/module.py +1958 -0
- msgflux/nn/modules/predictor.py +116 -0
- msgflux/nn/modules/retriever.py +245 -0
- msgflux/nn/modules/speaker.py +140 -0
- msgflux/nn/modules/tool.py +296 -0
- msgflux/nn/modules/transcriber.py +181 -0
- msgflux/nn/parameter.py +75 -0
- msgflux/py.typed +0 -0
- msgflux/telemetry/__init__.py +0 -0
- msgflux/telemetry/span.py +141 -0
- msgflux/telemetry/tracer.py +48 -0
- msgflux/utils/__init__.py +0 -0
- msgflux/utils/chat.py +508 -0
- msgflux/utils/common.py +25 -0
- msgflux/utils/convert.py +41 -0
- msgflux/utils/encode.py +101 -0
- msgflux/utils/hooks.py +68 -0
- msgflux/utils/imports.py +39 -0
- msgflux/utils/inspect.py +110 -0
- msgflux/utils/logging.py +15 -0
- msgflux/utils/mermaid.py +11 -0
- msgflux/utils/msgspec.py +489 -0
- msgflux/utils/pooling.py +45 -0
- msgflux/utils/tenacity.py +19 -0
- msgflux/utils/tool.py +121 -0
- msgflux/utils/torch.py +8 -0
- msgflux/utils/validation.py +22 -0
- msgflux/utils/xml.py +125 -0
- msgflux/version.py +1 -0
- msgflux-0.1.0a1.dist-info/METADATA +45 -0
- msgflux-0.1.0a1.dist-info/RECORD +108 -0
- msgflux-0.1.0a1.dist-info/WHEEL +4 -0
- msgflux-0.1.0a1.dist-info/licenses/LICENSE +21 -0
msgflux/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from .cache import response_cache
|
|
2
|
+
from .data.databases.database import DataBase
|
|
3
|
+
from .data.retrievers.retriever import Retriever
|
|
4
|
+
from .dotdict import dotdict
|
|
5
|
+
from .dsl.inline import inline
|
|
6
|
+
from .dsl.signature import InputField, OutputField, Signature
|
|
7
|
+
from .envs import set_envs
|
|
8
|
+
from .message import Message
|
|
9
|
+
from .models.gateway import ModelGateway
|
|
10
|
+
from .models.model import Model
|
|
11
|
+
from .utils.chat import ChatML
|
|
12
|
+
from .utils.inspect import get_fn_name
|
|
13
|
+
from .utils.msgspec import load, save
|
|
14
|
+
from .utils.tool import tool_config
|
|
15
|
+
from .telemetry.span import instrument
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"ChatML",
|
|
20
|
+
"DataBase",
|
|
21
|
+
"InputField",
|
|
22
|
+
"Message",
|
|
23
|
+
"Model",
|
|
24
|
+
"ModelGateway",
|
|
25
|
+
"OutputField",
|
|
26
|
+
"Retriever",
|
|
27
|
+
"Signature",
|
|
28
|
+
"dotdict",
|
|
29
|
+
"get_fn_name",
|
|
30
|
+
"inline",
|
|
31
|
+
"instrument",
|
|
32
|
+
"load",
|
|
33
|
+
"response_cache",
|
|
34
|
+
"save",
|
|
35
|
+
"set_envs",
|
|
36
|
+
"tool_config"
|
|
37
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
from msgflux._private.core import Core
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseClient(ABC, Core):
|
|
7
|
+
"""
|
|
8
|
+
BaseClient is an abstract base class that defines the interface and common behavior
|
|
9
|
+
for client classes in the msgflux system.
|
|
10
|
+
|
|
11
|
+
This class inherits from `Core` to leverage its state management capabilities and
|
|
12
|
+
extends it with additional functionality specific to clients. It requires subclasses
|
|
13
|
+
to implement certain methods, ensuring a consistent interface across different client
|
|
14
|
+
implementations.
|
|
15
|
+
|
|
16
|
+
Subclasses must implement the `_initialize` and `__call__` methods, which are
|
|
17
|
+
essential for client initialization and operation, respectively. Additionally, this
|
|
18
|
+
class provides methods for serialization and deserialization, allowing client instances
|
|
19
|
+
to be easily saved and restored.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
to_ignore ([optional[list[str]]): A list of attribute keys to be ignored during serialization.
|
|
23
|
+
msgflux_type (str): A string identifying the type of the client in the msgflux system.
|
|
24
|
+
provider (optional[str]): The provider associated with the client, if applicable.
|
|
25
|
+
instance_type (optional[callable]): A method or callable that returns additional
|
|
26
|
+
instance-specific data to be included during serialization.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def __call__(self):
|
|
31
|
+
"""
|
|
32
|
+
Execute the client's main functionality. This method must be implemented by subclasses.
|
|
33
|
+
|
|
34
|
+
This method defines the primary behavior of the client and is called when the instance
|
|
35
|
+
is invoked as a function.
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
NotImplementedError: If the method is not implemented by the subclass.
|
|
39
|
+
"""
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
async def acall(self, *args, **kwargs):
|
|
43
|
+
""" Async interface to __call__ """
|
|
44
|
+
return self.__call__(*args, **kwargs)
|
|
45
|
+
|
|
46
|
+
def serialize(self) -> Dict[str, Any]:
|
|
47
|
+
"""
|
|
48
|
+
Serialize the client instance into a dictionary.
|
|
49
|
+
|
|
50
|
+
This method prepares the client's state for serialization by removing unnecessary
|
|
51
|
+
attributes and including additional metadata such as the client type and provider.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dict[str, Any]: A dictionary containing the serialized state of the client,
|
|
55
|
+
including metadata and the client's internal state.
|
|
56
|
+
"""
|
|
57
|
+
state = self.__getstate__()
|
|
58
|
+
if hasattr(self, "to_ignore"):
|
|
59
|
+
for key in self.to_ignore:
|
|
60
|
+
state.pop(key, None)
|
|
61
|
+
data = {"msgflux_type": self.msgflux_type}
|
|
62
|
+
if hasattr(self, "provider"):
|
|
63
|
+
data["provider"] = self.provider
|
|
64
|
+
if hasattr(self, "instance_type"):
|
|
65
|
+
instance_type = self.instance_type()
|
|
66
|
+
data.update(instance_type)
|
|
67
|
+
data["state"] = state
|
|
68
|
+
return data
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_serialized(cls, state: Dict[str, Any]):
|
|
72
|
+
"""
|
|
73
|
+
Deserialize a client instance from a dictionary.
|
|
74
|
+
|
|
75
|
+
This method creates a new instance of the client and restores its state from the
|
|
76
|
+
provided dictionary. It also ensures that the client is properly initialized
|
|
77
|
+
after deserialization.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
state (Dict[str, Any]): A dictionary containing the serialized state of the client.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
BaseClient: A new instance of the client with its state restored.
|
|
84
|
+
"""
|
|
85
|
+
if "msgflux_type" in state:
|
|
86
|
+
state.pop("msgflux_type")
|
|
87
|
+
instance = cls()
|
|
88
|
+
instance.__setstate__(state)
|
|
89
|
+
if hasattr(instance, "_initialize"):
|
|
90
|
+
instance._initialize()
|
|
91
|
+
return instance
|
msgflux/_private/core.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class Core:
|
|
2
|
+
"""
|
|
3
|
+
Core is a set of methods that are common among some classes in msgflux.
|
|
4
|
+
|
|
5
|
+
This class provides basic functionality for state management, allowing instances
|
|
6
|
+
to be serialized and deserialized. It is intended to be inherited by other classes
|
|
7
|
+
that require these common features.
|
|
8
|
+
"""
|
|
9
|
+
def __getstate__(self):
|
|
10
|
+
state = self.__dict__.copy()
|
|
11
|
+
return state
|
|
12
|
+
|
|
13
|
+
def __setstate__(self, state):
|
|
14
|
+
self.__dict__.update(state)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import threading
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
from typing import Callable
|
|
5
|
+
import uvloop
|
|
6
|
+
from msgflux.envs import envs
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AsyncWorker:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
"""Initializes a worker with its own event loop in a separate thread."""
|
|
12
|
+
self.loop = uvloop.new_event_loop()
|
|
13
|
+
self.thread = threading.Thread(target=self.loop.run_forever)
|
|
14
|
+
self.thread.start()
|
|
15
|
+
|
|
16
|
+
def submit(self, coro):
|
|
17
|
+
"""Submits a coroutine to the worker's event loop."""
|
|
18
|
+
return asyncio.run_coroutine_threadsafe(coro, self.loop)
|
|
19
|
+
|
|
20
|
+
def shutdown(self):
|
|
21
|
+
"""Terminates the event loop and worker thread."""
|
|
22
|
+
self.loop.call_soon_threadsafe(self.loop.stop)
|
|
23
|
+
self.thread.join()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Executor:
|
|
27
|
+
"""
|
|
28
|
+
Async pool to manage the execution of synchronous and asynchronous code.
|
|
29
|
+
Executor distributes tasks among a ThreadPool or among Async Workers.
|
|
30
|
+
"""
|
|
31
|
+
_instance = None
|
|
32
|
+
_lock = threading.Lock()
|
|
33
|
+
|
|
34
|
+
def __init__(self):
|
|
35
|
+
self.num_threads = envs.executor_num_threads
|
|
36
|
+
self.num_async_workers = envs.executor_num_async_workers
|
|
37
|
+
self.async_worker_index = 0
|
|
38
|
+
self.thread_pool = ThreadPoolExecutor(max_workers=self.num_threads)
|
|
39
|
+
self.async_workers = [AsyncWorker() for _ in range(self.num_async_workers)]
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def get_instance(cls):
|
|
43
|
+
"""Returns the singleton instance in a thread-safe manner."""
|
|
44
|
+
if cls._instance is None:
|
|
45
|
+
with cls._lock:
|
|
46
|
+
if cls._instance is None:
|
|
47
|
+
cls._instance = cls()
|
|
48
|
+
return cls._instance
|
|
49
|
+
|
|
50
|
+
def submit(self, f: Callable, *args, **kwargs):
|
|
51
|
+
"""
|
|
52
|
+
Submits a task to the appropriate pool based on the function type.
|
|
53
|
+
Returns a Future to track the result.
|
|
54
|
+
"""
|
|
55
|
+
if hasattr(f, "acall"):
|
|
56
|
+
coro = f.acall(*args, **kwargs)
|
|
57
|
+
return self._submit_to_async_worker(coro)
|
|
58
|
+
elif asyncio.iscoroutinefunction(f):
|
|
59
|
+
coro = f(*args, **kwargs)
|
|
60
|
+
return self._submit_to_async_worker(coro)
|
|
61
|
+
else:
|
|
62
|
+
return self.thread_pool.submit(f, *args, **kwargs)
|
|
63
|
+
|
|
64
|
+
def _submit_to_async_worker(self, coro):
|
|
65
|
+
"""Distribute a coroutine to an asynchronous worker using round-robin."""
|
|
66
|
+
worker = self.async_workers[self.async_worker_index]
|
|
67
|
+
self.async_worker_index = (self.async_worker_index + 1) % self.num_async_workers
|
|
68
|
+
return worker.submit(coro)
|
|
69
|
+
|
|
70
|
+
def shutdown(self):
|
|
71
|
+
"""Shutdown the executor, closing the pools."""
|
|
72
|
+
self.thread_pool.shutdown()
|
|
73
|
+
for worker in self.async_workers:
|
|
74
|
+
worker.shutdown()
|
|
75
|
+
|
|
76
|
+
def __del__(self):
|
|
77
|
+
self.shutdown()
|
msgflux/cache.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from msgflux.data.databases.base import BaseDB
|
|
4
|
+
from msgflux.message import Message
|
|
5
|
+
from msgflux.models.types import TextEmbedderModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def response_cache(
|
|
9
|
+
db: BaseDB,
|
|
10
|
+
task_inputs: Optional[str] = None,
|
|
11
|
+
response_mode: Optional[str] = None,
|
|
12
|
+
model: Optional[TextEmbedderModel] = None,
|
|
13
|
+
):
|
|
14
|
+
def decorator(func):
|
|
15
|
+
@wraps(func)
|
|
16
|
+
def wrapper(msg):
|
|
17
|
+
if isinstance(msg, Message):
|
|
18
|
+
if task_inputs is None:
|
|
19
|
+
raise ValueError(
|
|
20
|
+
"`task_inputs` is required when input "
|
|
21
|
+
"is a Message instance"
|
|
22
|
+
)
|
|
23
|
+
key = msg.get(task_inputs)
|
|
24
|
+
else:
|
|
25
|
+
key = msg
|
|
26
|
+
|
|
27
|
+
if model:
|
|
28
|
+
key = model(key)
|
|
29
|
+
|
|
30
|
+
cached_response = db(key)
|
|
31
|
+
|
|
32
|
+
if cached_response is not None:
|
|
33
|
+
if isinstance(msg, Message):
|
|
34
|
+
if response_mode is None:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
"`response_mode` is required when "
|
|
37
|
+
"input is a Message instance"
|
|
38
|
+
)
|
|
39
|
+
msg.set(response_mode, cached_response)
|
|
40
|
+
return msg
|
|
41
|
+
return cached_response
|
|
42
|
+
|
|
43
|
+
response = func(msg)
|
|
44
|
+
|
|
45
|
+
if response is not None:
|
|
46
|
+
db.add(key, response)
|
|
47
|
+
|
|
48
|
+
return response
|
|
49
|
+
return wrapper
|
|
50
|
+
return decorator
|
msgflux/data/__init__.py
ADDED
|
File without changes
|
msgflux/data/audio.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#https://github.com/openai/openai-python/blob/384e6b23ce0366d6b2f31cc98d35525da5b22c10/src/openai/helpers/local_audio_player.py#L19
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from typing import Dict
|
|
3
|
+
from msgflux._private.client import BaseClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseDB(BaseClient):
|
|
7
|
+
|
|
8
|
+
msgflux_type = "db"
|
|
9
|
+
to_ignore = ["client", "documents"]
|
|
10
|
+
|
|
11
|
+
def instance_type(self) -> Dict[str, str]:
|
|
12
|
+
return {"db_type": self.db_type}
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def add(self):
|
|
16
|
+
"""
|
|
17
|
+
Insert data on database
|
|
18
|
+
"""
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def _initialize(self):
|
|
23
|
+
"""
|
|
24
|
+
Initialize the class. This method must be implemented by subclasses.
|
|
25
|
+
|
|
26
|
+
This method is called during the deserialization process to ensure that the client
|
|
27
|
+
is properly initialized after its state has been restored.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
NotImplementedError: If the method is not implemented by the subclass.
|
|
31
|
+
"""
|
|
32
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from typing import Any, Dict, Type
|
|
2
|
+
from msgflux.data.databases.base import BaseDB
|
|
3
|
+
from msgflux.data.databases.types import (
|
|
4
|
+
#GeoDB,
|
|
5
|
+
#GraphDB,
|
|
6
|
+
KVDB,
|
|
7
|
+
#NoSQLDB,
|
|
8
|
+
#RelationalDB,
|
|
9
|
+
#TimeSeriesDB,
|
|
10
|
+
VectorDB,
|
|
11
|
+
)
|
|
12
|
+
from msgflux.utils.imports import import_module_from_lib
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_SUPPORTED_DB_TYPES = [
|
|
16
|
+
"kv",
|
|
17
|
+
"vector",
|
|
18
|
+
#"graph",
|
|
19
|
+
#"relational",
|
|
20
|
+
#"nosql",
|
|
21
|
+
#"time_series",
|
|
22
|
+
#"geo",
|
|
23
|
+
]
|
|
24
|
+
_DB_NAMESPACE_TRANSLATOR = {
|
|
25
|
+
"cachetools": "CacheTools",
|
|
26
|
+
"diskmanager": "DiskManager",
|
|
27
|
+
"faiss": "FAISS",
|
|
28
|
+
#"qdrant": "Qdrant",
|
|
29
|
+
#"postgres": "Postgres",
|
|
30
|
+
#"weavite": "Weavite",
|
|
31
|
+
#"milvus": "Milvus",
|
|
32
|
+
#"lance_db": "LanceDB",
|
|
33
|
+
#"vespa": "Vespa",
|
|
34
|
+
#"pinecone": "Pinecone",
|
|
35
|
+
#"cassandra": "Cassandra",
|
|
36
|
+
#"redis": "Redis",
|
|
37
|
+
#"deep_lake": "DeepLake",
|
|
38
|
+
#"marqo": "Marqo",
|
|
39
|
+
#"azure": "AzureAISearch",
|
|
40
|
+
#"supabase": "SupaBase",
|
|
41
|
+
#"elastic_search": "ElasticSearch",
|
|
42
|
+
#"chroma": "Chroma",
|
|
43
|
+
#"sqlite": "SQLite",
|
|
44
|
+
#"neo4j": "Neo4J",
|
|
45
|
+
}
|
|
46
|
+
_VECTOR_DB_PROVIDERS = [
|
|
47
|
+
#"qdrant",
|
|
48
|
+
#"postgres",
|
|
49
|
+
#"weavite",
|
|
50
|
+
#"milvus",
|
|
51
|
+
#"lance_db",
|
|
52
|
+
#"vespa",
|
|
53
|
+
#"redis",
|
|
54
|
+
#"cassandra",
|
|
55
|
+
#"deep_lake",
|
|
56
|
+
#"marqo",
|
|
57
|
+
#"elastic_search",
|
|
58
|
+
#"sqlite",
|
|
59
|
+
"faiss",
|
|
60
|
+
#"chroma",
|
|
61
|
+
]
|
|
62
|
+
#_GRAPH_DB_PROVIDERS = ["neo4j"]
|
|
63
|
+
_KV_DB_PROVIDERS = ["cachetools", "diskmanager"]
|
|
64
|
+
|
|
65
|
+
_PROVIDERS_BY_DB_TYPE = {
|
|
66
|
+
"kv": _KV_DB_PROVIDERS,
|
|
67
|
+
"vector": _VECTOR_DB_PROVIDERS,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DataBase:
|
|
72
|
+
supported_db_types = _SUPPORTED_DB_TYPES
|
|
73
|
+
providers_by_db_type = _PROVIDERS_BY_DB_TYPE
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def _get_db_class(cls, db_type: str, provider: str) -> Type[BaseDB]:
|
|
77
|
+
if db_type not in cls.supported_db_types:
|
|
78
|
+
raise ValueError(f"DB type `{db_type}` is not supported")
|
|
79
|
+
|
|
80
|
+
providers = cls.providers_by_db_type[db_type]
|
|
81
|
+
if provider not in providers:
|
|
82
|
+
raise ValueError(f"Provider `{provider}` is not supported for {db_type}")
|
|
83
|
+
|
|
84
|
+
if len(db_type) <= 3:
|
|
85
|
+
db_type = db_type.upper()
|
|
86
|
+
else:
|
|
87
|
+
db_type = db_type.title().replace("_", "")
|
|
88
|
+
|
|
89
|
+
provider_class_name = f"{_DB_NAMESPACE_TRANSLATOR[provider]}{db_type}DB"
|
|
90
|
+
module_name = f"msgflux.data.databases.providers.{provider}"
|
|
91
|
+
return import_module_from_lib(provider_class_name, module_name)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def _create_db(cls, db_type: str, provider: str, **kwargs) -> Type[BaseDB]:
|
|
95
|
+
db_cls = cls._get_db_class(db_type, provider)
|
|
96
|
+
return db_cls(**kwargs)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_serialized(cls, provider: str, db_type: str, params: Dict[str, Any]) -> Type[BaseDB]:
|
|
100
|
+
"""
|
|
101
|
+
Creates a db instance from serialized parameters without calling __init__.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
provider: The db provider (e.g., "faiss", "cachetools")
|
|
105
|
+
db_type: The type of db (e.g., "vector", "kv")
|
|
106
|
+
params: Dictionary containing the serialized db parameters
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
An instance of the appropriate db class with restored state
|
|
110
|
+
"""
|
|
111
|
+
db_cls = cls._get_db_class(db_type, provider)
|
|
112
|
+
# Create instance without calling __init__
|
|
113
|
+
instance = object.__new__(db_cls)
|
|
114
|
+
# Restore the instance state
|
|
115
|
+
instance.from_serialized(params)
|
|
116
|
+
return instance
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def kv(cls, provider: str, **kwargs) -> KVDB:
|
|
120
|
+
return cls._create_db("kv", provider, **kwargs)
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def vector(cls, provider: str, **kwargs) -> VectorDB:
|
|
124
|
+
return cls._create_db("vector", provider, **kwargs)
|
|
125
|
+
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from cachetools import TTLCache
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
|
+
import msgspec
|
|
4
|
+
from msgflux.data.databases.base import BaseDB
|
|
5
|
+
from msgflux.data.databases.types import KVDB
|
|
6
|
+
from msgflux.utils.convert import convert_str_to_hash
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CacheToolsKVDB(BaseDB, KVDB):
|
|
10
|
+
"""CacheTools Key-Value DB."""
|
|
11
|
+
|
|
12
|
+
provider = "cachetools"
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
ttl: Optional[int] = 3600,
|
|
17
|
+
maxsize: Optional[int] = 10000,
|
|
18
|
+
hash_key: Optional[bool] = True
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
Args:
|
|
22
|
+
ttl:
|
|
23
|
+
The time-to-live (TTL) for each cache entry in seconds.
|
|
24
|
+
maxsize:
|
|
25
|
+
The maximum number of items the cache can store.
|
|
26
|
+
hash_key:
|
|
27
|
+
Whether to hash the keys before storing them in the cache.
|
|
28
|
+
"""
|
|
29
|
+
self.hash_key = hash_key
|
|
30
|
+
self.maxsize = maxsize
|
|
31
|
+
self.ttl = ttl
|
|
32
|
+
self._initialize()
|
|
33
|
+
|
|
34
|
+
def _initialize(self):
|
|
35
|
+
self.client = TTLCache(maxsize=self.maxsize, ttl=self.ttl)
|
|
36
|
+
|
|
37
|
+
def add(self, documents: Union[List[Dict[str, Any]], Dict[str, Any]]):
|
|
38
|
+
if not isinstance(documents, list):
|
|
39
|
+
documents = [documents]
|
|
40
|
+
for document in documents:
|
|
41
|
+
for k, v in document.items():
|
|
42
|
+
if self.hash_key:
|
|
43
|
+
k = convert_str_to_hash(k)
|
|
44
|
+
v = msgspec.msgpack.encode(v)
|
|
45
|
+
self.client[k] = v
|
|
46
|
+
|
|
47
|
+
def _search(self, queries: Union[str, List[str]]):
|
|
48
|
+
if not isinstance(queries, list):
|
|
49
|
+
queries = [queries]
|
|
50
|
+
query_results = []
|
|
51
|
+
for query in queries:
|
|
52
|
+
if self.hash_key:
|
|
53
|
+
query = convert_str_to_hash(query)
|
|
54
|
+
v = self.client.get(query)
|
|
55
|
+
if v is not None:
|
|
56
|
+
v = msgspec.msgpack.decode(v)
|
|
57
|
+
query_results.append(v)
|
|
58
|
+
return query_results
|
|
59
|
+
|
|
60
|
+
def __call__(self, queries: Union[str, List[str]]) -> Union[Any, List[Any]]:
|
|
61
|
+
query_results = self._search(queries)
|
|
62
|
+
if len(query_results) == 1:
|
|
63
|
+
return query_results[0]
|
|
64
|
+
return query_results
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union
|
|
2
|
+
import msgspec
|
|
3
|
+
try:
|
|
4
|
+
from diskcache import Cache
|
|
5
|
+
except:
|
|
6
|
+
raise ImportError("`diskcache` is not detected, please install"
|
|
7
|
+
"using `pip install diskcache`")
|
|
8
|
+
from msgflux.data.databases.base import BaseDB
|
|
9
|
+
from msgflux.data.databases.types import KVDB
|
|
10
|
+
from msgflux.utils.convert import convert_str_to_hash
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DiskCacheKVDB(BaseDB, KVDB):
|
|
14
|
+
"""DiskCache Key-Value DB."""
|
|
15
|
+
|
|
16
|
+
provider = "diskcache"
|
|
17
|
+
|
|
18
|
+
def __init__(self, ttl: Optional[int] = 3600, hash_key: Optional[bool] = True):
|
|
19
|
+
"""
|
|
20
|
+
Args:
|
|
21
|
+
ttl:
|
|
22
|
+
The time-to-live (TTL) for each cache entry in seconds.
|
|
23
|
+
hash_key:
|
|
24
|
+
Whether to hash the keys before storing them in the cache.
|
|
25
|
+
"""
|
|
26
|
+
self.hash_key = hash_key
|
|
27
|
+
self.ttl = ttl
|
|
28
|
+
self._initialize()
|
|
29
|
+
|
|
30
|
+
def _initialize(self):
|
|
31
|
+
self.client = Cache(timeout=1)
|
|
32
|
+
|
|
33
|
+
def add(self, documents: Union[List[Dict[str, Any]], Dict[str, Any]]):
|
|
34
|
+
if not isinstance(documents, list):
|
|
35
|
+
documents = [documents]
|
|
36
|
+
for document in documents:
|
|
37
|
+
for k, v in document.items():
|
|
38
|
+
if self.hash_key:
|
|
39
|
+
k = convert_str_to_hash(k)
|
|
40
|
+
v = msgspec.msgpack.encode(v)
|
|
41
|
+
self.client.set(k, v, expire=self.ttl)
|
|
42
|
+
|
|
43
|
+
def _search(self, queries: Union[str, List[str]]):
|
|
44
|
+
if not isinstance(queries, list):
|
|
45
|
+
queries = [queries]
|
|
46
|
+
query_results = []
|
|
47
|
+
for query in queries:
|
|
48
|
+
if self.hash_key:
|
|
49
|
+
query = convert_str_to_hash(query)
|
|
50
|
+
v = self.client.get(query)
|
|
51
|
+
if v is not None:
|
|
52
|
+
v = msgspec.msgpack.decode(v)
|
|
53
|
+
query_results.append(v)
|
|
54
|
+
return query_results
|
|
55
|
+
|
|
56
|
+
def __call__(self, queries: Union[str, List[str]]) -> Union[Any, List[Any]]:
|
|
57
|
+
query_results = self._search(queries)
|
|
58
|
+
if len(query_results) == 1:
|
|
59
|
+
return query_results[0]
|
|
60
|
+
return query_results
|