vectorwave 0.2.6__tar.gz → 0.2.8__tar.gz

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.
Files changed (58) hide show
  1. vectorwave-0.2.8/PKG-INFO +60 -0
  2. vectorwave-0.2.8/Readme.md +33 -0
  3. vectorwave-0.2.8/crates/src/lib.rs +171 -0
  4. {vectorwave-0.2.6 → vectorwave-0.2.8}/pyproject.toml +1 -1
  5. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/core/decorator.py +35 -4
  6. vectorwave-0.2.8/src/vectorwave/database/db.py +308 -0
  7. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/database/db_search.py +15 -26
  8. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/monitoring/tracer.py +64 -131
  9. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/utils/return_caching_utils.py +18 -7
  10. vectorwave-0.2.6/PKG-INFO +0 -680
  11. vectorwave-0.2.6/Readme.md +0 -653
  12. vectorwave-0.2.6/crates/src/lib.rs +0 -193
  13. vectorwave-0.2.6/src/vectorwave/database/db.py +0 -413
  14. {vectorwave-0.2.6 → vectorwave-0.2.8}/crates/Cargo.lock +0 -0
  15. {vectorwave-0.2.6 → vectorwave-0.2.8}/crates/Cargo.toml +0 -0
  16. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/__init__.py +0 -0
  17. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/batch/__init__.py +0 -0
  18. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/batch/batch.py +0 -0
  19. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/core/__init__.py +0 -0
  20. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/core/auto_injector.py +0 -0
  21. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/core/core.py +0 -0
  22. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/core/generator.py +0 -0
  23. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/core/llm/__init__.py +0 -0
  24. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/core/llm/base.py +0 -0
  25. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/core/llm/factory.py +0 -0
  26. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/core/llm/openai_client.py +0 -0
  27. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/database/__init__.py +0 -0
  28. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/database/archiver.py +0 -0
  29. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/database/dataset.py +0 -0
  30. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/exception/__init__.py +0 -0
  31. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/exception/exceptions.py +0 -0
  32. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/models/__init__.py +0 -0
  33. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/models/db_config.py +0 -0
  34. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/monitoring/__init__.py +0 -0
  35. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/monitoring/alert/__init__.py +0 -0
  36. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/monitoring/alert/base.py +0 -0
  37. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/monitoring/alert/factory.py +0 -0
  38. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/monitoring/alert/null_alerter.py +0 -0
  39. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/monitoring/alert/webhook_alerter.py +0 -0
  40. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/monitoring/monitoring.py +0 -0
  41. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/prediction/__init__.py +0 -0
  42. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/prediction/predictor.py +0 -0
  43. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/search/__init__.py +0 -0
  44. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/search/execution_search.py +0 -0
  45. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/search/extended_search.py +0 -0
  46. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/search/rag_search.py +0 -0
  47. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/utils/__init__.py +0 -0
  48. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/utils/context.py +0 -0
  49. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/utils/function_cache.py +0 -0
  50. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/utils/healer.py +0 -0
  51. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/utils/replayer.py +0 -0
  52. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/utils/replayer_semantic.py +0 -0
  53. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/utils/status.py +0 -0
  54. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/vectorizer/__init__.py +0 -0
  55. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/vectorizer/base.py +0 -0
  56. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/vectorizer/factory.py +0 -0
  57. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/vectorizer/huggingface_vectorizer.py +0 -0
  58. {vectorwave-0.2.6 → vectorwave-0.2.8}/src/vectorwave/vectorizer/openai_vectorizer.py +0 -0
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: vectorwave
3
+ Version: 0.2.8
4
+ Classifier: Programming Language :: Python :: 3
5
+ Classifier: Programming Language :: Python :: 3.10
6
+ Classifier: Programming Language :: Python :: 3.11
7
+ Classifier: Programming Language :: Python :: 3.12
8
+ Classifier: Programming Language :: Python :: 3.13
9
+ Classifier: Programming Language :: Rust
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Requires-Dist: weaviate-client>=4.0.0
14
+ Requires-Dist: pydantic-settings>=2.0.0
15
+ Requires-Dist: sentence-transformers
16
+ Requires-Dist: requests
17
+ Requires-Dist: openai
18
+ License-File: LICENSE
19
+ License-File: NOTICE
20
+ Summary: VectorWave: Seamless Auto-Vectorization Framework
21
+ Author-email: junyeonggim <junyeonggim5@gmail.com>
22
+ License-Expression: MIT
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
25
+ Project-URL: Repository, https://github.com/cozymori/vectorwave
26
+
27
+ Need more information? Visit [here](https://cozymori.github.io/vectorwave-docs/)
28
+
29
+ # VectorWave
30
+ ![Star](https://badgen.net/github/stars/cozymori/vectorwave)
31
+ ![Release](https://badgen.net/github/release/cozymori/vectorwave)
32
+ ![Tag](https://badgen.net/github/tag/cozymori/vectorwave)
33
+ ![Commit](https://badgen.net/github/last-commit/cozymori/vectorwave)
34
+ ![Contributors](https://badgen.net/github/contributors/cozymori/vectorwave)
35
+ ![OpenPrs](https://badgen.net/github/open-prs/cozymori/vectorwave)
36
+ ![MergedPrs](https://badgen.net/github/merged-prs/cozymori/vectorwave)
37
+ ![Checks](https://badgen.net/github/checks/cozymori/vectorwave)
38
+ ![Pypi](https://badgen.net/pypi/v/vectorwave)
39
+ <br>**Seamless Auto-Vectorization Framework**
40
+
41
+ We transform volatile data that disappears the moment code is executed into a Searchable and Reusable permanent knowledge asset.
42
+
43
+ ### Requirements
44
+
45
+ * **Python**: 3.10 ~ 3.13
46
+ * **Docker**: Required to run the Weaviate database.
47
+ * (Optional) **OpenAI API Key**: Required for AI auto-documentation and high-performance embedding.
48
+
49
+ ### How to reach us
50
+
51
+ Have questions or found a bug? Please join our community.
52
+
53
+ * **GitHub Issues**: [https://github.com/cozymori/vectorwave/issues](https://github.com/cozymori/vectorwave/issues)
54
+
55
+ ### Contributors
56
+ [See the contributors of vectorwave](https://github.com/Cozymori/VectorWave/graphs/contributors)<br>
57
+ VectorWave is an open-source project and we welcome your contributions.
58
+ Please refer to [CONTRIBUTING.md](https://www.google.com/search?q=https://github.com/cozymori/vectorwave/blob/main/CONTRIBUTING.md) in the GitHub repository.
59
+
60
+
@@ -0,0 +1,33 @@
1
+ Need more information? Visit [here](https://cozymori.github.io/vectorwave-docs/)
2
+
3
+ # VectorWave
4
+ ![Star](https://badgen.net/github/stars/cozymori/vectorwave)
5
+ ![Release](https://badgen.net/github/release/cozymori/vectorwave)
6
+ ![Tag](https://badgen.net/github/tag/cozymori/vectorwave)
7
+ ![Commit](https://badgen.net/github/last-commit/cozymori/vectorwave)
8
+ ![Contributors](https://badgen.net/github/contributors/cozymori/vectorwave)
9
+ ![OpenPrs](https://badgen.net/github/open-prs/cozymori/vectorwave)
10
+ ![MergedPrs](https://badgen.net/github/merged-prs/cozymori/vectorwave)
11
+ ![Checks](https://badgen.net/github/checks/cozymori/vectorwave)
12
+ ![Pypi](https://badgen.net/pypi/v/vectorwave)
13
+ <br>**Seamless Auto-Vectorization Framework**
14
+
15
+ We transform volatile data that disappears the moment code is executed into a Searchable and Reusable permanent knowledge asset.
16
+
17
+ ### Requirements
18
+
19
+ * **Python**: 3.10 ~ 3.13
20
+ * **Docker**: Required to run the Weaviate database.
21
+ * (Optional) **OpenAI API Key**: Required for AI auto-documentation and high-performance embedding.
22
+
23
+ ### How to reach us
24
+
25
+ Have questions or found a bug? Please join our community.
26
+
27
+ * **GitHub Issues**: [https://github.com/cozymori/vectorwave/issues](https://github.com/cozymori/vectorwave/issues)
28
+
29
+ ### Contributors
30
+ [See the contributors of vectorwave](https://github.com/Cozymori/VectorWave/graphs/contributors)<br>
31
+ VectorWave is an open-source project and we welcome your contributions.
32
+ Please refer to [CONTRIBUTING.md](https://www.google.com/search?q=https://github.com/cozymori/vectorwave/blob/main/CONTRIBUTING.md) in the GitHub repository.
33
+
@@ -0,0 +1,171 @@
1
+ use pyo3::prelude::*;
2
+ use pyo3::types::{PyDict, PyList, PyString, PyBool, PyFloat, PyInt};
3
+ use std::collections::HashSet;
4
+ use std::thread;
5
+ use std::time::{Duration, Instant};
6
+ use std::sync::Mutex;
7
+ use crossbeam_channel::{bounded, Receiver, Sender, TrySendError};
8
+
9
+ struct LogItem {
10
+ collection: PyObject,
11
+ properties: Py<PyDict>,
12
+ uuid: Option<PyObject>,
13
+ vector: Option<Vec<f32>>,
14
+ }
15
+
16
+ #[pyclass]
17
+ struct RustBatchManager {
18
+ sender: Sender<LogItem>,
19
+ #[allow(dead_code)]
20
+ flush_callback: PyObject,
21
+ worker_handle: Mutex<Option<thread::JoinHandle<()>>>,
22
+ stop_signal: Sender<()>,
23
+ }
24
+
25
+ #[pymethods]
26
+ impl RustBatchManager {
27
+ #[new]
28
+ fn new(
29
+ py: Python<'_>,
30
+ callback: PyObject,
31
+ batch_threshold: usize,
32
+ flush_interval_ms: u64,
33
+ ) -> Self {
34
+ let (tx, rx) = bounded::<LogItem>(10000);
35
+ let (stop_tx, stop_rx) = bounded(1);
36
+ let worker_callback = callback.clone_ref(py);
37
+
38
+ let handle = thread::spawn(move || {
39
+ Self::worker_loop(rx, stop_rx, worker_callback, batch_threshold, flush_interval_ms);
40
+ });
41
+
42
+ RustBatchManager {
43
+ sender: tx,
44
+ flush_callback: callback,
45
+ worker_handle: Mutex::new(Some(handle)),
46
+ stop_signal: stop_tx,
47
+ }
48
+ }
49
+
50
+ #[pyo3(signature = (collection, properties, uuid=None, vector=None))]
51
+ fn add_object(&self, collection: PyObject, properties: Py<PyDict>, uuid: Option<PyObject>, vector: Option<Vec<f32>>) {
52
+ let item = LogItem { collection, properties, uuid, vector };
53
+ let _ = self.sender.try_send(item);
54
+ }
55
+
56
+ fn shutdown(&self, py: Python<'_>) {
57
+ let _ = self.stop_signal.send(());
58
+ py.allow_threads(|| {
59
+ if let Ok(mut handle_guard) = self.worker_handle.lock() {
60
+ if let Some(handle) = handle_guard.take() {
61
+ let _ = handle.join();
62
+ }
63
+ }
64
+ });
65
+ }
66
+ }
67
+
68
+ impl RustBatchManager {
69
+ fn worker_loop(rx: Receiver<LogItem>, stop_rx: Receiver<()>, callback: PyObject, threshold: usize, interval_ms: u64) {
70
+ let mut buffer = Vec::with_capacity(threshold);
71
+ let mut last_flush = Instant::now();
72
+ let flush_interval = Duration::from_millis(interval_ms);
73
+
74
+ loop {
75
+ crossbeam_channel::select! {
76
+ recv(stop_rx) -> msg => {
77
+ break;
78
+ }
79
+ recv(rx) -> msg => {
80
+ match msg {
81
+ Ok(item) => buffer.push(item),
82
+ Err(_) => break,
83
+ }
84
+ }
85
+ default(Duration::from_millis(100)) => {}
86
+ }
87
+
88
+
89
+ if stop_rx.is_empty() && (buffer.len() >= threshold || (last_flush.elapsed() >= flush_interval && !buffer.is_empty())) {
90
+
91
+ let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
92
+ Self::flush_buffer(&buffer, &callback);
93
+ }));
94
+ buffer.clear();
95
+ last_flush = Instant::now();
96
+ }
97
+ }
98
+ }
99
+
100
+ fn flush_buffer(buffer: &Vec<LogItem>, callback: &PyObject) {
101
+ Python::with_gil(|py| {
102
+ let py_list = PyList::empty(py);
103
+
104
+ for item in buffer {
105
+ let dict = PyDict::new(py);
106
+ let mut set_field = |key: &str, val: PyObject| -> bool {
107
+ if let Err(_) = dict.set_item(key, val) { return false; }
108
+ true
109
+ };
110
+
111
+ if !set_field("collection", item.collection.clone_ref(py)) { continue; }
112
+ if !set_field("properties", item.properties.to_object(py)) { continue; }
113
+ let uuid_val = item.uuid.as_ref().map_or(py.None(), |u| u.clone_ref(py));
114
+ if !set_field("uuid", uuid_val) { continue; }
115
+ let vector_val = item.vector.as_ref().map_or(py.None(), |v| v.to_object(py));
116
+ if !set_field("vector", vector_val) { continue; }
117
+
118
+ if let Err(e) = py_list.append(dict) {
119
+ eprintln!("[RustCore] ⚠️ Failed to append item: {}", e);
120
+ }
121
+ }
122
+
123
+ if let Err(e) = callback.call1(py, (py_list,)) {
124
+ eprintln!("[RustCore] ⚠️ Flush failed: {}", e);
125
+ }
126
+ });
127
+ }
128
+ }
129
+
130
+ fn process_recursive(py: Python, value: &Bound<'_, PyAny>, sensitive_set: &HashSet<String>) -> PyResult<PyObject> {
131
+ if let Ok(dict_obj) = value.downcast::<PyDict>() {
132
+ let new_dict = PyDict::new(py);
133
+ for (k, v) in dict_obj {
134
+ let k_str = k.to_string().to_lowercase();
135
+ if sensitive_set.contains(&k_str) {
136
+ new_dict.set_item(k, "[MASKED]")?;
137
+ } else {
138
+ new_dict.set_item(k, process_recursive(py, &v, sensitive_set)?)?;
139
+ }
140
+ }
141
+ Ok(new_dict.into())
142
+ } else if let Ok(list_obj) = value.downcast::<PyList>() {
143
+ let new_list = PyList::empty(py);
144
+ for item in list_obj {
145
+ new_list.append(process_recursive(py, &item, sensitive_set)?)?;
146
+ }
147
+ Ok(new_list.into())
148
+ } else {
149
+ if value.is_none() || value.is_instance_of::<PyBool>() || value.is_instance_of::<PyFloat>() || value.is_instance_of::<PyInt>() || value.is_instance_of::<PyString>() {
150
+ Ok(value.clone().unbind())
151
+ } else {
152
+ match value.str() {
153
+ Ok(s) => Ok(s.into()),
154
+ Err(_) => Ok(PyString::new(py, "[SERIALIZATION_ERROR]").into())
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ #[pyfunction]
161
+ fn mask_and_serialize(py: Python, data: &Bound<'_, PyAny>, sensitive_keys: Vec<String>) -> PyResult<PyObject> {
162
+ let sensitive_set: HashSet<String> = sensitive_keys.into_iter().map(|s| s.to_lowercase()).collect();
163
+ process_recursive(py, data, &sensitive_set)
164
+ }
165
+
166
+ #[pymodule]
167
+ fn vectorwave_core(m: &Bound<'_, PyModule>) -> PyResult<()> {
168
+ m.add_class::<RustBatchManager>()?;
169
+ m.add_function(wrap_pyfunction!(mask_and_serialize, m)?)?;
170
+ Ok(())
171
+ }
@@ -5,7 +5,7 @@ build-backend = "maturin"
5
5
 
6
6
  [project]
7
7
  name = "vectorwave"
8
- version = "0.2.06"
8
+ version = "0.2.8"
9
9
  authors = [
10
10
  { name = "junyeonggim", email = "junyeonggim5@gmail.com" },
11
11
  ]
@@ -26,6 +26,9 @@ def vectorize(search_description: Optional[str] = None,
26
26
  cache_threshold: float = 0.9,
27
27
  replay: bool = False,
28
28
  attributes_to_capture: Optional[List[str]] = None,
29
+ capture_inputs: bool = False,
30
+ semantic_cache_filters: Optional[Dict[str, Any]] = None,
31
+ semantic_cache_scope: Optional[List[str]] = None,
29
32
  **execution_tags):
30
33
  """
31
34
  VectorWave Decorator with Auto-Generation support.
@@ -60,6 +63,15 @@ def vectorize(search_description: Optional[str] = None,
60
63
  if attr not in final_attributes:
61
64
  final_attributes.append(attr)
62
65
 
66
+ if capture_inputs:
67
+ try:
68
+ sig = inspect.signature(func)
69
+ for param_name in sig.parameters:
70
+ if param_name not in ('self', 'cls') and param_name not in final_attributes:
71
+ final_attributes.append(param_name)
72
+ except Exception as e:
73
+ logger.warning(f"Failed to inspect signature for auto-capture in '{function_name}': {e}")
74
+
63
75
  if replay:
64
76
  try:
65
77
  sig = inspect.signature(func)
@@ -109,7 +121,6 @@ def vectorize(search_description: Optional[str] = None,
109
121
  "static_properties": static_properties
110
122
  })
111
123
  else:
112
- # [Existing] Immediate Registration Mode
113
124
  current_content_hash = function_cache_manager.calculate_content_hash(func_identifier, static_properties)
114
125
 
115
126
  if function_cache_manager.is_cached_and_unchanged(func_uuid, current_content_hash):
@@ -137,6 +148,25 @@ def vectorize(search_description: Optional[str] = None,
137
148
  except Exception as e:
138
149
  logger.error("Error in @vectorize setup for '%s': %s", func.__name__, e)
139
150
 
151
+ def resolve_semantic_filters(args, kwargs):
152
+ runtime_filters = semantic_cache_filters.copy() if semantic_cache_filters else {}
153
+
154
+ if semantic_cache_scope:
155
+ try:
156
+ sig = inspect.signature(func)
157
+ bound_args = sig.bind(*args, **kwargs)
158
+ bound_args.apply_defaults()
159
+
160
+ for arg_name in semantic_cache_scope:
161
+ if arg_name in bound_args.arguments:
162
+ runtime_filters[arg_name] = bound_args.arguments[arg_name]
163
+ else:
164
+ logger.warning(f"Semantic Scope: Argument '{arg_name}' not found in call to '{function_name}'.")
165
+ except Exception as e:
166
+ logger.error(f"Failed to resolve semantic_cache_scope for '{function_name}': {e}")
167
+
168
+ return runtime_filters
169
+
140
170
  # --- Wrapper Logic ---
141
171
 
142
172
  if is_async_func:
@@ -144,7 +174,6 @@ def vectorize(search_description: Optional[str] = None,
144
174
  @trace_span(attributes_to_capture=final_attributes, capture_return_value=capture_return_value)
145
175
  @wraps(func)
146
176
  async def inner_wrapper(*args, **kwargs):
147
- # Remove injected tags from kwargs before calling original func
148
177
  clean_kwargs = {k: v for k, v in kwargs.items() if
149
178
  k not in valid_execution_tags and k != 'function_uuid' and k != 'exec_source'}
150
179
  return await func(*args, **clean_kwargs)
@@ -152,7 +181,8 @@ def vectorize(search_description: Optional[str] = None,
152
181
  @wraps(func)
153
182
  async def outer_wrapper(*args, **kwargs):
154
183
  if semantic_cache:
155
- cached = _check_and_return_cached_result(func, args, kwargs, function_name, cache_threshold, True)
184
+ filters = resolve_semantic_filters(args, kwargs)
185
+ cached = _check_and_return_cached_result(func, args, kwargs, function_name, cache_threshold, True, filters=filters)
156
186
  if cached is not None: return cached
157
187
 
158
188
  full_kwargs = kwargs.copy()
@@ -176,7 +206,8 @@ def vectorize(search_description: Optional[str] = None,
176
206
  @wraps(func)
177
207
  def outer_wrapper(*args, **kwargs):
178
208
  if semantic_cache:
179
- cached = _check_and_return_cached_result(func, args, kwargs, function_name, cache_threshold, False)
209
+ filters = resolve_semantic_filters(args, kwargs)
210
+ cached = _check_and_return_cached_result(func, args, kwargs, function_name, cache_threshold, False, filters=filters)
180
211
  if cached is not None: return cached
181
212
 
182
213
  full_kwargs = kwargs.copy()
@@ -0,0 +1,308 @@
1
+ import logging
2
+ from functools import lru_cache
3
+
4
+ import weaviate
5
+ import weaviate.classes.config as wvc
6
+ from weaviate.classes.config import Tokenization
7
+ from vectorwave.exception.exceptions import (
8
+ WeaviateConnectionError,
9
+ WeaviateNotReadyError,
10
+ SchemaCreationError
11
+ )
12
+ from vectorwave.models.db_config import WeaviateSettings
13
+ from vectorwave.models.db_config import get_weaviate_settings
14
+ from weaviate.config import AdditionalConfig
15
+ from weaviate.exceptions import WeaviateConnectionError as WeaviateClientConnectionError
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def get_weaviate_client(settings: WeaviateSettings) -> weaviate.WeaviateClient:
21
+ try:
22
+ client = weaviate.connect_to_local(
23
+ host=settings.WEAVIATE_HOST,
24
+ port=settings.WEAVIATE_PORT,
25
+ grpc_port=settings.WEAVIATE_GRPC_PORT,
26
+ additional_config=AdditionalConfig(
27
+ dynamic=True,
28
+ batch_size=20,
29
+ timeout_retries=3
30
+ )
31
+ )
32
+ except WeaviateClientConnectionError as e:
33
+ raise WeaviateConnectionError(f"Failed to connect to Weaviate: {e}")
34
+ except Exception as e:
35
+ raise WeaviateConnectionError(f"An unknown error occurred while connecting to Weaviate: {e}")
36
+
37
+ if not client.is_ready():
38
+ raise WeaviateNotReadyError("Connected to Weaviate, but the server is not ready.")
39
+
40
+ logger.info("Weaviate client connected successfully")
41
+ return client
42
+
43
+
44
+ @lru_cache()
45
+ def get_cached_client() -> weaviate.WeaviateClient:
46
+ logger.debug("Creating and caching new Weaviate client instance")
47
+ settings = get_weaviate_settings()
48
+ client = get_weaviate_client(settings)
49
+ return client
50
+
51
+
52
+ def _create_property_from_config(name: str, prop_details: dict) -> wvc.Property:
53
+ dtype_str = prop_details.get("data_type")
54
+ if not dtype_str:
55
+ raise ValueError(f"Property '{name}' missing 'data_type'")
56
+
57
+ if not hasattr(wvc.DataType, dtype_str.upper()):
58
+ raise ValueError(f"Invalid data_type '{dtype_str}' for property '{name}'")
59
+
60
+ data_type = getattr(wvc.DataType, dtype_str.upper())
61
+ description = prop_details.get("description")
62
+
63
+ tokenization = None
64
+ token_str = prop_details.get("tokenization")
65
+
66
+ if token_str:
67
+ token_map = {
68
+ "word": Tokenization.WORD,
69
+ "whitespace": Tokenization.WHITESPACE,
70
+ "field": Tokenization.FIELD,
71
+ "lowercase": Tokenization.LOWERCASE
72
+ }
73
+ token_key = token_str.lower()
74
+ if token_key in token_map:
75
+ tokenization = token_map[token_key]
76
+ else:
77
+ logger.warning(f"Invalid tokenization '{token_str}' for '{name}'. Using default.")
78
+
79
+ return wvc.Property(
80
+ name=name,
81
+ data_type=data_type,
82
+ description=description,
83
+ tokenization=tokenization
84
+ )
85
+
86
+
87
+ def create_vectorwave_schema(client: weaviate.WeaviateClient, settings: WeaviateSettings):
88
+ collection_name = settings.COLLECTION_NAME
89
+
90
+ if client.collections.exists(collection_name):
91
+ logger.info("Collection '%s' already exists, skipping creation", collection_name)
92
+ return client.collections.get(collection_name)
93
+
94
+ logger.info("Creating collection '%s'", collection_name)
95
+
96
+ base_properties = [
97
+ wvc.Property(name="function_name", data_type=wvc.DataType.TEXT),
98
+ wvc.Property(name="module_name", data_type=wvc.DataType.TEXT),
99
+ wvc.Property(name="docstring", data_type=wvc.DataType.TEXT),
100
+ wvc.Property(name="source_code", data_type=wvc.DataType.TEXT),
101
+ wvc.Property(name="search_description", data_type=wvc.DataType.TEXT),
102
+ wvc.Property(name="sequence_narrative", data_type=wvc.DataType.TEXT),
103
+ ]
104
+
105
+ custom_properties = []
106
+ if settings.custom_properties:
107
+ logger.info(f"Adding {len(settings.custom_properties)} custom properties to '{collection_name}'")
108
+ for name, prop_details in settings.custom_properties.items():
109
+ try:
110
+ prop = _create_property_from_config(name, prop_details)
111
+ custom_properties.append(prop)
112
+ except Exception as e:
113
+ logger.error(f"Invalid property definition for '{name}': {e}")
114
+ raise SchemaCreationError(f"Invalid property definition for '{name}': {e}")
115
+
116
+ all_properties = base_properties + custom_properties
117
+
118
+ vector_config = wvc.Configure.Vectorizer.none()
119
+ generative_config = None
120
+
121
+ if settings.VECTORIZER.lower() == "weaviate_module":
122
+ if settings.WEAVIATE_VECTORIZER_MODULE == "text2vec-openai":
123
+ vector_config = wvc.Configure.Vectorizer.text2vec_openai()
124
+ if settings.WEAVIATE_GENERATIVE_MODULE == "generative-openai":
125
+ generative_config = wvc.Configure.Generative.openai()
126
+
127
+ elif settings.VECTORIZER.lower() not in ["none", "huggingface", "openai_client"]:
128
+ raise SchemaCreationError(f"Invalid VECTORIZER setting: {settings.VECTORIZER}")
129
+
130
+ try:
131
+ return client.collections.create(
132
+ name=collection_name,
133
+ properties=all_properties,
134
+ vectorizer_config=vector_config,
135
+ generative_config=generative_config
136
+ )
137
+ except Exception as e:
138
+ raise SchemaCreationError(f"Error during schema creation: {e}")
139
+
140
+
141
+ def create_execution_schema(client: weaviate.WeaviateClient, settings: WeaviateSettings):
142
+ collection_name = settings.EXECUTION_COLLECTION_NAME
143
+
144
+ if client.collections.exists(collection_name):
145
+ logger.info("Collection '%s' already exists, skipping creation", collection_name)
146
+ return client.collections.get(collection_name)
147
+
148
+ logger.info("Creating collection '%s'", collection_name)
149
+
150
+ properties = [
151
+ wvc.Property(name="trace_id", data_type=wvc.DataType.TEXT),
152
+ wvc.Property(name="span_id", data_type=wvc.DataType.TEXT),
153
+ wvc.Property(name="parent_span_id", data_type=wvc.DataType.TEXT),
154
+ wvc.Property(name="function_name", data_type=wvc.DataType.TEXT),
155
+ wvc.Property(name="function_uuid", data_type=wvc.DataType.UUID),
156
+ wvc.Property(name="timestamp_utc", data_type=wvc.DataType.DATE),
157
+ wvc.Property(name="duration_ms", data_type=wvc.DataType.NUMBER),
158
+ wvc.Property(name="status", data_type=wvc.DataType.TEXT),
159
+ wvc.Property(name="error_message", data_type=wvc.DataType.TEXT),
160
+ wvc.Property(name="error_code", data_type=wvc.DataType.TEXT),
161
+ wvc.Property(name="return_value", data_type=wvc.DataType.TEXT),
162
+ wvc.Property(name="exec_source", data_type=wvc.DataType.TEXT)
163
+ ]
164
+
165
+ if settings.custom_properties:
166
+ logger.info(f"Adding {len(settings.custom_properties)} custom properties to '{collection_name}'")
167
+ for name, prop_details in settings.custom_properties.items():
168
+ try:
169
+ prop = _create_property_from_config(name, prop_details)
170
+ properties.append(prop)
171
+ except Exception as e:
172
+ logger.error(f"Invalid property definition for '{name}': {e}")
173
+ raise SchemaCreationError(f"Invalid property definition for '{name}': {e}")
174
+
175
+ try:
176
+ return client.collections.create(
177
+ name=collection_name,
178
+ properties=properties,
179
+ vectorizer_config=wvc.Configure.Vectorizer.none(),
180
+ )
181
+ except Exception as e:
182
+ raise SchemaCreationError(f"Error during execution schema creation: {e}")
183
+
184
+
185
+ def create_golden_dataset_schema(client: weaviate.WeaviateClient, settings: WeaviateSettings):
186
+ collection_name = settings.GOLDEN_COLLECTION_NAME
187
+
188
+ if client.collections.exists(collection_name):
189
+ logger.info("Collection '%s' already exists.", collection_name)
190
+ return client.collections.get(collection_name)
191
+
192
+ logger.info("Creating collection '%s'", collection_name)
193
+
194
+ properties = [
195
+ wvc.Property(name="original_uuid", data_type=wvc.DataType.TEXT),
196
+ wvc.Property(name="function_name", data_type=wvc.DataType.TEXT),
197
+ wvc.Property(name="function_uuid", data_type=wvc.DataType.UUID),
198
+ wvc.Property(name="return_value", data_type=wvc.DataType.TEXT),
199
+ wvc.Property(name="note", data_type=wvc.DataType.TEXT),
200
+ wvc.Property(name="created_at", data_type=wvc.DataType.DATE),
201
+ wvc.Property(name="tags", data_type=wvc.DataType.TEXT_ARRAY)
202
+ ]
203
+
204
+ if settings.custom_properties:
205
+ logger.info(f"Adding {len(settings.custom_properties)} custom properties to '{collection_name}'")
206
+ for name, prop_details in settings.custom_properties.items():
207
+ try:
208
+ prop = _create_property_from_config(name, prop_details)
209
+ properties.append(prop)
210
+ except Exception as e:
211
+ logger.error(f"Invalid property definition for '{name}' in Golden Dataset: {e}")
212
+ raise SchemaCreationError(f"Invalid property definition for '{name}': {e}")
213
+
214
+ try:
215
+ return client.collections.create(
216
+ name=collection_name,
217
+ properties=properties,
218
+ vectorizer_config=wvc.Configure.Vectorizer.none(),
219
+ )
220
+ except Exception as e:
221
+ raise SchemaCreationError(f"Error creating Golden Dataset schema: {e}")
222
+
223
+
224
+ def create_usage_schema(client: weaviate.WeaviateClient, settings: WeaviateSettings):
225
+ collection_name = "VectorWaveTokenUsage"
226
+ if client.collections.exists(collection_name):
227
+ return client.collections.get(collection_name)
228
+
229
+ logger.info("Creating collection '%s'", collection_name)
230
+ properties = [
231
+ wvc.Property(name="timestamp_utc", data_type=wvc.DataType.DATE),
232
+ wvc.Property(name="model", data_type=wvc.DataType.TEXT),
233
+ wvc.Property(name="usage_type", data_type=wvc.DataType.TEXT),
234
+ wvc.Property(name="category", data_type=wvc.DataType.TEXT),
235
+ wvc.Property(name="tokens", data_type=wvc.DataType.INT),
236
+ ]
237
+ try:
238
+ return client.collections.create(
239
+ name=collection_name,
240
+ properties=properties,
241
+ vectorizer_config=wvc.Configure.Vectorizer.none(),
242
+ )
243
+ except Exception as e:
244
+ raise SchemaCreationError(f"Error usage schema: {e}")
245
+
246
+
247
+ def update_database_schema():
248
+ """
249
+ [Migration Utility]
250
+ Updates the schema of the running database.
251
+ Finds properties defined in .weaviate_properties that are missing from the DB and adds them.
252
+ Used to apply new filtering features without data loss.
253
+ """
254
+ try:
255
+ settings = get_weaviate_settings()
256
+ client = get_cached_client()
257
+
258
+ target_collections = [
259
+ settings.EXECUTION_COLLECTION_NAME,
260
+ settings.GOLDEN_COLLECTION_NAME
261
+ ]
262
+
263
+ logger.info("🚀 Starting Schema Migration...")
264
+
265
+ for col_name in target_collections:
266
+ if not client.collections.exists(col_name):
267
+ logger.warning(f"Collection '{col_name}' does not exist. Skipping.")
268
+ continue
269
+
270
+ collection = client.collections.get(col_name)
271
+ # 현재 DB에 존재하는 속성 이름 가져오기
272
+ existing_props = {p.name for p in collection.config.get().properties}
273
+
274
+ # 추가할 속성 확인
275
+ if settings.custom_properties:
276
+ for name, prop_details in settings.custom_properties.items():
277
+ if name not in existing_props:
278
+ logger.info(f"➕ Adding new property '{name}' to '{col_name}'...")
279
+ try:
280
+ new_prop = _create_property_from_config(name, prop_details)
281
+ collection.config.add_property(new_prop)
282
+ logger.info(f" ✅ Property '{name}' added successfully.")
283
+ except Exception as e:
284
+ logger.error(f" ❌ Failed to add property '{name}': {e}")
285
+ else:
286
+ logger.debug(f" Pass: Property '{name}' already exists.")
287
+
288
+ logger.info("✨ Schema Migration Completed!")
289
+ return True
290
+
291
+ except Exception as e:
292
+ logger.error(f"Migration failed: {e}", exc_info=True)
293
+ return False
294
+
295
+
296
+ def initialize_database():
297
+ try:
298
+ settings = get_weaviate_settings()
299
+ client = get_cached_client()
300
+ if client:
301
+ create_vectorwave_schema(client, settings)
302
+ create_execution_schema(client, settings)
303
+ create_usage_schema(client, settings)
304
+ create_golden_dataset_schema(client, settings)
305
+ return client
306
+ except Exception as e:
307
+ logger.error("Failed to initialize VectorWave database: %s", e)
308
+ return None