vectorwave 0.1.0__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 +97 -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 +464 -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 +143 -0
- tests/monitoring/__init__.py +0 -0
- tests/prediction/__init__.py +0 -0
- vectorwave/__init__.py +13 -0
- vectorwave/batch/__init__.py +0 -0
- vectorwave/batch/batch.py +65 -0
- vectorwave/core/__init__.py +0 -0
- vectorwave/core/core.py +0 -0
- vectorwave/core/decorator.py +108 -0
- vectorwave/database/__init__.py +0 -0
- vectorwave/database/db.py +302 -0
- vectorwave/database/db_search.py +100 -0
- vectorwave/exception/__init__.py +0 -0
- vectorwave/exception/exceptions.py +22 -0
- vectorwave/models/__init__.py +0 -0
- vectorwave/models/db_config.py +82 -0
- vectorwave/monitoring/__init__.py +0 -0
- vectorwave/monitoring/monitoring.py +0 -0
- vectorwave/monitoring/tracer.py +128 -0
- vectorwave/prediction/__init__.py +0 -0
- vectorwave/prediction/predictor.py +0 -0
- vectorwave-0.1.0.dist-info/METADATA +280 -0
- vectorwave-0.1.0.dist-info/RECORD +35 -0
- vectorwave-0.1.0.dist-info/WHEEL +5 -0
- vectorwave-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import weaviate
|
|
2
|
+
import weaviate.classes as wvc
|
|
3
|
+
from typing import Dict, Any, Optional, List
|
|
4
|
+
|
|
5
|
+
from weaviate.collections.classes.filters import _Filters
|
|
6
|
+
|
|
7
|
+
from ..models.db_config import get_weaviate_settings, WeaviateSettings
|
|
8
|
+
from .db import get_cached_client
|
|
9
|
+
from ..exception.exceptions import WeaviateConnectionError
|
|
10
|
+
|
|
11
|
+
import uuid
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _build_weaviate_filters(filters: Optional[Dict[str, Any]]) -> _Filters | None:
|
|
16
|
+
if not filters:
|
|
17
|
+
return None
|
|
18
|
+
filter_list = [
|
|
19
|
+
wvc.query.Filter.by_property(key).equal(value)
|
|
20
|
+
for key, value in filters.items()
|
|
21
|
+
]
|
|
22
|
+
if not filter_list:
|
|
23
|
+
return None
|
|
24
|
+
return wvc.query.Filter.all_of(filter_list)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def search_functions(query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
|
|
28
|
+
"""
|
|
29
|
+
Searches function definitions from the [VectorWaveFunctions] collection using natural language (nearText).
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
settings: WeaviateSettings = get_weaviate_settings()
|
|
33
|
+
client: weaviate.WeaviateClient = get_cached_client()
|
|
34
|
+
|
|
35
|
+
collection = client.collections.get(settings.COLLECTION_NAME)
|
|
36
|
+
weaviate_filter = _build_weaviate_filters(filters)
|
|
37
|
+
|
|
38
|
+
response = collection.query.near_text(
|
|
39
|
+
query=query,
|
|
40
|
+
limit=limit,
|
|
41
|
+
filters=weaviate_filter,
|
|
42
|
+
return_metadata=wvc.query.MetadataQuery(distance=True)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
#todo expand custom range needed
|
|
46
|
+
results = [
|
|
47
|
+
{
|
|
48
|
+
"properties": obj.properties,
|
|
49
|
+
"metadata": obj.metadata, # This contains the distance
|
|
50
|
+
"uuid": obj.uuid # Add uuid separately here
|
|
51
|
+
}
|
|
52
|
+
for obj in response.objects
|
|
53
|
+
]
|
|
54
|
+
return results
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"Error during Weaviate search: {e}")
|
|
58
|
+
raise WeaviateConnectionError(f"Failed to execute 'search_functions': {e}")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def search_executions(
|
|
62
|
+
limit: int = 10,
|
|
63
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
64
|
+
sort_by: Optional[str] = "timestamp_utc",
|
|
65
|
+
sort_ascending: bool = False
|
|
66
|
+
) -> List[Dict[str, Any]]:
|
|
67
|
+
"""
|
|
68
|
+
Searches execution logs from the [VectorWaveExecutions] collection using filtering and sorting.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
settings: WeaviateSettings = get_weaviate_settings()
|
|
72
|
+
client: weaviate.WeaviateClient = get_cached_client()
|
|
73
|
+
|
|
74
|
+
collection = client.collections.get(settings.EXECUTION_COLLECTION_NAME)
|
|
75
|
+
weaviate_filter = _build_weaviate_filters(filters)
|
|
76
|
+
weaviate_sort = None
|
|
77
|
+
|
|
78
|
+
if sort_by:
|
|
79
|
+
weaviate_sort = wvc.query.Sort.by_property(
|
|
80
|
+
name=sort_by,
|
|
81
|
+
ascending=sort_ascending
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
response = collection.query.fetch_objects(
|
|
85
|
+
limit=limit,
|
|
86
|
+
filters=weaviate_filter,
|
|
87
|
+
sort=weaviate_sort
|
|
88
|
+
)
|
|
89
|
+
results = []
|
|
90
|
+
for obj in response.objects:
|
|
91
|
+
props = obj.properties.copy()
|
|
92
|
+
for key, value in props.items():
|
|
93
|
+
if isinstance(value, uuid.UUID) or isinstance(value, datetime):
|
|
94
|
+
props[key] = str(value)
|
|
95
|
+
results.append(props)
|
|
96
|
+
|
|
97
|
+
return results
|
|
98
|
+
|
|
99
|
+
except Exception as e:
|
|
100
|
+
raise WeaviateConnectionError(f"Failed to execute 'search_executions': {e}")
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Defines custom exceptions for the VectorWave project.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
class VectorWaveError(Exception):
|
|
6
|
+
"""Base exception class for the VectorWave library."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WeaviateConnectionError(VectorWaveError):
|
|
11
|
+
"""Raised when an error occurs while attempting to connect to the Weaviate server."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WeaviateNotReadyError(VectorWaveError):
|
|
16
|
+
"""Raised when connected to Weaviate, but the server is not in a ready state."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SchemaCreationError(VectorWaveError):
|
|
21
|
+
"""Raised when an error occurs during Weaviate collection schema creation."""
|
|
22
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
from typing import Dict, Optional, Any
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WeaviateSettings(BaseSettings):
|
|
9
|
+
"""
|
|
10
|
+
Manages Weaviate database connection settings.
|
|
11
|
+
|
|
12
|
+
Reads values from environment variables or a .env file.
|
|
13
|
+
(e.g., WEAVIATE_HOST=10.0.0.1)
|
|
14
|
+
"""
|
|
15
|
+
# If environment variables are not set, these default values will be used.
|
|
16
|
+
WEAVIATE_HOST: str = "localhost"
|
|
17
|
+
WEAVIATE_PORT: int = 8080
|
|
18
|
+
WEAVIATE_GRPC_PORT: int = 50051
|
|
19
|
+
COLLECTION_NAME: str = "VectorWaveFunctions"
|
|
20
|
+
EXECUTION_COLLECTION_NAME: str = "VectorWaveExecutions"
|
|
21
|
+
IS_VECTORIZE_COLLECTION_NAME: bool = True
|
|
22
|
+
|
|
23
|
+
# Configure to read from a .env file (optional)
|
|
24
|
+
|
|
25
|
+
VECTORIZER_CONFIG: str = "text2vec-openai"
|
|
26
|
+
GENERATIVE_CONFIG: str = "generative-openai"
|
|
27
|
+
|
|
28
|
+
CUSTOM_PROPERTIES_FILE_PATH: str = ".weaviate_properties"
|
|
29
|
+
custom_properties: Optional[Dict[str, Dict[str, Any]]] = None
|
|
30
|
+
global_custom_values: Optional[Dict[str, Any]] = None
|
|
31
|
+
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8",extra='ignore')
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# @lru_cache ensures this function creates the Settings object only once (Singleton pattern)
|
|
35
|
+
# and reuses the cached object on subsequent calls.
|
|
36
|
+
@lru_cache()
|
|
37
|
+
def get_weaviate_settings() -> WeaviateSettings:
|
|
38
|
+
"""
|
|
39
|
+
Factory function that returns the settings object.
|
|
40
|
+
"""
|
|
41
|
+
settings = WeaviateSettings()
|
|
42
|
+
|
|
43
|
+
file_path = settings.CUSTOM_PROPERTIES_FILE_PATH
|
|
44
|
+
|
|
45
|
+
if file_path and os.path.exists(file_path):
|
|
46
|
+
print(f"Loading custom properties schema from '{file_path}'...")
|
|
47
|
+
try:
|
|
48
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
49
|
+
loaded_data = json.load(f)
|
|
50
|
+
|
|
51
|
+
if isinstance(loaded_data, dict):
|
|
52
|
+
settings.custom_properties = loaded_data
|
|
53
|
+
else:
|
|
54
|
+
print(
|
|
55
|
+
f"Warning: Content in '{file_path}' is not a valid dictionary (JSON root). "
|
|
56
|
+
f"Custom properties will not be loaded."
|
|
57
|
+
)
|
|
58
|
+
settings.custom_properties = None
|
|
59
|
+
|
|
60
|
+
except json.JSONDecodeError as e:
|
|
61
|
+
print(f"Warning: Could not parse JSON from '{file_path}'. File might be malformed. {e}")
|
|
62
|
+
settings.custom_properties = None
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(f"Warning: Could not read file '{file_path}': {e}")
|
|
65
|
+
settings.custom_properties = None
|
|
66
|
+
|
|
67
|
+
elif file_path:
|
|
68
|
+
print(f"Note: Custom properties file not found at '{file_path}'. Skipping schema.")
|
|
69
|
+
|
|
70
|
+
if settings.custom_properties:
|
|
71
|
+
settings.global_custom_values = {}
|
|
72
|
+
print(f"Loading global custom values from environment variables...")
|
|
73
|
+
|
|
74
|
+
for prop_name in settings.custom_properties.keys():
|
|
75
|
+
env_var_name = prop_name.upper()
|
|
76
|
+
value = os.environ.get(env_var_name)
|
|
77
|
+
|
|
78
|
+
if value:
|
|
79
|
+
settings.global_custom_values[prop_name] = value
|
|
80
|
+
print(f"Loaded global value for '{prop_name}' from env var '{env_var_name}'.")
|
|
81
|
+
|
|
82
|
+
return settings
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import time
|
|
3
|
+
import traceback
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from contextvars import ContextVar
|
|
6
|
+
from typing import Optional, List, Dict, Any, Callable
|
|
7
|
+
from uuid import uuid4
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
|
|
10
|
+
from ..batch.batch import get_batch_manager
|
|
11
|
+
from ..models.db_config import get_weaviate_settings, WeaviateSettings
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TraceCollector:
|
|
15
|
+
def __init__(self, trace_id: str):
|
|
16
|
+
self.trace_id = trace_id
|
|
17
|
+
self.settings: WeaviateSettings = get_weaviate_settings()
|
|
18
|
+
self.batch = get_batch_manager()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
current_tracer_var: ContextVar[Optional[TraceCollector]] = ContextVar('current_tracer', default=None)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def trace_root() -> Callable:
|
|
25
|
+
"""
|
|
26
|
+
Decorator factory for the workflow's entry point function.
|
|
27
|
+
Creates and sets the TraceCollector in ContextVar.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def decorator(func: Callable) -> Callable:
|
|
31
|
+
@wraps(func)
|
|
32
|
+
def wrapper(*args, **kwargs):
|
|
33
|
+
if current_tracer_var.get() is not None:
|
|
34
|
+
return func(*args, **kwargs)
|
|
35
|
+
|
|
36
|
+
trace_id = kwargs.pop('trace_id', str(uuid4()))
|
|
37
|
+
tracer = TraceCollector(trace_id=trace_id)
|
|
38
|
+
token = current_tracer_var.set(tracer)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
# ⭐️ Key: Here, func is the wrapper of @trace_span.
|
|
42
|
+
return func(*args, **kwargs)
|
|
43
|
+
finally:
|
|
44
|
+
current_tracer_var.reset(token)
|
|
45
|
+
|
|
46
|
+
return wrapper
|
|
47
|
+
|
|
48
|
+
return decorator
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def trace_span(
|
|
52
|
+
_func: Optional[Callable] = None,
|
|
53
|
+
*,
|
|
54
|
+
attributes_to_capture: Optional[List[str]] = None
|
|
55
|
+
) -> Callable:
|
|
56
|
+
"""
|
|
57
|
+
Decorator to capture function execution as a 'span'.
|
|
58
|
+
Can be used as @trace_span or @trace_span(attributes_to_capture=[...]).
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def decorator(func: Callable) -> Callable:
|
|
62
|
+
@wraps(func)
|
|
63
|
+
def wrapper(*args, **kwargs):
|
|
64
|
+
tracer = current_tracer_var.get()
|
|
65
|
+
if not tracer:
|
|
66
|
+
return func(*args, **kwargs)
|
|
67
|
+
|
|
68
|
+
start_time = time.perf_counter()
|
|
69
|
+
status = "SUCCESS"
|
|
70
|
+
error_msg = None
|
|
71
|
+
result = None
|
|
72
|
+
|
|
73
|
+
captured_attributes = {}
|
|
74
|
+
if attributes_to_capture:
|
|
75
|
+
try:
|
|
76
|
+
# Directly checks the kwargs dictionary.
|
|
77
|
+
for attr_name in attributes_to_capture:
|
|
78
|
+
if attr_name in kwargs:
|
|
79
|
+
value = kwargs[attr_name]
|
|
80
|
+
if not isinstance(value, (str, int, float, bool, list, dict, type(None))):
|
|
81
|
+
value = str(value)
|
|
82
|
+
captured_attributes[attr_name] = value
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"Warning: Failed to capture attributes for {func.__name__}: {e}")
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
result = func(*args, **kwargs)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
status = "ERROR"
|
|
90
|
+
error_msg = traceback.format_exc()
|
|
91
|
+
raise e
|
|
92
|
+
finally:
|
|
93
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
94
|
+
|
|
95
|
+
span_properties = {
|
|
96
|
+
"trace_id": tracer.trace_id,
|
|
97
|
+
"span_id": str(uuid4()),
|
|
98
|
+
"function_name": func.__name__,
|
|
99
|
+
"timestamp_utc": datetime.now(timezone.utc).isoformat(),
|
|
100
|
+
"duration_ms": duration_ms,
|
|
101
|
+
"status": status,
|
|
102
|
+
"error_message": error_msg,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# 1. Apply global tags first.
|
|
106
|
+
if tracer.settings.global_custom_values:
|
|
107
|
+
span_properties.update(tracer.settings.global_custom_values)
|
|
108
|
+
|
|
109
|
+
# 2. Apply captured attributes second (overriding global values if necessary).
|
|
110
|
+
# (If 'run_id' was captured, this value (e.g., override-run-xyz) overrides the global value.)
|
|
111
|
+
span_properties.update(captured_attributes)
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
tracer.batch.add_object(
|
|
115
|
+
collection=tracer.settings.EXECUTION_COLLECTION_NAME,
|
|
116
|
+
properties=span_properties
|
|
117
|
+
)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(f"Error: Failed to log span for {func.__name__} (trace_id: {tracer.trace_id}): {e}")
|
|
120
|
+
|
|
121
|
+
return result
|
|
122
|
+
|
|
123
|
+
return wrapper
|
|
124
|
+
|
|
125
|
+
if _func is None:
|
|
126
|
+
return decorator
|
|
127
|
+
else:
|
|
128
|
+
return decorator(_func)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vectorwave
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: VectorWave: Seamless Auto-Vectorization Framework
|
|
5
|
+
Author-email: junyeonggim <junyeonggim5@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: weaviate-client>=4.0.0
|
|
11
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# VectorWave: Seamless Auto-Vectorization Framework
|
|
15
|
+
|
|
16
|
+
[](https://www.google.com/search?q=LICENSE)
|
|
17
|
+
|
|
18
|
+
## 🌟 Overview
|
|
19
|
+
|
|
20
|
+
**VectorWave** is an innovative framework that uses a **decorator** to automatically save and manage the output of Python functions/methods in a **Vector Database (Vector DB)**. Developers can convert function outputs into intelligent vector data with a single line of code (`@vectorize`), without worrying about the complex processes of data collection, embedding generation, or storage in a Vector DB.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## ✨ Features
|
|
25
|
+
|
|
26
|
+
* **`@vectorize` Decorator:**
|
|
27
|
+
1. **Static Data Collection:** Saves the function's source code, docstring, and metadata to the `VectorWaveFunctions` collection once when the script is loaded.
|
|
28
|
+
2. **Dynamic Data Logging:** Records the execution time, success/failure status, error logs, and 'dynamic tags' to the `VectorWaveExecutions` collection every time the function is called.
|
|
29
|
+
* **Distributed Tracing:** By combining the `@vectorize` and `@trace_span` decorators, you can analyze the execution of complex multi-step workflows, grouped under a single **`trace_id`**.
|
|
30
|
+
* **Search Interface:** Provides `search_functions` (for vector search) and `search_executions` (for log filtering) to facilitate the construction of RAG and monitoring systems.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🚀 Usage
|
|
35
|
+
|
|
36
|
+
VectorWave consists of 'storing' via decorators and 'searching' via functions, and now includes **execution flow tracing**.
|
|
37
|
+
|
|
38
|
+
### 1. (Required) Initialize the Database and Configuration
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import time
|
|
42
|
+
from vectorwave import (
|
|
43
|
+
vectorize,
|
|
44
|
+
initialize_database,
|
|
45
|
+
search_functions,
|
|
46
|
+
search_executions
|
|
47
|
+
)
|
|
48
|
+
# [ADDITION] Import trace_span separately for distributed tracing.
|
|
49
|
+
from vectorwave.monitoring.tracer import trace_span
|
|
50
|
+
|
|
51
|
+
# This only needs to be called once when the script starts.
|
|
52
|
+
try:
|
|
53
|
+
client = initialize_database()
|
|
54
|
+
print("VectorWave DB initialized successfully.")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
print(f"DB initialization failed: {e}")
|
|
57
|
+
exit()
|
|
58
|
+
````
|
|
59
|
+
|
|
60
|
+
### 2\. [Store] Use `@vectorize` with Distributed Tracing
|
|
61
|
+
|
|
62
|
+
The `@vectorize` acts as the **Root** for tracing, and `@trace_span` is used on internal functions to group the execution flow under a single `trace_id`.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
# --- Child Span Function: Captures arguments ---
|
|
66
|
+
@trace_span(attributes_to_capture=['user_id', 'amount'])
|
|
67
|
+
def step_1_validate_payment(user_id: str, amount: int):
|
|
68
|
+
"""(Span) Payment validation. Records user_id and amount in the log."""
|
|
69
|
+
print(f" [SPAN 1] Validating payment for {user_id}...")
|
|
70
|
+
time.sleep(0.1)
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
@trace_span(attributes_to_capture=['user_id', 'receipt_id'])
|
|
74
|
+
def step_2_send_receipt(user_id: str, receipt_id: str):
|
|
75
|
+
"""(Span) Sends the receipt."""
|
|
76
|
+
print(f" [SPAN 2] Sending receipt {receipt_id}...")
|
|
77
|
+
time.sleep(0.2)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# --- Root Function (@trace_root role) ---
|
|
81
|
+
@vectorize(
|
|
82
|
+
search_description="Charges a user in the payment system.",
|
|
83
|
+
sequence_narrative="Returns a receipt ID upon successful payment.",
|
|
84
|
+
team="billing", # <-- Custom Tag (recorded in all execution logs)
|
|
85
|
+
priority=1 # <-- Custom Tag (execution priority)
|
|
86
|
+
)
|
|
87
|
+
def process_payment(user_id: str, amount: int):
|
|
88
|
+
"""(Root Span) Executes the user payment workflow."""
|
|
89
|
+
print(f" [ROOT EXEC] process_payment: Starting workflow for {user_id}...")
|
|
90
|
+
|
|
91
|
+
# When calling child functions, the same trace_id is automatically inherited via ContextVar.
|
|
92
|
+
step_1_validate_payment(user_id=user_id, amount=amount)
|
|
93
|
+
|
|
94
|
+
receipt_id = f"receipt_{user_id}_{amount}"
|
|
95
|
+
step_2_send_receipt(user_id=user_id, receipt_id=receipt_id)
|
|
96
|
+
|
|
97
|
+
print(f" [ROOT DONE] process_payment")
|
|
98
|
+
return {"status": "success", "receipt_id": receipt_id}
|
|
99
|
+
|
|
100
|
+
# --- Execute the Function ---
|
|
101
|
+
print("Now calling 'process_payment'...")
|
|
102
|
+
# This single call records 3 execution logs (spans) in the DB,
|
|
103
|
+
# all grouped under one 'trace_id'.
|
|
104
|
+
process_payment("user_789", 5000)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3\. [Search ①] Function Definition Search (for RAG)
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
# Search for functions related to 'payment' using natural language (vector search).
|
|
111
|
+
print("\n--- Searching for 'payment' functions ---")
|
|
112
|
+
payment_funcs = search_functions(
|
|
113
|
+
query="user payment processing",
|
|
114
|
+
limit=3
|
|
115
|
+
)
|
|
116
|
+
for func in payment_funcs:
|
|
117
|
+
print(f" - Function: {func['properties']['function_name']}")
|
|
118
|
+
print(f" - Description: {func['properties']['search_description']}")
|
|
119
|
+
print(f" - Similarity (Distance): {func['metadata'].distance:.4f}")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 4\. [Search ②] Execution Log Search (Monitoring and Tracing)
|
|
123
|
+
|
|
124
|
+
The `search_executions` function can now search for all related execution logs (spans) based on the `trace_id`.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# 1. Find the Trace ID of a specific workflow (process_payment).
|
|
128
|
+
latest_payment_span = search_executions(
|
|
129
|
+
limit=1,
|
|
130
|
+
filters={"function_name": "process_payment"},
|
|
131
|
+
sort_by="timestamp_utc",
|
|
132
|
+
sort_ascending=False
|
|
133
|
+
)
|
|
134
|
+
trace_id = latest_payment_span[0]["trace_id"]
|
|
135
|
+
|
|
136
|
+
# 2. Search all spans belonging to that Trace ID, sorted chronologically.
|
|
137
|
+
print(f"\n--- Full Trace for ID ({trace_id[:8]}...) ---")
|
|
138
|
+
trace_spans = search_executions(
|
|
139
|
+
limit=10,
|
|
140
|
+
filters={"trace_id": trace_id},
|
|
141
|
+
sort_by="timestamp_utc",
|
|
142
|
+
sort_ascending=True # Ascending sort for workflow flow analysis
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
for i, span in enumerate(trace_spans):
|
|
146
|
+
print(f" - [Span {i+1}] {span['function_name']} ({span['duration_ms']:.2f}ms)")
|
|
147
|
+
# Captured arguments (user_id, amount, etc.) are displayed for the child spans.
|
|
148
|
+
|
|
149
|
+
# Example Output:
|
|
150
|
+
# - [Span 1] step_1_validate_payment (100.81ms)
|
|
151
|
+
# - [Span 2] step_2_send_receipt (202.06ms)
|
|
152
|
+
# - [Span 3] process_payment (333.18ms)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
-----
|
|
156
|
+
|
|
157
|
+
## ⚙️ Configuration
|
|
158
|
+
|
|
159
|
+
VectorWave automatically reads Weaviate database connection information from **environment variables** or a `.env` file.
|
|
160
|
+
|
|
161
|
+
Create a `.env` file in the root directory of your project (e.g., where `main.py` is located) and set the required values.
|
|
162
|
+
|
|
163
|
+
### .env File Example
|
|
164
|
+
|
|
165
|
+
```ini
|
|
166
|
+
# .env
|
|
167
|
+
# --- Basic Weaviate Connection Settings ---
|
|
168
|
+
WEAVIATE_HOST=localhost
|
|
169
|
+
WEAVIATE_PORT=8080
|
|
170
|
+
WEAVIATE_GRPC_PORT=50051
|
|
171
|
+
|
|
172
|
+
# --- Vectorizer , Generative Module Config ---
|
|
173
|
+
# (default: text2vec-openai) Set to 'none' to disable vectorization.
|
|
174
|
+
VECTORIZER_CONFIG=text2vec-openai
|
|
175
|
+
# (default: generative-openai)
|
|
176
|
+
GENERATIVE_CONFIG=generative-openai
|
|
177
|
+
# An OpenAI API key is required if using modules like text2vec-openai.
|
|
178
|
+
# OPENAI_API_KEY=sk-your-key-here
|
|
179
|
+
|
|
180
|
+
# --- [Advanced] Custom Property Settings ---
|
|
181
|
+
# 1. The path to the JSON file defining custom properties to add to the schema.
|
|
182
|
+
CUSTOM_PROPERTIES_FILE_PATH=.weaviate_properties
|
|
183
|
+
|
|
184
|
+
# 2. Environment variables to be used for 'Global Dynamic Tagging'.
|
|
185
|
+
# ("run_id" must be defined in the .weaviate_properties file)
|
|
186
|
+
RUN_ID=test-run-001
|
|
187
|
+
EXPERIMENT_ID=exp-abc
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
-----
|
|
191
|
+
|
|
192
|
+
### Custom Properties and Dynamic Execution Tagging
|
|
193
|
+
|
|
194
|
+
VectorWave can store user-defined metadata in both static definitions (`VectorWaveFunctions`) and dynamic logs (`VectorWaveExecutions`). This works in two steps.
|
|
195
|
+
|
|
196
|
+
#### Step 1: Define Custom Schema (The "Allow-List")
|
|
197
|
+
|
|
198
|
+
Create a JSON file at the path specified by `CUSTOM_PROPERTIES_FILE_PATH` (default: `.weaviate_properties`).
|
|
199
|
+
|
|
200
|
+
This file instructs VectorWave to add **new properties (columns)** to the Weaviate collections. This file acts as an **"allow-list"** for all custom tags.
|
|
201
|
+
|
|
202
|
+
**`.weaviate_properties` Example:**
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"run_id": {
|
|
207
|
+
"data_type": "TEXT",
|
|
208
|
+
"description": "The ID of the specific test run"
|
|
209
|
+
},
|
|
210
|
+
"experiment_id": {
|
|
211
|
+
"data_type": "TEXT",
|
|
212
|
+
"description": "Identifier for the experiment"
|
|
213
|
+
},
|
|
214
|
+
"team": {
|
|
215
|
+
"data_type": "TEXT",
|
|
216
|
+
"description": "The team responsible for this function"
|
|
217
|
+
},
|
|
218
|
+
"priority": {
|
|
219
|
+
"data_type": "INT",
|
|
220
|
+
"description": "Execution priority level"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
* Defining these will add `run_id`, `experiment_id`, `team`, and `priority` properties to *both* collections.
|
|
226
|
+
|
|
227
|
+
#### Step 2: Dynamic Execution Tagging (Adding Values)
|
|
228
|
+
|
|
229
|
+
When a function executes, VectorWave adds tags to the `VectorWaveExecutions` log. It does this in two ways, which are then merged:
|
|
230
|
+
|
|
231
|
+
**1. Global Tags (from Environment Variables)**
|
|
232
|
+
VectorWave searches for environment variables whose names match the **uppercase** keys from Step 1 (e.g., `RUN_ID`, `EXPERIMENT_ID`) and uses these for run-wide metadata.
|
|
233
|
+
|
|
234
|
+
**2. Function-Specific Tags (from Decorator)**
|
|
235
|
+
You can pass tags directly to the `@vectorize` decorator as keyword arguments (`**execution_tags`). This is ideal for function-specific metadata.
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
# --- .env file ---
|
|
239
|
+
# RUN_ID=global-run-abc
|
|
240
|
+
# TEAM=default-team
|
|
241
|
+
|
|
242
|
+
@vectorize(
|
|
243
|
+
search_description="Process a payment",
|
|
244
|
+
sequence_narrative="...",
|
|
245
|
+
team="billing", # <-- Function-specific tag
|
|
246
|
+
priority=1 # <-- Function-specific tag
|
|
247
|
+
)
|
|
248
|
+
def process_payment():
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
@vectorize(
|
|
252
|
+
search_description="Another function",
|
|
253
|
+
sequence_narrative="...",
|
|
254
|
+
run_id="override-run-xyz" # <-- Overrides the global tag
|
|
255
|
+
)
|
|
256
|
+
def other_function():
|
|
257
|
+
pass
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Tag Merging and Validation Rules**
|
|
261
|
+
|
|
262
|
+
1. **Validation (Most Important):** A tag (either global or specific) will **only** be saved to Weaviate if its key (e.g., `run_id`, `team`, `priority`) was first defined in your `.weaviate_properties` file (Step 1). Tags not defined in the schema will be **ignored**, and a warning will be printed on startup.
|
|
263
|
+
|
|
264
|
+
2. **Priority (Override):** If a tag key is defined in both places (e.g., a global `RUN_ID` in `.env` and a specific `run_id="override-run-xyz"` on the decorator), the **function-specific tag from the decorator will always win**.
|
|
265
|
+
|
|
266
|
+
**Resulting Logs:**
|
|
267
|
+
|
|
268
|
+
* `process_payment()` log will have: `{"run_id": "global-run-abc", "team": "billing", "priority": 1}`
|
|
269
|
+
* `other_function()` log will have: `{"run_id": "override-run-xyz", "team": "default-team"}`
|
|
270
|
+
|
|
271
|
+
-----
|
|
272
|
+
|
|
273
|
+
## 🤝 Contributing
|
|
274
|
+
|
|
275
|
+
All forms of contribution are welcome, including bug reports, feature requests, and code contributions. For details, please refer to [CONTRIBUTING.md](https://www.google.com/search?q=httpsS://www.google.com/search%3Fq%3DCONTRIBUTING.md).
|
|
276
|
+
|
|
277
|
+
## 📜 License
|
|
278
|
+
|
|
279
|
+
This project is distributed under the MIT License. See the [LICENSE](https://www.google.com/search?q=httpsS://www.google.com/search%3Fq%3DLICENSE) file for details.
|
|
280
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
tests/batch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
tests/batch/test_batch.py,sha256=vDP-W4KTH6f6XfTof-EYgRtz5MlKSRC1kwGrQ5Z4v0k,3315
|
|
4
|
+
tests/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
tests/core/test_decorator.py,sha256=uLlqBoGJ83Emu5qCdBmhvR12jd5tfDhWUzfCchgL6l8,12852
|
|
6
|
+
tests/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
tests/database/test_db.py,sha256=m_xuEBt2EzCX-o3UuY79xQdVvpTA3k05RvVahu4bP1k,16615
|
|
8
|
+
tests/database/test_db_search.py,sha256=fM75zvHhJ06meMbRN_T9cMoXVdcFaC5dwzjHu0nc3-g,5456
|
|
9
|
+
tests/exception/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
tests/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
tests/models/test_db_config.py,sha256=wdWTF4ZL9zieSMnfwUhsBtAmsJsVDC2YQbuYW0Tw-28,4823
|
|
12
|
+
tests/monitoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
tests/prediction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
vectorwave/__init__.py,sha256=6EC-i4Ty_H_mZb4ZdsyryQpj_uobRf84mp8fPMNui3w,318
|
|
15
|
+
vectorwave/batch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
vectorwave/batch/batch.py,sha256=gSfRjxF0Z5pCIvJndAWleMas5Ms78FkURfGVYqvtD8E,2225
|
|
17
|
+
vectorwave/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
vectorwave/core/core.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
vectorwave/core/decorator.py,sha256=rX9GSpMN76aki_0y7bm-ZpQt3pJrUcqGMuDTWevp5AI,3796
|
|
20
|
+
vectorwave/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
vectorwave/database/db.py,sha256=OP8LxJSFGbeArSatLzKXBhFHTzVydBuBfVyRHO6rpd0,10963
|
|
22
|
+
vectorwave/database/db_search.py,sha256=iN7m8jYsIM-h-qTnPcp-1grDOQtnM3xt1Fq3TEhaz4g,3319
|
|
23
|
+
vectorwave/exception/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
vectorwave/exception/exceptions.py,sha256=A7NuY3NyctYlQRtDhU4uvDjdSWormddf-Pk-kR0vMsQ,592
|
|
25
|
+
vectorwave/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
vectorwave/models/db_config.py,sha256=fcijRdX6Lh_vRRWGGvxS2Zn-eQ7ukwq68OeuUeSp3DA,3106
|
|
27
|
+
vectorwave/monitoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
vectorwave/monitoring/monitoring.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
vectorwave/monitoring/tracer.py,sha256=SH54QfGPosYzHmnz8SSEeZ08LQ9XUYscCIZuSp5pU3I,4431
|
|
30
|
+
vectorwave/prediction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
vectorwave/prediction/predictor.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
+
vectorwave-0.1.0.dist-info/METADATA,sha256=dBb61UixAxREuoUHR9pS4uSo2SmPDnplIudYknshxJk,10518
|
|
33
|
+
vectorwave-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
34
|
+
vectorwave-0.1.0.dist-info/top_level.txt,sha256=Nce880E2pZRKbWF9XpB-FT5iZL9ETMDqSUVKF-_0m78,17
|
|
35
|
+
vectorwave-0.1.0.dist-info/RECORD,,
|