vectorwave 0.1.3__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.
- tests/__init__.py +0 -0
- tests/batch/__init__.py +0 -0
- tests/batch/test_batch.py +98 -0
- tests/core/__init__.py +0 -0
- tests/core/test_decorator.py +345 -0
- tests/database/__init__.py +0 -0
- tests/database/test_db.py +468 -0
- tests/database/test_db_search.py +163 -0
- tests/exception/__init__.py +0 -0
- tests/models/__init__.py +0 -0
- tests/models/test_db_config.py +152 -0
- tests/monitoring/__init__.py +0 -0
- tests/monitoring/test_tracer.py +202 -0
- tests/prediction/__init__.py +0 -0
- tests/vectorizer/__init__.py +0 -0
- vectorwave/__init__.py +13 -0
- vectorwave/batch/__init__.py +0 -0
- vectorwave/batch/batch.py +68 -0
- vectorwave/core/__init__.py +0 -0
- vectorwave/core/core.py +0 -0
- vectorwave/core/decorator.py +131 -0
- vectorwave/database/__init__.py +0 -0
- vectorwave/database/db.py +328 -0
- vectorwave/database/db_search.py +122 -0
- vectorwave/exception/__init__.py +0 -0
- vectorwave/exception/exceptions.py +22 -0
- vectorwave/models/__init__.py +0 -0
- vectorwave/models/db_config.py +92 -0
- vectorwave/monitoring/__init__.py +0 -0
- vectorwave/monitoring/monitoring.py +0 -0
- vectorwave/monitoring/tracer.py +131 -0
- vectorwave/prediction/__init__.py +0 -0
- vectorwave/prediction/predictor.py +0 -0
- vectorwave/vectorizer/__init__.py +0 -0
- vectorwave/vectorizer/base.py +12 -0
- vectorwave/vectorizer/factory.py +49 -0
- vectorwave/vectorizer/huggingface_vectorizer.py +33 -0
- vectorwave/vectorizer/openai_vectorizer.py +35 -0
- vectorwave-0.1.3.dist-info/METADATA +352 -0
- vectorwave-0.1.3.dist-info/RECORD +44 -0
- vectorwave-0.1.3.dist-info/WHEEL +5 -0
- vectorwave-0.1.3.dist-info/licenses/LICENSE +21 -0
- vectorwave-0.1.3.dist-info/licenses/NOTICE +31 -0
- vectorwave-0.1.3.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import json
|
|
3
|
+
from unittest.mock import patch, mock_open
|
|
4
|
+
from json import JSONDecodeError
|
|
5
|
+
|
|
6
|
+
# Function to test
|
|
7
|
+
from vectorwave.models.db_config import get_weaviate_settings
|
|
8
|
+
|
|
9
|
+
# --- Mock Data ---
|
|
10
|
+
|
|
11
|
+
# Mock content for a successfully loaded .weaviate_properties file
|
|
12
|
+
MOCK_JSON_DATA = """
|
|
13
|
+
{
|
|
14
|
+
"run_id": {
|
|
15
|
+
"data_type": "TEXT",
|
|
16
|
+
"description": "Test run ID"
|
|
17
|
+
},
|
|
18
|
+
"experiment_id": {
|
|
19
|
+
"data_type": "INT",
|
|
20
|
+
"description": "Identifier for the experiment"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# Mock content for a malformed .weaviate_properties file (invalid JSON)
|
|
26
|
+
MOCK_INVALID_JSON = """
|
|
27
|
+
{
|
|
28
|
+
"run_id": {
|
|
29
|
+
"data_type": "TEXT"
|
|
30
|
+
}
|
|
31
|
+
""" # Missing closing '}'
|
|
32
|
+
|
|
33
|
+
# --- Test Cases ---
|
|
34
|
+
|
|
35
|
+
@patch('os.path.exists', return_value=True)
|
|
36
|
+
@patch('builtins.open', new_callable=mock_open, read_data=MOCK_JSON_DATA)
|
|
37
|
+
def test_get_settings_loads_custom_props_success(mock_open_file, mock_exists):
|
|
38
|
+
"""
|
|
39
|
+
Case 1: .weaviate_properties file exists and JSON is valid
|
|
40
|
+
- settings.custom_properties should be loaded correctly as a dictionary
|
|
41
|
+
"""
|
|
42
|
+
# Arrange
|
|
43
|
+
# Clear the @lru_cache to bypass caching for this test
|
|
44
|
+
get_weaviate_settings.cache_clear()
|
|
45
|
+
|
|
46
|
+
# Act
|
|
47
|
+
settings = get_weaviate_settings()
|
|
48
|
+
|
|
49
|
+
# Assert
|
|
50
|
+
# Verify that the default path (.weaviate_properties) was checked
|
|
51
|
+
mock_exists.assert_called_with(".weaviate_properties")
|
|
52
|
+
# Verify the file was opened in 'r' mode
|
|
53
|
+
mock_open_file.assert_called_with(".weaviate_properties", 'r', encoding='utf-8')
|
|
54
|
+
|
|
55
|
+
assert settings.custom_properties is not None
|
|
56
|
+
assert "run_id" in settings.custom_properties
|
|
57
|
+
assert settings.custom_properties["run_id"]["data_type"] == "TEXT"
|
|
58
|
+
assert settings.custom_properties["run_id"]["description"] == "Test run ID"
|
|
59
|
+
assert "experiment_id" in settings.custom_properties
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@patch('os.path.exists', return_value=False)
|
|
63
|
+
def test_get_settings_file_not_found(mock_exists, caplog):
|
|
64
|
+
"""
|
|
65
|
+
Case 2: .weaviate_properties file does not exist
|
|
66
|
+
- settings.custom_properties should be None
|
|
67
|
+
- A 'file not found' message should be logged at DEBUG level
|
|
68
|
+
"""
|
|
69
|
+
import logging
|
|
70
|
+
|
|
71
|
+
# Arrange
|
|
72
|
+
caplog.set_level(logging.DEBUG) # DEBUG 레벨로 설정 (중요!)
|
|
73
|
+
get_weaviate_settings.cache_clear()
|
|
74
|
+
|
|
75
|
+
# Act
|
|
76
|
+
settings = get_weaviate_settings()
|
|
77
|
+
|
|
78
|
+
# Assert
|
|
79
|
+
mock_exists.assert_called_with(".weaviate_properties")
|
|
80
|
+
assert settings.custom_properties is None
|
|
81
|
+
|
|
82
|
+
# Check if 'file not found' message was logged
|
|
83
|
+
assert "file not found" in caplog.text.lower() or "not found" in caplog.text
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@patch('os.path.exists', return_value=True)
|
|
87
|
+
@patch('builtins.open', new_callable=mock_open, read_data=MOCK_INVALID_JSON)
|
|
88
|
+
@patch('json.load', side_effect=JSONDecodeError("Mock JSON Decode Error", "", 0))
|
|
89
|
+
def test_get_settings_invalid_json(mock_json_load, mock_open_file, mock_exists, caplog):
|
|
90
|
+
"""
|
|
91
|
+
Case 3: File exists but JSON format is invalid
|
|
92
|
+
- settings.custom_properties should be None
|
|
93
|
+
- A 'Could not parse JSON' warning should be logged
|
|
94
|
+
"""
|
|
95
|
+
import logging
|
|
96
|
+
|
|
97
|
+
# Arrange
|
|
98
|
+
caplog.set_level(logging.WARNING)
|
|
99
|
+
get_weaviate_settings.cache_clear()
|
|
100
|
+
|
|
101
|
+
# Act
|
|
102
|
+
settings = get_weaviate_settings()
|
|
103
|
+
|
|
104
|
+
# Assert
|
|
105
|
+
mock_exists.assert_called_once()
|
|
106
|
+
mock_open_file.assert_called_once()
|
|
107
|
+
mock_json_load.assert_called_once() # json.load was called but failed (due to side_effect)
|
|
108
|
+
assert settings.custom_properties is None # Should be None due to parsing failure
|
|
109
|
+
|
|
110
|
+
# Check if 'Could not parse JSON' warning was logged
|
|
111
|
+
assert "Could not parse JSON" in caplog.text
|
|
112
|
+
|
|
113
|
+
# Also check the log level
|
|
114
|
+
warning_logs = [r for r in caplog.records if "parse JSON" in r.message]
|
|
115
|
+
assert len(warning_logs) > 0
|
|
116
|
+
assert warning_logs[0].levelname == "WARNING"
|
|
117
|
+
|
|
118
|
+
@patch('os.path.exists', return_value=True)
|
|
119
|
+
@patch('builtins.open', new_callable=mock_open, read_data=MOCK_JSON_DATA)
|
|
120
|
+
@patch('os.environ.get') # os.environ.get을 모킹합니다
|
|
121
|
+
def test_get_settings_loads_global_custom_values(mock_env_get, mock_open_file, mock_exists):
|
|
122
|
+
"""
|
|
123
|
+
Case 4: Test if the value of the "RUN_ID" environment variable is loaded
|
|
124
|
+
into global_custom_values for "run_id" defined in .weaviate_properties
|
|
125
|
+
"""
|
|
126
|
+
# 1. Arrange
|
|
127
|
+
# MOCK_JSON_DATA defines "run_id" and "experiment_id".
|
|
128
|
+
# Set os.environ.get("RUN_ID") to return "test-run-123".
|
|
129
|
+
# Set os.environ.get("EXPERIMENT_ID") to return None.
|
|
130
|
+
def mock_env_side_effect(key):
|
|
131
|
+
if key == "RUN_ID":
|
|
132
|
+
return "test-run-123"
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
mock_env_get.side_effect = mock_env_side_effect
|
|
136
|
+
get_weaviate_settings.cache_clear()
|
|
137
|
+
|
|
138
|
+
# 2. Act
|
|
139
|
+
settings = get_weaviate_settings()
|
|
140
|
+
|
|
141
|
+
# 3. Assert
|
|
142
|
+
# .weaviate_properties should be loaded correctly.
|
|
143
|
+
assert settings.custom_properties is not None
|
|
144
|
+
assert "run_id" in settings.custom_properties
|
|
145
|
+
|
|
146
|
+
# Check if global_custom_values was loaded correctly.
|
|
147
|
+
assert settings.global_custom_values is not None
|
|
148
|
+
assert "run_id" in settings.global_custom_values
|
|
149
|
+
assert settings.global_custom_values["run_id"] == "test-run-123"
|
|
150
|
+
|
|
151
|
+
# "EXPERIMENT_ID" should not be included as os.environ.get returned None.
|
|
152
|
+
assert "experiment_id" not in settings.global_custom_values
|
|
File without changes
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import MagicMock
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from vectorwave.monitoring.tracer import trace_root, trace_span
|
|
6
|
+
from vectorwave.models.db_config import WeaviateSettings
|
|
7
|
+
|
|
8
|
+
# --- Import real functions for cache clearing ---
|
|
9
|
+
from vectorwave.batch.batch import get_batch_manager as real_get_batch_manager
|
|
10
|
+
from vectorwave.database.db import get_cached_client as real_get_cached_client
|
|
11
|
+
from vectorwave.models.db_config import get_weaviate_settings as real_get_settings
|
|
12
|
+
|
|
13
|
+
# Module paths to mock (adjust to your project structure if needed)
|
|
14
|
+
TRACER_MODULE_PATH = "vectorwave.monitoring.tracer"
|
|
15
|
+
BATCH_MODULE_PATH = "vectorwave.batch.batch"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def mock_tracer_deps(monkeypatch):
|
|
20
|
+
"""
|
|
21
|
+
Mocks dependencies for tracer.py (batch, settings).
|
|
22
|
+
"""
|
|
23
|
+
# 1. Mock BatchManager
|
|
24
|
+
mock_batch_instance = MagicMock()
|
|
25
|
+
mock_batch_instance.add_object = MagicMock()
|
|
26
|
+
mock_get_batch_manager = MagicMock(return_value=mock_batch_instance)
|
|
27
|
+
|
|
28
|
+
# 2. Mock Settings (including global tags)
|
|
29
|
+
mock_settings = WeaviateSettings(
|
|
30
|
+
COLLECTION_NAME="TestFunctions",
|
|
31
|
+
EXECUTION_COLLECTION_NAME="TestExecutions",
|
|
32
|
+
custom_properties=None, # Not important for this test
|
|
33
|
+
global_custom_values={"run_id": "global-run-abc", "env": "test"}
|
|
34
|
+
)
|
|
35
|
+
mock_get_settings = MagicMock(return_value=mock_settings)
|
|
36
|
+
|
|
37
|
+
mock_client = MagicMock()
|
|
38
|
+
mock_get_client = MagicMock(return_value=mock_client)
|
|
39
|
+
|
|
40
|
+
# --- Patch dependencies for tracer.py ---
|
|
41
|
+
monkeypatch.setattr(f"{TRACER_MODULE_PATH}.get_batch_manager", mock_get_batch_manager)
|
|
42
|
+
monkeypatch.setattr(f"{TRACER_MODULE_PATH}.get_weaviate_settings", mock_get_settings)
|
|
43
|
+
|
|
44
|
+
# Patch dependencies inside batch.py to prevent BatchManager init failure
|
|
45
|
+
monkeypatch.setattr(f"{BATCH_MODULE_PATH}.get_weaviate_client", mock_get_client)
|
|
46
|
+
monkeypatch.setattr(f"{BATCH_MODULE_PATH}.get_weaviate_settings", mock_get_settings)
|
|
47
|
+
|
|
48
|
+
# 5. Clear caches
|
|
49
|
+
real_get_batch_manager.cache_clear()
|
|
50
|
+
real_get_cached_client.cache_clear()
|
|
51
|
+
real_get_settings.cache_clear()
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"batch": mock_batch_instance,
|
|
55
|
+
"settings": mock_settings
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_trace_root_and_span_success(mock_tracer_deps):
|
|
60
|
+
"""
|
|
61
|
+
Case 1: Success (Root + Span) - The span should be recorded successfully.
|
|
62
|
+
"""
|
|
63
|
+
mock_batch = mock_tracer_deps["batch"]
|
|
64
|
+
|
|
65
|
+
@trace_span
|
|
66
|
+
def my_inner_span(x):
|
|
67
|
+
return f"result: {x}"
|
|
68
|
+
|
|
69
|
+
@trace_root()
|
|
70
|
+
def my_workflow_root():
|
|
71
|
+
return my_inner_span(x=10)
|
|
72
|
+
|
|
73
|
+
# --- Act ---
|
|
74
|
+
result = my_workflow_root()
|
|
75
|
+
|
|
76
|
+
# --- Assert ---
|
|
77
|
+
assert result == "result: 10"
|
|
78
|
+
mock_batch.add_object.assert_called_once()
|
|
79
|
+
|
|
80
|
+
args, kwargs = mock_batch.add_object.call_args
|
|
81
|
+
props = kwargs["properties"]
|
|
82
|
+
|
|
83
|
+
assert kwargs["collection"] == "TestExecutions"
|
|
84
|
+
assert props["status"] == "SUCCESS"
|
|
85
|
+
assert props["function_name"] == "my_inner_span"
|
|
86
|
+
assert props["error_message"] is None
|
|
87
|
+
assert "trace_id" in props
|
|
88
|
+
assert props["run_id"] == "global-run-abc"
|
|
89
|
+
assert props["env"] == "test"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_trace_span_failure(mock_tracer_deps):
|
|
93
|
+
"""
|
|
94
|
+
Case 2: Failure (Root + Failing Span) - The span should be recorded with an ERROR status.
|
|
95
|
+
"""
|
|
96
|
+
mock_batch = mock_tracer_deps["batch"]
|
|
97
|
+
|
|
98
|
+
@trace_span
|
|
99
|
+
def my_failing_span():
|
|
100
|
+
raise ValueError("This is a test error")
|
|
101
|
+
|
|
102
|
+
@trace_root()
|
|
103
|
+
def my_workflow_root_fail():
|
|
104
|
+
my_failing_span()
|
|
105
|
+
|
|
106
|
+
# --- Act & Assert (Exception) ---
|
|
107
|
+
with pytest.raises(ValueError, match="This is a test error"):
|
|
108
|
+
my_workflow_root_fail()
|
|
109
|
+
|
|
110
|
+
# --- Assert (Log) ---
|
|
111
|
+
mock_batch.add_object.assert_called_once()
|
|
112
|
+
|
|
113
|
+
args, kwargs = mock_batch.add_object.call_args
|
|
114
|
+
props = kwargs["properties"]
|
|
115
|
+
|
|
116
|
+
assert props["status"] == "ERROR"
|
|
117
|
+
assert "ValueError: This is a test error" in props["error_message"]
|
|
118
|
+
assert props["function_name"] == "my_failing_span"
|
|
119
|
+
assert props["run_id"] == "global-run-abc"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_span_without_root_does_nothing(mock_tracer_deps):
|
|
123
|
+
"""
|
|
124
|
+
Case 3: Tracing disabled (Span only) - If there's no Root, nothing should be recorded.
|
|
125
|
+
"""
|
|
126
|
+
mock_batch = mock_tracer_deps["batch"]
|
|
127
|
+
|
|
128
|
+
@trace_span
|
|
129
|
+
def my_lonely_span():
|
|
130
|
+
return "lonely_result"
|
|
131
|
+
|
|
132
|
+
# --- Act ---
|
|
133
|
+
result = my_lonely_span()
|
|
134
|
+
|
|
135
|
+
# --- Assert ---
|
|
136
|
+
assert result == "lonely_result"
|
|
137
|
+
mock_batch.add_object.assert_not_called()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_span_captures_attributes_and_overrides_globals(mock_tracer_deps):
|
|
141
|
+
"""
|
|
142
|
+
Case 4/5: Attribute Capturing and Overriding
|
|
143
|
+
"""
|
|
144
|
+
mock_batch = mock_tracer_deps["batch"]
|
|
145
|
+
|
|
146
|
+
class MyObject:
|
|
147
|
+
def __str__(self): return "MyObjectInstance"
|
|
148
|
+
|
|
149
|
+
@trace_span(attributes_to_capture=["team", "priority", "run_id", "user_obj"])
|
|
150
|
+
def my_span_with_attrs(team, priority, run_id, user_obj, other_arg="default"):
|
|
151
|
+
return "captured"
|
|
152
|
+
|
|
153
|
+
@trace_root()
|
|
154
|
+
def my_workflow_root_attrs():
|
|
155
|
+
return my_span_with_attrs(
|
|
156
|
+
team="backend",
|
|
157
|
+
priority=1,
|
|
158
|
+
run_id="override-run-xyz", # <-- This should override "global-run-abc"
|
|
159
|
+
user_obj=MyObject(),
|
|
160
|
+
other_arg="should-be-ignored"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# --- Act ---
|
|
164
|
+
my_workflow_root_attrs()
|
|
165
|
+
|
|
166
|
+
# --- Assert ---
|
|
167
|
+
mock_batch.add_object.assert_called_once()
|
|
168
|
+
props = mock_batch.add_object.call_args.kwargs["properties"]
|
|
169
|
+
|
|
170
|
+
assert props["team"] == "backend"
|
|
171
|
+
assert props["priority"] == 1
|
|
172
|
+
assert props["user_obj"] == "MyObjectInstance"
|
|
173
|
+
assert props["run_id"] == "override-run-xyz" # Overridden
|
|
174
|
+
assert props["env"] == "test" # Non-overridden global remains
|
|
175
|
+
assert "other_arg" not in props
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_root_accepts_custom_trace_id(mock_tracer_deps):
|
|
179
|
+
"""
|
|
180
|
+
Bonus: Test case for manually providing a 'trace_id'.
|
|
181
|
+
(This is the test that was fixed)
|
|
182
|
+
"""
|
|
183
|
+
mock_batch = mock_tracer_deps["batch"]
|
|
184
|
+
|
|
185
|
+
@trace_span
|
|
186
|
+
def my_inner_span():
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
@trace_root()
|
|
190
|
+
def my_workflow_root_custom_id(): # <-- ✅ FIXED: Removed 'trace_id' arg
|
|
191
|
+
my_inner_span()
|
|
192
|
+
|
|
193
|
+
# --- Act ---
|
|
194
|
+
# The decorator wrapper still receives 'trace_id' from this call
|
|
195
|
+
my_workflow_root_custom_id(trace_id="my-custom-trace-id-123")
|
|
196
|
+
|
|
197
|
+
# --- Assert ---
|
|
198
|
+
mock_batch.add_object.assert_called_once()
|
|
199
|
+
props = mock_batch.add_object.call_args.kwargs["properties"]
|
|
200
|
+
|
|
201
|
+
# Check if the trace_id was popped and injected correctly
|
|
202
|
+
assert props["trace_id"] == "my-custom-trace-id-123"
|
|
File without changes
|
|
File without changes
|
vectorwave/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .core.decorator import vectorize
|
|
2
|
+
|
|
3
|
+
from .database.db import initialize_database
|
|
4
|
+
from .database.db_search import search_functions, search_executions
|
|
5
|
+
from .monitoring.tracer import trace_span
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'vectorize',
|
|
9
|
+
'initialize_database',
|
|
10
|
+
'search_functions',
|
|
11
|
+
'search_executions',
|
|
12
|
+
'trace_span'
|
|
13
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import weaviate
|
|
2
|
+
import atexit
|
|
3
|
+
import logging
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from typing import Optional, List
|
|
6
|
+
from ..models.db_config import get_weaviate_settings, WeaviateSettings
|
|
7
|
+
from ..database.db import get_weaviate_client
|
|
8
|
+
from ..exception.exceptions import WeaviateConnectionError
|
|
9
|
+
|
|
10
|
+
# Create module-level logger
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class WeaviateBatchManager:
|
|
14
|
+
"""
|
|
15
|
+
A singleton class that manages Weaviate batch imports.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._initialized = False
|
|
20
|
+
logger.debug("Initializing WeaviateBatchManager")
|
|
21
|
+
self.client: weaviate.WeaviateClient = None
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
# (get_weaviate_settings is reused as it is handled by lru_cache)
|
|
25
|
+
self.settings: WeaviateSettings = get_weaviate_settings()
|
|
26
|
+
self.client: weaviate.WeaviateClient = get_weaviate_client(self.settings)
|
|
27
|
+
|
|
28
|
+
if not self.client:
|
|
29
|
+
raise WeaviateConnectionError("Client is None, cannot configure batch.")
|
|
30
|
+
|
|
31
|
+
# self.client.batch.configure(
|
|
32
|
+
# batch_size=20,
|
|
33
|
+
# dynamic=True,
|
|
34
|
+
# timeout_retries=3,
|
|
35
|
+
# )
|
|
36
|
+
|
|
37
|
+
# Register atexit: Automatically calls self.flush() on script exit
|
|
38
|
+
# atexit.register(self.flush)
|
|
39
|
+
self._initialized = True
|
|
40
|
+
logger.info("WeaviateBatchManager initialized successfully")
|
|
41
|
+
|
|
42
|
+
except Exception as e:
|
|
43
|
+
# Prevents VectorWave from stopping the main app upon DB connection failure
|
|
44
|
+
logger.error("Failed to initialize WeaviateBatchManager: %s", e)
|
|
45
|
+
|
|
46
|
+
def add_object(self, collection: str, properties: dict, uuid: str = None, vector: Optional[List[float]] = None):
|
|
47
|
+
"""
|
|
48
|
+
Adds an object to the Weaviate batch queue.
|
|
49
|
+
"""
|
|
50
|
+
if not self._initialized or not self.client:
|
|
51
|
+
logger.warning("Batch manager not initialized, skipping add_object")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
self.client.collections.get(collection).data.insert(
|
|
56
|
+
properties=properties,
|
|
57
|
+
uuid=uuid,
|
|
58
|
+
vector=vector
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.error("Failed to add object to batch (collection '%s'): %s", collection, e)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@lru_cache(None)
|
|
67
|
+
def get_batch_manager() -> WeaviateBatchManager:
|
|
68
|
+
return WeaviateBatchManager()
|
|
File without changes
|
vectorwave/core/core.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# vtm/src/vectorwave/core/decorator.py
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import inspect
|
|
5
|
+
from functools import wraps
|
|
6
|
+
|
|
7
|
+
from weaviate.util import generate_uuid5
|
|
8
|
+
|
|
9
|
+
from ..batch.batch import get_batch_manager
|
|
10
|
+
from ..models.db_config import get_weaviate_settings
|
|
11
|
+
from ..monitoring.tracer import trace_root, trace_span
|
|
12
|
+
from ..vectorizer.factory import get_vectorizer
|
|
13
|
+
|
|
14
|
+
# Create module-level logger
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
def vectorize(search_description: str,
|
|
18
|
+
sequence_narrative: str,
|
|
19
|
+
**execution_tags):
|
|
20
|
+
"""
|
|
21
|
+
VectorWave Decorator
|
|
22
|
+
|
|
23
|
+
(1) Collects function definitions (static data) once on script load.
|
|
24
|
+
(2) Records function execution (dynamic data) every time the function is called.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def decorator(func):
|
|
28
|
+
|
|
29
|
+
func_uuid = None
|
|
30
|
+
valid_execution_tags = {}
|
|
31
|
+
try:
|
|
32
|
+
module_name = func.__module__
|
|
33
|
+
function_name = func.__name__
|
|
34
|
+
|
|
35
|
+
func_identifier = f"{module_name}.{function_name}"
|
|
36
|
+
func_uuid = generate_uuid5(func_identifier)
|
|
37
|
+
|
|
38
|
+
static_properties = {
|
|
39
|
+
"function_name": function_name,
|
|
40
|
+
"module_name": module_name,
|
|
41
|
+
"docstring": inspect.getdoc(func) or "",
|
|
42
|
+
"source_code": inspect.getsource(func),
|
|
43
|
+
"search_description": search_description,
|
|
44
|
+
"sequence_narrative": sequence_narrative
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
batch = get_batch_manager()
|
|
48
|
+
settings = get_weaviate_settings()
|
|
49
|
+
|
|
50
|
+
vectorizer = get_vectorizer()
|
|
51
|
+
vector_to_add = None
|
|
52
|
+
|
|
53
|
+
if vectorizer:
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
print(f"[VectorWave] Vectorizing '{function_name}' using Python vectorizer...")
|
|
57
|
+
vector_to_add = vectorizer.embed(search_description)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"Warning: Failed to vectorize '{function_name}' with Python client: {e}")
|
|
60
|
+
|
|
61
|
+
if execution_tags:
|
|
62
|
+
if not settings.custom_properties:
|
|
63
|
+
logger.warning(
|
|
64
|
+
f"Function '{function_name}' provided execution_tags {list(execution_tags.keys())} "
|
|
65
|
+
f"but no .weaviate_properties file was loaded. These tags will be IGNORED."
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
allowed_keys = set(settings.custom_properties.keys())
|
|
69
|
+
for key, value in execution_tags.items():
|
|
70
|
+
if key in allowed_keys:
|
|
71
|
+
valid_execution_tags[key] = value
|
|
72
|
+
else:
|
|
73
|
+
logger.warning(
|
|
74
|
+
"Function '%s' has undefined execution_tag: '%s'. "
|
|
75
|
+
"This tag will be IGNORED. Please add it to your .weaviate_properties file.",
|
|
76
|
+
function_name,
|
|
77
|
+
key
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
batch.add_object(
|
|
81
|
+
collection=settings.COLLECTION_NAME,
|
|
82
|
+
properties=static_properties,
|
|
83
|
+
uuid=func_uuid,
|
|
84
|
+
vector=vector_to_add
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
except Exception as e:
|
|
88
|
+
|
|
89
|
+
logger.error("Error in @vectorize setup for '%s': %s", func.__name__, e)
|
|
90
|
+
@wraps(func)
|
|
91
|
+
def original_func_wrapper(*args, **kwargs):
|
|
92
|
+
return func(*args, **kwargs)
|
|
93
|
+
return original_func_wrapper
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# 2a. The *inner* wrapper to be wrapped by @trace_span
|
|
97
|
+
# This function receives all tags including full_kwargs from @trace_span.
|
|
98
|
+
@trace_root()
|
|
99
|
+
@trace_span(attributes_to_capture=['function_uuid', 'team', 'priority', 'run_id'])
|
|
100
|
+
@wraps(func)
|
|
101
|
+
def inner_wrapper(*args, **kwargs):
|
|
102
|
+
|
|
103
|
+
original_kwargs = kwargs.copy()
|
|
104
|
+
|
|
105
|
+
keys_to_remove = list(valid_execution_tags.keys())
|
|
106
|
+
keys_to_remove.append('function_uuid')
|
|
107
|
+
|
|
108
|
+
for key in execution_tags.keys():
|
|
109
|
+
if key not in keys_to_remove:
|
|
110
|
+
keys_to_remove.append(key)
|
|
111
|
+
|
|
112
|
+
for key in keys_to_remove:
|
|
113
|
+
original_kwargs.pop(key, None)
|
|
114
|
+
|
|
115
|
+
return func(*args, **original_kwargs)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@wraps(func)
|
|
119
|
+
def outer_wrapper(*args, **kwargs):
|
|
120
|
+
|
|
121
|
+
full_kwargs = kwargs.copy()
|
|
122
|
+
full_kwargs.update(valid_execution_tags)
|
|
123
|
+
full_kwargs['function_uuid'] = func_uuid
|
|
124
|
+
|
|
125
|
+
# 2. Call the *inner* wrapper with the full_kwargs
|
|
126
|
+
# This call passes through the @trace_root -> @trace_span decorators.
|
|
127
|
+
return inner_wrapper(*args, **full_kwargs)
|
|
128
|
+
|
|
129
|
+
return outer_wrapper
|
|
130
|
+
|
|
131
|
+
return decorator
|
|
File without changes
|