vectorwave 0.1.8__tar.gz → 0.1.9__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.
- {vectorwave-0.1.8/src/vectorwave.egg-info → vectorwave-0.1.9}/PKG-INFO +46 -52
- {vectorwave-0.1.8 → vectorwave-0.1.9}/Readme.md +44 -50
- {vectorwave-0.1.8 → vectorwave-0.1.9}/pyproject.toml +2 -2
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/core/test_semantic_caching.py +6 -3
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/search/test_execution_search.py +23 -3
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/utils/test_replayer.py +45 -1
- vectorwave-0.1.9/src/tests/utils/test_status.py +126 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/database/db_search.py +55 -2
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/models/db_config.py +4 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/monitoring/alert/factory.py +4 -2
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/monitoring/tracer.py +71 -7
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/search/execution_search.py +2 -8
- vectorwave-0.1.9/src/vectorwave/utils/healer.py +155 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/utils/replayer.py +11 -4
- vectorwave-0.1.9/src/vectorwave/utils/replayer_semantic.py +224 -0
- vectorwave-0.1.9/src/vectorwave/utils/status.py +55 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/vectorizer/factory.py +12 -10
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/vectorizer/huggingface_vectorizer.py +6 -5
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/vectorizer/openai_vectorizer.py +6 -5
- {vectorwave-0.1.8 → vectorwave-0.1.9/src/vectorwave.egg-info}/PKG-INFO +46 -52
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave.egg-info/SOURCES.txt +4 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/LICENSE +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/MANIFEST.in +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/NOTICE +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/setup.cfg +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/batch/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/batch/test_batch.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/conftest.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/core/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/core/test_decorator.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/database/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/database/test_archiver.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/database/test_db.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/database/test_db_search.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/exception/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/models/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/models/test_db_config.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/monitoring/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/monitoring/alert/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/monitoring/alert/test_alerter.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/monitoring/test_async_trace.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/monitoring/test_tracer.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/prediction/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/search/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/utils/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/utils/test_function_cahe.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/tests/vectorizer/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/batch/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/batch/batch.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/core/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/core/core.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/core/decorator.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/core/generator.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/database/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/database/archiver.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/database/db.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/exception/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/exception/exceptions.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/models/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/monitoring/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/monitoring/alert/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/monitoring/alert/base.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/monitoring/alert/null_alerter.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/monitoring/alert/webhook_alerter.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/monitoring/monitoring.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/prediction/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/prediction/predictor.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/search/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/search/extended_search.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/search/rag_search.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/utils/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/utils/function_cache.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/utils/return_caching_utils.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/vectorizer/__init__.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave/vectorizer/base.py +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave.egg-info/dependency_links.txt +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave.egg-info/requires.txt +0 -0
- {vectorwave-0.1.8 → vectorwave-0.1.9}/src/vectorwave.egg-info/top_level.txt +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vectorwave
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: VectorWave: Seamless Auto-Vectorization Framework
|
|
5
5
|
Author-email: junyeonggim <junyeonggim5@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Repository, https://github.com/
|
|
7
|
+
Project-URL: Repository, https://github.com/cozymori/vectorwave
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.10
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -130,62 +130,56 @@ process_payment("user_789", 5000)
|
|
|
130
130
|
|
|
131
131
|
To use the LLM feature, you must specify dependencies and environment variables.
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
> # WEAVIATE_GENERATIVE_MODULE="generative-openai" (Required to enable the Weaviate module when using OpenAI LLM)
|
|
148
|
-
> ```
|
|
133
|
+
#### Prerequisites for AI Auto-Documentation
|
|
134
|
+
|
|
135
|
+
To use the AI-powered documentation feature, you must have the `openai` library installed and configure your API key.
|
|
136
|
+
|
|
137
|
+
1. **Install Library:**
|
|
138
|
+
```bash
|
|
139
|
+
pip install openai
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
2. **Set API Key:** Add your valid OpenAI API key to your `.env` file.
|
|
143
|
+
```ini
|
|
144
|
+
OPENAI_API_KEY="sk-proj-YOUR_API_KEY_HERE"
|
|
145
|
+
# WEAVIATE_GENERATIVE_MODULE="generative-openai" (Required to enable the Weaviate module when using OpenAI LLM)
|
|
146
|
+
```
|
|
149
147
|
|
|
150
148
|
### 2.2. 🚀 Usage: Auto-Generating Function Metadata (Auto=True)
|
|
151
149
|
|
|
152
150
|
Instead of manually defining `search_description` and `sequence_narrative`, you can use the `auto=True` flag.
|
|
153
151
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
> generate_and_register_metadata()
|
|
184
|
-
> ```
|
|
185
|
-
> ````
|
|
186
|
-
>
|
|
187
|
-
> *Note: Since this process involves LLM API calls, it can cause **latency** if run during server startup. It is recommended to execute this via a separate management script or an admin API endpoint in a production environment.*
|
|
152
|
+
#### 3. Automatic Function Metadata Generation (Auto=True)
|
|
153
|
+
|
|
154
|
+
You can use the `auto=True` flag instead of manually defining `search_description` and `sequence_narrative`.
|
|
155
|
+
|
|
156
|
+
1. **Mark Function:** Set `auto=True`. It is **strongly recommended to include a detailed Docstring** to enhance the LLM's analysis quality.
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# Code from vectorwave/test_ex/example.py
|
|
160
|
+
@vectorize(auto=True, team="loyalty-program")
|
|
161
|
+
def calculate_loyalty_points(purchase_amount: int, is_vip: bool):
|
|
162
|
+
"""
|
|
163
|
+
Function to calculate loyalty points based on purchase amount.
|
|
164
|
+
VIP customers earn double points.
|
|
165
|
+
"""
|
|
166
|
+
points = purchase_amount // 10
|
|
167
|
+
if is_vip:
|
|
168
|
+
points *= 2
|
|
169
|
+
return {"points": points, "tier": "VIP" if is_vip else "Regular"}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
2. **Trigger Generation:** Call `generate_and_register_metadata()` **immediately after** all `@vectorize` function definitions are complete. This function calls the LLM, vectorizes the generated metadata, and registers it to the DB.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# ... (After defining the calculate_loyalty_points function above)
|
|
176
|
+
|
|
177
|
+
# [Mandatory] Must be called after all function definitions are complete.
|
|
178
|
+
print("🚀 Checking for functions needing auto-documentation...")
|
|
179
|
+
generate_and_register_metadata()
|
|
180
|
+
```
|
|
188
181
|
|
|
182
|
+
> **Note:** Since this process involves LLM API calls, it can cause **latency** if run during server startup. It is recommended to execute this via a separate management script or an admin API endpoint in a production environment.
|
|
189
183
|
-----
|
|
190
184
|
|
|
191
185
|
#### Semantic Caching Example
|
|
@@ -104,62 +104,56 @@ process_payment("user_789", 5000)
|
|
|
104
104
|
|
|
105
105
|
To use the LLM feature, you must specify dependencies and environment variables.
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
> # WEAVIATE_GENERATIVE_MODULE="generative-openai" (Required to enable the Weaviate module when using OpenAI LLM)
|
|
122
|
-
> ```
|
|
107
|
+
#### Prerequisites for AI Auto-Documentation
|
|
108
|
+
|
|
109
|
+
To use the AI-powered documentation feature, you must have the `openai` library installed and configure your API key.
|
|
110
|
+
|
|
111
|
+
1. **Install Library:**
|
|
112
|
+
```bash
|
|
113
|
+
pip install openai
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
2. **Set API Key:** Add your valid OpenAI API key to your `.env` file.
|
|
117
|
+
```ini
|
|
118
|
+
OPENAI_API_KEY="sk-proj-YOUR_API_KEY_HERE"
|
|
119
|
+
# WEAVIATE_GENERATIVE_MODULE="generative-openai" (Required to enable the Weaviate module when using OpenAI LLM)
|
|
120
|
+
```
|
|
123
121
|
|
|
124
122
|
### 2.2. 🚀 Usage: Auto-Generating Function Metadata (Auto=True)
|
|
125
123
|
|
|
126
124
|
Instead of manually defining `search_description` and `sequence_narrative`, you can use the `auto=True` flag.
|
|
127
125
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
> generate_and_register_metadata()
|
|
158
|
-
> ```
|
|
159
|
-
> ````
|
|
160
|
-
>
|
|
161
|
-
> *Note: Since this process involves LLM API calls, it can cause **latency** if run during server startup. It is recommended to execute this via a separate management script or an admin API endpoint in a production environment.*
|
|
126
|
+
#### 3. Automatic Function Metadata Generation (Auto=True)
|
|
127
|
+
|
|
128
|
+
You can use the `auto=True` flag instead of manually defining `search_description` and `sequence_narrative`.
|
|
129
|
+
|
|
130
|
+
1. **Mark Function:** Set `auto=True`. It is **strongly recommended to include a detailed Docstring** to enhance the LLM's analysis quality.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
# Code from vectorwave/test_ex/example.py
|
|
134
|
+
@vectorize(auto=True, team="loyalty-program")
|
|
135
|
+
def calculate_loyalty_points(purchase_amount: int, is_vip: bool):
|
|
136
|
+
"""
|
|
137
|
+
Function to calculate loyalty points based on purchase amount.
|
|
138
|
+
VIP customers earn double points.
|
|
139
|
+
"""
|
|
140
|
+
points = purchase_amount // 10
|
|
141
|
+
if is_vip:
|
|
142
|
+
points *= 2
|
|
143
|
+
return {"points": points, "tier": "VIP" if is_vip else "Regular"}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
2. **Trigger Generation:** Call `generate_and_register_metadata()` **immediately after** all `@vectorize` function definitions are complete. This function calls the LLM, vectorizes the generated metadata, and registers it to the DB.
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
# ... (After defining the calculate_loyalty_points function above)
|
|
150
|
+
|
|
151
|
+
# [Mandatory] Must be called after all function definitions are complete.
|
|
152
|
+
print("🚀 Checking for functions needing auto-documentation...")
|
|
153
|
+
generate_and_register_metadata()
|
|
154
|
+
```
|
|
162
155
|
|
|
156
|
+
> **Note:** Since this process involves LLM API calls, it can cause **latency** if run during server startup. It is recommended to execute this via a separate management script or an admin API endpoint in a production environment.
|
|
163
157
|
-----
|
|
164
158
|
|
|
165
159
|
#### Semantic Caching Example
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vectorwave"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.9"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name = "junyeonggim", email = "junyeonggim5@gmail.com" },
|
|
10
10
|
]
|
|
@@ -32,7 +32,7 @@ dependencies = [
|
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[project.urls]
|
|
35
|
-
Repository = "https://github.com/
|
|
35
|
+
Repository = "https://github.com/cozymori/vectorwave"
|
|
36
36
|
|
|
37
37
|
[tool.setuptools.packages.find]
|
|
38
38
|
where = ["src"]
|
|
@@ -14,6 +14,8 @@ from vectorwave.batch.batch import get_batch_manager as real_get_batch_manager
|
|
|
14
14
|
from vectorwave.database.db import get_cached_client as real_get_cached_client
|
|
15
15
|
from vectorwave.models.db_config import get_weaviate_settings as real_get_settings
|
|
16
16
|
from vectorwave.vectorizer.factory import get_vectorizer as real_get_vectorizer
|
|
17
|
+
from vectorwave.monitoring.tracer import _create_input_vector_data
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
# --- Fixture Setup ---
|
|
19
21
|
|
|
@@ -326,7 +328,6 @@ def test_tracer_input_vector_data_and_masking(mock_caching_deps):
|
|
|
326
328
|
"""
|
|
327
329
|
Tests that the _create_input_vector_data function masks sensitive keys.
|
|
328
330
|
"""
|
|
329
|
-
from vectorwave.monitoring.tracer import _create_input_vector_data
|
|
330
331
|
|
|
331
332
|
# Arrange: settings has sensitive_keys={"secret_key"}
|
|
332
333
|
settings = mock_caching_deps["settings"]
|
|
@@ -343,10 +344,12 @@ def test_tracer_input_vector_data_and_masking(mock_caching_deps):
|
|
|
343
344
|
text_content = input_data["text"]
|
|
344
345
|
assert "test_func" in text_content
|
|
345
346
|
assert "amount" in text_content
|
|
346
|
-
|
|
347
|
+
|
|
348
|
+
assert "[MASKED]" not in text_content
|
|
349
|
+
assert "secret_key" not in text_content
|
|
347
350
|
assert "my_top_secret" not in text_content
|
|
348
351
|
|
|
349
|
-
# 2. Verification: Check
|
|
352
|
+
# 2. Verification: Check stored properties
|
|
350
353
|
props = input_data["properties"]
|
|
351
354
|
assert props["function"] == "test_func"
|
|
352
355
|
assert props["kwargs"]["amount"] == 100
|
|
@@ -54,7 +54,6 @@ mock_db_errors = [
|
|
|
54
54
|
{"timestamp_utc": (mock_now - timedelta(minutes=15)).isoformat(), "error_code": "INVALID_INPUT"}, # Too old
|
|
55
55
|
]
|
|
56
56
|
|
|
57
|
-
|
|
58
57
|
# [Core Fix] Create mock datetime class
|
|
59
58
|
# Replaces the datetime.datetime class itself.
|
|
60
59
|
MockDateTime = MagicMock()
|
|
@@ -80,10 +79,31 @@ def test_find_recent_errors(mock_find_executions):
|
|
|
80
79
|
filters_arg = call_args.kwargs['filters']
|
|
81
80
|
|
|
82
81
|
assert filters_arg['status'] == 'ERROR'
|
|
83
|
-
assert filters_arg['error_code'] == 'INVALID_INPUT'
|
|
82
|
+
assert filters_arg['error_code'] == ['INVALID_INPUT']
|
|
84
83
|
assert filters_arg['timestamp_utc__gte'] == expected_time_limit_iso
|
|
85
84
|
|
|
86
85
|
|
|
86
|
+
@patch('vectorwave.search.execution_search.datetime', MockDateTime)
|
|
87
|
+
@patch('vectorwave.search.execution_search.find_executions')
|
|
88
|
+
def test_find_recent_errors_multi_code(mock_find_executions):
|
|
89
|
+
"""
|
|
90
|
+
Tests if find_recent_errors handles multiple error codes correctly by passing the list.
|
|
91
|
+
(Tests the newly implemented multi-code filtering path)
|
|
92
|
+
"""
|
|
93
|
+
error_list = ["INVALID_INPUT", "TIMEOUT_ERROR"]
|
|
94
|
+
find_recent_errors(minutes_ago=20, limit=5, error_codes=error_list)
|
|
95
|
+
|
|
96
|
+
call_args = mock_find_executions.call_args
|
|
97
|
+
filters_arg = call_args.kwargs['filters']
|
|
98
|
+
|
|
99
|
+
assert filters_arg['status'] == 'ERROR'
|
|
100
|
+
assert filters_arg['error_code'] == error_list
|
|
101
|
+
assert call_args.kwargs['limit'] == 5
|
|
102
|
+
|
|
103
|
+
expected_time_limit_iso_20 = (mock_now - timedelta(minutes=20)).isoformat()
|
|
104
|
+
assert filters_arg['timestamp_utc__gte'] == expected_time_limit_iso_20
|
|
105
|
+
|
|
106
|
+
|
|
87
107
|
@patch('vectorwave.search.execution_search.find_executions')
|
|
88
108
|
def test_find_slowest_executions(mock_find_executions):
|
|
89
109
|
"""
|
|
@@ -122,4 +142,4 @@ def test_find_by_trace_id(mock_find_executions):
|
|
|
122
142
|
# 2. Verify sort order (time ascending)
|
|
123
143
|
assert call_args.kwargs['sort_by'] == 'timestamp_utc'
|
|
124
144
|
assert call_args.kwargs['sort_ascending'] == True
|
|
125
|
-
assert call_args.kwargs['limit'] == 100
|
|
145
|
+
assert call_args.kwargs['limit'] == 100
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
import json
|
|
3
|
+
import asyncio
|
|
4
|
+
import inspect
|
|
3
5
|
from unittest.mock import MagicMock, patch
|
|
4
6
|
from vectorwave.utils.replayer import VectorWaveReplayer
|
|
5
7
|
|
|
@@ -194,4 +196,46 @@ def test_replay_argument_filtering(mock_replayer_deps):
|
|
|
194
196
|
|
|
195
197
|
# Assert
|
|
196
198
|
# Should only be called with 'a=10', excluding 'team' and 'priority'
|
|
197
|
-
mock_func.assert_called_once_with(a=10)
|
|
199
|
+
mock_func.assert_called_once_with(a=10)
|
|
200
|
+
|
|
201
|
+
def test_replay_async_function_execution_fixed(mock_replayer_deps):
|
|
202
|
+
"""
|
|
203
|
+
[Case 5] Async Function Test (FIXED): Tests the async execution path using patching
|
|
204
|
+
of the import mechanism and executing the actual async function via asyncio.run.
|
|
205
|
+
"""
|
|
206
|
+
# Arrange
|
|
207
|
+
replayer = VectorWaveReplayer()
|
|
208
|
+
|
|
209
|
+
# 1. DB Mock Data
|
|
210
|
+
inputs = {"a": 1, "b": 2}
|
|
211
|
+
expected_result = 3
|
|
212
|
+
mock_logs = [create_mock_log("uuid-async-1", inputs, expected_result)]
|
|
213
|
+
mock_replayer_deps["query"].fetch_objects.return_value.objects = mock_logs
|
|
214
|
+
|
|
215
|
+
# 2. Define the actual ASYNC function for replayer to execute
|
|
216
|
+
async def real_async_add(a, b):
|
|
217
|
+
# This function will be called and executed by asyncio.run
|
|
218
|
+
await asyncio.sleep(0.001)
|
|
219
|
+
return a + b
|
|
220
|
+
|
|
221
|
+
# Manually attach the signature for the replayer's inspection check to pass
|
|
222
|
+
setattr(real_async_add, '__signature__', inspect.Signature([
|
|
223
|
+
inspect.Parameter('a', inspect.Parameter.POSITIONAL_OR_KEYWORD),
|
|
224
|
+
inspect.Parameter('b', inspect.Parameter.POSITIONAL_OR_KEYWORD)
|
|
225
|
+
]))
|
|
226
|
+
|
|
227
|
+
# 3. Patch importlib.import_module to return a mock module that contains the target function
|
|
228
|
+
mock_module = MagicMock()
|
|
229
|
+
mock_module.async_add = real_async_add # The mock module must have the function
|
|
230
|
+
|
|
231
|
+
with patch("vectorwave.utils.replayer.importlib.import_module", return_value=mock_module):
|
|
232
|
+
|
|
233
|
+
# Act
|
|
234
|
+
# replayer.replay (sync function) calls asyncio.run(real_async_add(**inputs)) internally.
|
|
235
|
+
result = replayer.replay("my_module.async_add", limit=1)
|
|
236
|
+
|
|
237
|
+
# Assert
|
|
238
|
+
# 1. The result should be successful and match the expected output
|
|
239
|
+
assert result["passed"] == 1
|
|
240
|
+
assert result["failed"] == 0
|
|
241
|
+
assert result["failures"] == []
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
from vectorwave.utils.status import get_db_status, get_registered_functions
|
|
4
|
+
from vectorwave.models.db_config import WeaviateSettings
|
|
5
|
+
|
|
6
|
+
# --- Fixtures ---
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def mock_status_deps(monkeypatch):
|
|
10
|
+
"""
|
|
11
|
+
Mocks dependencies of status.py (get_cached_client, get_weaviate_settings).
|
|
12
|
+
"""
|
|
13
|
+
# 1. Mock Weaviate Client
|
|
14
|
+
mock_client = MagicMock()
|
|
15
|
+
mock_get_client = MagicMock(return_value=mock_client)
|
|
16
|
+
|
|
17
|
+
# 2. Mock Settings
|
|
18
|
+
mock_settings = WeaviateSettings(COLLECTION_NAME="TestFunctions")
|
|
19
|
+
mock_get_settings = MagicMock(return_value=mock_settings)
|
|
20
|
+
|
|
21
|
+
# 3. Apply Patches
|
|
22
|
+
monkeypatch.setattr("vectorwave.utils.status.get_cached_client", mock_get_client)
|
|
23
|
+
monkeypatch.setattr("vectorwave.utils.status.get_weaviate_settings", mock_get_settings)
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
"client": mock_client,
|
|
27
|
+
"settings": mock_settings,
|
|
28
|
+
"get_client": mock_get_client
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# --- Tests for get_db_status ---
|
|
32
|
+
|
|
33
|
+
def test_get_db_status_ready(mock_status_deps):
|
|
34
|
+
"""
|
|
35
|
+
Case 1: Test if True is returned when DB is connected and ready.
|
|
36
|
+
"""
|
|
37
|
+
mock_status_deps["client"].is_ready.return_value = True
|
|
38
|
+
|
|
39
|
+
assert get_db_status() is True
|
|
40
|
+
mock_status_deps["client"].is_ready.assert_called_once()
|
|
41
|
+
|
|
42
|
+
def test_get_db_status_not_ready(mock_status_deps):
|
|
43
|
+
"""
|
|
44
|
+
Case 2: Test if False is returned when DB is connected but not ready.
|
|
45
|
+
"""
|
|
46
|
+
mock_status_deps["client"].is_ready.return_value = False
|
|
47
|
+
|
|
48
|
+
assert get_db_status() is False
|
|
49
|
+
|
|
50
|
+
def test_get_db_status_exception(mock_status_deps):
|
|
51
|
+
"""
|
|
52
|
+
Case 3: Test if False is returned and error is logged when an exception occurs during DB connection.
|
|
53
|
+
"""
|
|
54
|
+
# Set exception to occur on client call
|
|
55
|
+
mock_status_deps["get_client"].side_effect = Exception("Connection Error")
|
|
56
|
+
|
|
57
|
+
assert get_db_status() is False
|
|
58
|
+
|
|
59
|
+
# --- Tests for get_registered_functions ---
|
|
60
|
+
|
|
61
|
+
@patch("vectorwave.utils.status.get_db_status")
|
|
62
|
+
def test_get_registered_functions_db_offline(mock_get_db_status, mock_status_deps):
|
|
63
|
+
"""
|
|
64
|
+
Case 4: Test if an empty list is returned when DB is offline (False).
|
|
65
|
+
"""
|
|
66
|
+
mock_get_db_status.return_value = False
|
|
67
|
+
|
|
68
|
+
result = get_registered_functions()
|
|
69
|
+
|
|
70
|
+
assert result == []
|
|
71
|
+
# Should not access client or collection if DB is offline
|
|
72
|
+
mock_status_deps["client"].collections.get.assert_not_called()
|
|
73
|
+
|
|
74
|
+
@patch("vectorwave.utils.status.get_db_status")
|
|
75
|
+
def test_get_registered_functions_success(mock_get_db_status, mock_status_deps):
|
|
76
|
+
"""
|
|
77
|
+
Case 5: Test if the list of registered functions is returned correctly.
|
|
78
|
+
"""
|
|
79
|
+
# 1. Arrange
|
|
80
|
+
mock_get_db_status.return_value = True
|
|
81
|
+
|
|
82
|
+
# Mock collection and query results
|
|
83
|
+
mock_collection = MagicMock()
|
|
84
|
+
mock_status_deps["client"].collections.get.return_value = mock_collection
|
|
85
|
+
|
|
86
|
+
# Create fake Weaviate objects
|
|
87
|
+
obj1 = MagicMock()
|
|
88
|
+
obj1.properties = {"function_name": "func_a", "module_name": "mod_a"}
|
|
89
|
+
obj2 = MagicMock()
|
|
90
|
+
obj2.properties = {"function_name": "func_b", "module_name": "mod_b"}
|
|
91
|
+
|
|
92
|
+
# Mock result of fetch_objects call
|
|
93
|
+
mock_collection.iterator.return_value = [obj1, obj2]
|
|
94
|
+
|
|
95
|
+
# 2. Act
|
|
96
|
+
result = get_registered_functions()
|
|
97
|
+
|
|
98
|
+
# 3. Assert
|
|
99
|
+
assert len(result) == 2
|
|
100
|
+
assert result[0]["function_name"] == "func_a"
|
|
101
|
+
assert result[1]["function_name"] == "func_b"
|
|
102
|
+
|
|
103
|
+
# Verify correct name usage when retrieving collection
|
|
104
|
+
mock_status_deps["client"].collections.get.assert_called_with("TestFunctions")
|
|
105
|
+
|
|
106
|
+
mock_collection.iterator.assert_called_once()
|
|
107
|
+
call_kwargs = mock_collection.iterator.call_args.kwargs
|
|
108
|
+
assert "return_properties" in call_kwargs
|
|
109
|
+
assert "function_name" in call_kwargs["return_properties"]
|
|
110
|
+
|
|
111
|
+
@patch("vectorwave.utils.status.get_db_status")
|
|
112
|
+
def test_get_registered_functions_exception(mock_get_db_status, mock_status_deps):
|
|
113
|
+
"""
|
|
114
|
+
Case 6: Test if an empty list is returned and error is handled when an exception occurs during retrieval.
|
|
115
|
+
"""
|
|
116
|
+
# 1. Arrange
|
|
117
|
+
mock_get_db_status.return_value = True
|
|
118
|
+
|
|
119
|
+
# Induce exception during collection retrieval
|
|
120
|
+
mock_status_deps["client"].collections.get.side_effect = Exception("Query Failed")
|
|
121
|
+
|
|
122
|
+
# 2. Act
|
|
123
|
+
result = get_registered_functions()
|
|
124
|
+
|
|
125
|
+
# 3. Assert
|
|
126
|
+
assert result == []
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import weaviate
|
|
3
3
|
import weaviate.classes as wvc
|
|
4
|
-
from typing import Dict, Any, Optional, List
|
|
4
|
+
from typing import Dict, Any, Optional, List, Tuple
|
|
5
5
|
|
|
6
6
|
from weaviate.collections.classes.filters import _Filters
|
|
7
7
|
from weaviate.classes.query import Filter
|
|
@@ -33,7 +33,11 @@ def _build_weaviate_filters(filters: Optional[Dict[str, Any]]) -> _Filters | Non
|
|
|
33
33
|
prop = Filter.by_property(prop_name)
|
|
34
34
|
|
|
35
35
|
if operator == 'equal':
|
|
36
|
-
|
|
36
|
+
if isinstance(value, list) and value:
|
|
37
|
+
# Use contains_any for matching any value in the list (equivalent to SQL IN)
|
|
38
|
+
filter_list.append(prop.contains_any(value))
|
|
39
|
+
else:
|
|
40
|
+
filter_list.append(prop.equal(value))
|
|
37
41
|
elif operator == 'not_equal':
|
|
38
42
|
filter_list.append(prop.not_equal(value))
|
|
39
43
|
elif operator == 'gte': # Greater than or equal
|
|
@@ -356,3 +360,52 @@ def search_functions_hybrid(
|
|
|
356
360
|
except Exception as e:
|
|
357
361
|
logger.error("Error during Weaviate Hybrid search: %s", e)
|
|
358
362
|
raise WeaviateConnectionError(f"Failed to execute 'search_functions_hybrid': {e}")
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def check_semantic_drift(
|
|
366
|
+
vector: List[float],
|
|
367
|
+
function_name: str,
|
|
368
|
+
threshold: float,
|
|
369
|
+
k: int = 5
|
|
370
|
+
) -> Tuple[bool, float, Optional[str]]:
|
|
371
|
+
"""
|
|
372
|
+
KNN based semantic drift check.
|
|
373
|
+
"""
|
|
374
|
+
try:
|
|
375
|
+
settings = get_weaviate_settings()
|
|
376
|
+
client = get_cached_client()
|
|
377
|
+
collection = client.collections.get(settings.EXECUTION_COLLECTION_NAME)
|
|
378
|
+
|
|
379
|
+
response = collection.query.near_vector(
|
|
380
|
+
near_vector=vector,
|
|
381
|
+
limit=k,
|
|
382
|
+
filters=(
|
|
383
|
+
wvc.query.Filter.by_property("function_name").equal(function_name) &
|
|
384
|
+
wvc.query.Filter.by_property("status").equal("SUCCESS")
|
|
385
|
+
),
|
|
386
|
+
return_metadata=wvc.query.MetadataQuery(distance=True),
|
|
387
|
+
return_properties=[]
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
objects = response.objects
|
|
391
|
+
if not objects:
|
|
392
|
+
return False, 0.0, None
|
|
393
|
+
|
|
394
|
+
distances = [obj.metadata.distance for obj in objects]
|
|
395
|
+
avg_distance = sum(distances) / len(distances)
|
|
396
|
+
|
|
397
|
+
nearest_uuid = str(objects[0].uuid)
|
|
398
|
+
|
|
399
|
+
is_drift = avg_distance > threshold
|
|
400
|
+
|
|
401
|
+
if is_drift:
|
|
402
|
+
logger.warning(
|
|
403
|
+
f"🚨 [Semantic Drift] '{function_name}' detected anomaly! "
|
|
404
|
+
f"Avg Distance (k={len(objects)}): {avg_distance:.4f} (Threshold: {threshold})"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
return is_drift, avg_distance, nearest_uuid
|
|
408
|
+
|
|
409
|
+
except Exception as e:
|
|
410
|
+
logger.error(f"Failed to check semantic drift: {e}")
|
|
411
|
+
return False, 0.0, None
|
|
@@ -49,6 +49,10 @@ class WeaviateSettings(BaseSettings):
|
|
|
49
49
|
ALERTER_WEBHOOK_URL: Optional[str] = None
|
|
50
50
|
ALERTER_MIN_LEVEL: str = "ERROR"
|
|
51
51
|
|
|
52
|
+
DRIFT_DETECTION_ENABLED: bool = False
|
|
53
|
+
DRIFT_DISTANCE_THRESHOLD: float = 0.25
|
|
54
|
+
DRIFT_NEIGHBOR_AMOUNT: int = 5
|
|
55
|
+
|
|
52
56
|
SENSITIVE_FIELD_NAMES: str = "password,api_key,token,secret,auth_token"
|
|
53
57
|
sensitive_keys: Set[str] = set()
|
|
54
58
|
|
|
@@ -3,6 +3,9 @@ from ...models.db_config import get_weaviate_settings
|
|
|
3
3
|
from .base import BaseAlerter
|
|
4
4
|
from .webhook_alerter import WebhookAlerter
|
|
5
5
|
from .null_alerter import NullAlerter
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
6
9
|
|
|
7
10
|
@lru_cache()
|
|
8
11
|
def get_alerter() -> BaseAlerter:
|
|
@@ -11,9 +14,8 @@ def get_alerter() -> BaseAlerter:
|
|
|
11
14
|
|
|
12
15
|
if strategy == "webhook":
|
|
13
16
|
if not settings.ALERTER_WEBHOOK_URL:
|
|
14
|
-
|
|
17
|
+
logger.warning("ALERTER_STRATEGY='webhook' but ALERTER_WEBHOOK_URL is not set. Using 'none'.")
|
|
15
18
|
return NullAlerter()
|
|
16
19
|
return WebhookAlerter(url=settings.ALERTER_WEBHOOK_URL)
|
|
17
20
|
|
|
18
|
-
# 기본값
|
|
19
21
|
return NullAlerter()
|