vectorwave 0.1.9__tar.gz → 0.2.0__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 (82) hide show
  1. {vectorwave-0.1.9/src/vectorwave.egg-info → vectorwave-0.2.0}/PKG-INFO +1 -1
  2. {vectorwave-0.1.9 → vectorwave-0.2.0}/pyproject.toml +1 -1
  3. vectorwave-0.2.0/src/tests/utils/test_return_caching.py +126 -0
  4. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/__init__.py +7 -1
  5. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/database/db_search.py +52 -0
  6. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/utils/replayer.py +38 -9
  7. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/utils/replayer_semantic.py +43 -35
  8. vectorwave-0.2.0/src/vectorwave/utils/return_caching_utils.py +134 -0
  9. {vectorwave-0.1.9 → vectorwave-0.2.0/src/vectorwave.egg-info}/PKG-INFO +1 -1
  10. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave.egg-info/SOURCES.txt +1 -0
  11. vectorwave-0.1.9/src/vectorwave/utils/return_caching_utils.py +0 -76
  12. {vectorwave-0.1.9 → vectorwave-0.2.0}/LICENSE +0 -0
  13. {vectorwave-0.1.9 → vectorwave-0.2.0}/MANIFEST.in +0 -0
  14. {vectorwave-0.1.9 → vectorwave-0.2.0}/NOTICE +0 -0
  15. {vectorwave-0.1.9 → vectorwave-0.2.0}/Readme.md +0 -0
  16. {vectorwave-0.1.9 → vectorwave-0.2.0}/setup.cfg +0 -0
  17. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/__init__.py +0 -0
  18. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/batch/__init__.py +0 -0
  19. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/batch/test_batch.py +0 -0
  20. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/conftest.py +0 -0
  21. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/core/__init__.py +0 -0
  22. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/core/test_decorator.py +0 -0
  23. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/core/test_semantic_caching.py +0 -0
  24. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/database/__init__.py +0 -0
  25. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/database/test_archiver.py +0 -0
  26. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/database/test_db.py +0 -0
  27. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/database/test_db_search.py +0 -0
  28. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/exception/__init__.py +0 -0
  29. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/models/__init__.py +0 -0
  30. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/models/test_db_config.py +0 -0
  31. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/monitoring/__init__.py +0 -0
  32. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/monitoring/alert/__init__.py +0 -0
  33. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/monitoring/alert/test_alerter.py +0 -0
  34. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/monitoring/test_async_trace.py +0 -0
  35. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/monitoring/test_tracer.py +0 -0
  36. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/prediction/__init__.py +0 -0
  37. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/search/__init__.py +0 -0
  38. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/search/test_execution_search.py +0 -0
  39. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/utils/__init__.py +0 -0
  40. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/utils/test_function_cahe.py +0 -0
  41. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/utils/test_replayer.py +0 -0
  42. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/utils/test_status.py +0 -0
  43. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/tests/vectorizer/__init__.py +0 -0
  44. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/batch/__init__.py +0 -0
  45. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/batch/batch.py +0 -0
  46. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/core/__init__.py +0 -0
  47. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/core/core.py +0 -0
  48. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/core/decorator.py +0 -0
  49. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/core/generator.py +0 -0
  50. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/database/__init__.py +0 -0
  51. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/database/archiver.py +0 -0
  52. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/database/db.py +0 -0
  53. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/exception/__init__.py +0 -0
  54. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/exception/exceptions.py +0 -0
  55. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/models/__init__.py +0 -0
  56. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/models/db_config.py +0 -0
  57. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/monitoring/__init__.py +0 -0
  58. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/monitoring/alert/__init__.py +0 -0
  59. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/monitoring/alert/base.py +0 -0
  60. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/monitoring/alert/factory.py +0 -0
  61. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/monitoring/alert/null_alerter.py +0 -0
  62. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/monitoring/alert/webhook_alerter.py +0 -0
  63. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/monitoring/monitoring.py +0 -0
  64. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/monitoring/tracer.py +0 -0
  65. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/prediction/__init__.py +0 -0
  66. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/prediction/predictor.py +0 -0
  67. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/search/__init__.py +0 -0
  68. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/search/execution_search.py +0 -0
  69. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/search/extended_search.py +0 -0
  70. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/search/rag_search.py +0 -0
  71. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/utils/__init__.py +0 -0
  72. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/utils/function_cache.py +0 -0
  73. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/utils/healer.py +0 -0
  74. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/utils/status.py +0 -0
  75. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/vectorizer/__init__.py +0 -0
  76. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/vectorizer/base.py +0 -0
  77. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/vectorizer/factory.py +0 -0
  78. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/vectorizer/huggingface_vectorizer.py +0 -0
  79. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave/vectorizer/openai_vectorizer.py +0 -0
  80. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave.egg-info/dependency_links.txt +0 -0
  81. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave.egg-info/requires.txt +0 -0
  82. {vectorwave-0.1.9 → vectorwave-0.2.0}/src/vectorwave.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vectorwave
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Summary: VectorWave: Seamless Auto-Vectorization Framework
5
5
  Author-email: junyeonggim <junyeonggim5@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vectorwave"
7
- version = "0.1.9"
7
+ version = "0.2.0"
8
8
  authors = [
9
9
  { name = "junyeonggim", email = "junyeonggim5@gmail.com" },
10
10
  ]
@@ -0,0 +1,126 @@
1
+ import pytest
2
+ from unittest.mock import MagicMock, patch
3
+ import json
4
+ from vectorwave.utils.return_caching_utils import _check_and_return_cached_result
5
+ from vectorwave.models.db_config import WeaviateSettings
6
+
7
+
8
+ # --- Mock Fixtures ---
9
+
10
+ @pytest.fixture
11
+ def mock_caching_utils_deps(monkeypatch):
12
+ """
13
+ Mocks external dependencies of return_caching_utils.py (BatchManager, Tracer, DB search, etc.).
14
+ """
15
+ # 1. Mock Settings
16
+ mock_settings = WeaviateSettings(
17
+ EXECUTION_COLLECTION_NAME="TestExecutions",
18
+ global_custom_values={"run_id": "test-run-123"}
19
+ )
20
+ mock_get_settings = MagicMock(return_value=mock_settings)
21
+
22
+ # 2. Mock Batch Manager (Key verification target)
23
+ mock_batch_manager = MagicMock()
24
+ mock_batch_manager.add_object = MagicMock()
25
+ mock_get_batch = MagicMock(return_value=mock_batch_manager)
26
+
27
+ # 3. Mock Vectorizer
28
+ mock_vectorizer = MagicMock()
29
+ mock_vectorizer.embed.return_value = [0.1, 0.2, 0.3] # Dummy Vector
30
+ mock_get_vectorizer = MagicMock(return_value=mock_vectorizer)
31
+
32
+ # 4. Mock Tracer Context (Provides current Trace ID)
33
+ mock_tracer = MagicMock()
34
+ mock_tracer.trace_id = "existing-trace-id-abc"
35
+
36
+ # 5. Apply Monkeypatches
37
+ TARGET_MODULE = "vectorwave.utils.return_caching_utils"
38
+
39
+ monkeypatch.setattr(f"{TARGET_MODULE}.get_weaviate_settings", mock_get_settings)
40
+ monkeypatch.setattr(f"{TARGET_MODULE}.get_batch_manager", mock_get_batch)
41
+ monkeypatch.setattr(f"{TARGET_MODULE}.get_vectorizer", mock_get_vectorizer)
42
+
43
+ return {
44
+ "batch_manager": mock_batch_manager,
45
+ "vectorizer": mock_vectorizer,
46
+ "tracer_obj": mock_tracer
47
+ }
48
+
49
+
50
+ def test_check_and_return_cached_result_cache_hit_logging(mock_caching_utils_deps):
51
+ """
52
+ [Case 1] Verify that DB logging is correctly performed with 'CACHE_HIT' status upon a cache hit.
53
+ """
54
+ # Arrange
55
+ # 1. Mock cache search result (Log found in DB)
56
+ mock_cached_log = {
57
+ "return_value": json.dumps({"result": "cached_data"}),
58
+ "metadata": {"distance": 0.1},
59
+ "uuid": "cached-log-uuid"
60
+ }
61
+
62
+ # 2. Mock search_similar_execution to return the above result (Simulate Cache Hit)
63
+ with patch("vectorwave.utils.return_caching_utils.search_similar_execution", return_value=mock_cached_log):
64
+ # [Fix] Instead of directly patching the .get method of the ContextVar object, replace the variable itself with a Mock object.
65
+ with patch("vectorwave.utils.return_caching_utils.current_tracer_var") as mock_tracer_var:
66
+ with patch("vectorwave.utils.return_caching_utils.current_span_id_var") as mock_span_var:
67
+ # Set return value for .get() call on the Mock object
68
+ mock_tracer_var.get.return_value = mock_caching_utils_deps["tracer_obj"]
69
+ mock_span_var.get.return_value = "parent-span-123"
70
+
71
+ # 4. Execute the target function
72
+ def dummy_func(a, b): pass # Target function
73
+
74
+ result = _check_and_return_cached_result(
75
+ func=dummy_func,
76
+ args=(10,),
77
+ kwargs={"b": 20},
78
+ function_name="dummy_func",
79
+ cache_threshold=0.9,
80
+ is_async=False
81
+ )
82
+
83
+ # Assert
84
+ # 1. Was the cached result returned correctly?
85
+ assert result == {"result": "cached_data"}
86
+
87
+ # 2. [Core] Was BatchManager.add_object called? (Check if logging occurred)
88
+ mock_batch = mock_caching_utils_deps["batch_manager"]
89
+ mock_batch.add_object.assert_called_once()
90
+
91
+ # 3. [Core] Verify properties of the saved log
92
+ call_kwargs = mock_batch.add_object.call_args.kwargs
93
+ props = call_kwargs["properties"]
94
+
95
+ assert props["status"] == "CACHE_HIT" # Check if status is CACHE_HIT
96
+ assert props["duration_ms"] == 0.0 # Check if duration is 0
97
+ assert props["trace_id"] == "existing-trace-id-abc" # Check if existing Trace ID is maintained
98
+ assert props["parent_span_id"] == "parent-span-123" # Check for parent Span ID maintenance
99
+ assert props["function_name"] == "dummy_func"
100
+ assert props["run_id"] == "test-run-123" # Check for inclusion of global tags
101
+
102
+
103
+ def test_check_and_return_cached_result_cache_miss(mock_caching_utils_deps):
104
+ """
105
+ [Case 2] Verify that None is returned without logging upon a cache miss.
106
+ """
107
+ # Arrange
108
+ # Set search_similar_execution to return None (Cache Miss)
109
+ with patch("vectorwave.utils.return_caching_utils.search_similar_execution", return_value=None):
110
+ def dummy_func(): pass
111
+
112
+ result = _check_and_return_cached_result(
113
+ func=dummy_func,
114
+ args=(),
115
+ kwargs={},
116
+ function_name="dummy_func",
117
+ cache_threshold=0.9,
118
+ is_async=False
119
+ )
120
+
121
+ # Assert
122
+ # 1. Result should be None (To proceed to actual function execution)
123
+ assert result is None
124
+
125
+ # 2. BatchManager.add_object should not be called
126
+ mock_caching_utils_deps["batch_manager"].add_object.assert_not_called()
@@ -5,6 +5,9 @@ from .database.db_search import search_functions, search_executions, search_erro
5
5
  from .monitoring.tracer import trace_span
6
6
  from .search.rag_search import search_and_answer, analyze_trace_log
7
7
  from .core.generator import generate_and_register_metadata
8
+ from .utils.healer import VectorWaveHealer
9
+ from .utils.replayer import VectorWaveReplayer
10
+ from .utils.replayer_semantic import SemanticReplayer
8
11
 
9
12
  __all__ = [
10
13
  'vectorize',
@@ -16,5 +19,8 @@ __all__ = [
16
19
  'trace_span',
17
20
  'search_and_answer',
18
21
  'analyze_trace_log',
19
- 'generate_and_register_metadata'
22
+ 'generate_and_register_metadata',
23
+ 'VectorWaveHealer',
24
+ 'VectorWaveReplayer',
25
+ 'SemanticReplayer'
20
26
  ]
@@ -409,3 +409,55 @@ def check_semantic_drift(
409
409
  except Exception as e:
410
410
  logger.error(f"Failed to check semantic drift: {e}")
411
411
  return False, 0.0, None
412
+
413
+
414
+ def simulate_drift_check(
415
+ text: str,
416
+ function_name: str,
417
+ threshold: Optional[float] = None,
418
+ k: Optional[int] = None
419
+ ) -> Dict[str, Any]:
420
+ """
421
+ Simulates drift detection for a hypothetical input string without executing the function.
422
+ Useful for 'Drift Radar' or debugging.
423
+ """
424
+ try:
425
+ settings = get_weaviate_settings()
426
+ vectorizer = get_vectorizer()
427
+
428
+ if not vectorizer:
429
+ return {"error": "No vectorizer configured."}
430
+
431
+ # 1. Set defaults from settings if not provided
432
+ if threshold is None:
433
+ threshold = settings.DRIFT_DISTANCE_THRESHOLD
434
+ if k is None:
435
+ k = settings.DRIFT_NEIGHBOR_AMOUNT
436
+
437
+ # 2. Vectorize the input text
438
+ try:
439
+ vector = vectorizer.embed(text)
440
+ except Exception as e:
441
+ return {"error": f"Vectorization failed: {e}"}
442
+
443
+ # 3. Perform the check using the existing logic
444
+ is_drift, avg_distance, nearest_uuid = check_semantic_drift(
445
+ vector=vector,
446
+ function_name=function_name,
447
+ threshold=threshold,
448
+ k=k
449
+ )
450
+
451
+ return {
452
+ "function_name": function_name,
453
+ "input_text": text,
454
+ "is_drift": is_drift,
455
+ "avg_distance": avg_distance,
456
+ "threshold": threshold,
457
+ "nearest_neighbor_uuid": nearest_uuid,
458
+ "status": "ANOMALY" if is_drift else "NORMAL"
459
+ }
460
+
461
+ except Exception as e:
462
+ logger.error(f"Simulation failed: {e}")
463
+ return {"error": str(e)}
@@ -5,6 +5,8 @@ import traceback
5
5
  import inspect
6
6
  from typing import Any, Dict, List, Optional
7
7
  import asyncio
8
+ import difflib
9
+ import pprint
8
10
 
9
11
  import weaviate.classes.query as wvc_query
10
12
 
@@ -33,6 +35,7 @@ class VectorWaveReplayer:
33
35
  update_baseline: bool = False) -> Dict[str, Any]:
34
36
  """
35
37
  Retrieves past execution history of a specific function, re-executes it (Replay), and validates the result.
38
+ Returns a dictionary containing diff_html for UI visualization.
36
39
  """
37
40
  # 1. Dynamic Function Loading (Dynamic Import)
38
41
  try:
@@ -72,7 +75,7 @@ class VectorWaveReplayer:
72
75
  logger.warning(f"No data found to test: {function_full_name}")
73
76
  return results
74
77
 
75
- print(f"🔄 Replaying {len(response.objects)} logs for '{function_full_name}'...")
78
+ logger.info(f"Starting Replay: {len(response.objects)} logs for '{function_full_name}'")
76
79
 
77
80
  for obj in response.objects:
78
81
  results["total"] += 1
@@ -92,37 +95,46 @@ class VectorWaveReplayer:
92
95
 
93
96
  if is_match:
94
97
  results["passed"] += 1
98
+ logger.debug(f"UUID {obj.uuid}: PASSED")
95
99
  else:
96
100
  # 5. Handle Mismatch
97
101
  if update_baseline:
98
102
  self._update_baseline_value(collection, obj.uuid, actual_output)
99
103
  results["updated"] += 1
100
104
  results["passed"] += 1
101
- print(f" ⚠️ [Updated] UUID {obj.uuid} result updated.")
105
+ logger.info(f"UUID {obj.uuid}: Baseline UPDATED")
102
106
  else:
103
107
  results["failed"] += 1
108
+
109
+ # Generate Visual Diff
110
+ diff_html = self._generate_diff_html(expected_output, actual_output)
111
+
104
112
  results["failures"].append({
105
113
  "uuid": str(obj.uuid),
106
114
  "inputs": inputs,
107
115
  "expected": expected_output,
108
- "actual": actual_output
116
+ "actual": actual_output,
117
+ "diff_html": diff_html # UI visualization field
109
118
  })
110
- print(f" ❌ [Failed] UUID {obj.uuid} | Expected: {expected_output} != Actual: {actual_output}")
119
+ logger.warning(f"UUID {obj.uuid}: FAILED (Mismatch)")
111
120
 
112
121
  except Exception as e:
113
-
114
122
  results["failed"] += 1
123
+ error_msg = f"Exception: {str(e)}"
124
+ logger.error(f"UUID {obj.uuid}: EXECUTION ERROR - {e}")
115
125
 
116
126
  results["failures"].append({
117
127
  "uuid": str(obj.uuid),
118
128
  "inputs": inputs,
119
- "expected": expected_output, # <-- Added
120
- "actual": "EXCEPTION_RAISED", # <-- Added
121
- "error": str(e),
129
+ "expected": expected_output,
130
+ "actual": "EXCEPTION_RAISED",
131
+ "error": error_msg,
132
+ "diff_html": f"<div class='error' style='color:red; padding:10px; border:1px solid red; background:#fff0f0;"
133
+ f"'><strong>Runtime Error:</strong><pre>{traceback.format_exc()}</pre></div>",
122
134
  "traceback": traceback.format_exc()
123
135
  })
124
- print(f" ❌ [Error] UUID {obj.uuid} exception occurred during execution: {e}")
125
136
 
137
+ logger.info(f"Replay Finished. Passed: {results['passed']}, Failed: {results['failed']}, Updated: {results['updated']}")
126
138
  return results
127
139
 
128
140
  def _extract_inputs(self, props: Dict[str, Any], target_func: callable) -> Dict[str, Any]:
@@ -180,3 +192,20 @@ class VectorWaveReplayer:
180
192
  )
181
193
  except Exception as e:
182
194
  logger.error(f"Failed to update baseline for {uuid}: {e}")
195
+
196
+ def _generate_diff_html(self, expected: Any, actual: Any) -> str:
197
+ """Generates a side-by-side HTML diff table."""
198
+ # Pretty print for better diff comparison
199
+ exp_str = pprint.pformat(expected, width=80)
200
+ act_str = pprint.pformat(actual, width=80)
201
+
202
+ # Generate HTML table
203
+ diff_html = difflib.HtmlDiff(wrapcolumn=80).make_table(
204
+ fromlines=exp_str.splitlines(),
205
+ tolines=act_str.splitlines(),
206
+ fromdesc='Expected (Baseline)',
207
+ todesc='Actual (Current)',
208
+ context=True, # Show only context around changes
209
+ numlines=3
210
+ )
211
+ return diff_html
@@ -1,10 +1,11 @@
1
+ import importlib
1
2
  import json
2
3
  import logging
3
4
  import traceback
4
5
  import math
5
6
  import asyncio
6
7
  import inspect
7
- from typing import Any, Dict, Optional
8
+ from typing import Any, Dict, List, Optional
8
9
 
9
10
  import weaviate.classes.query as wvc_query
10
11
 
@@ -23,6 +24,7 @@ class SemanticReplayer(VectorWaveReplayer):
23
24
  """
24
25
  A subclass of VectorWaveReplayer that adds AI-powered semantic comparison capabilities.
25
26
  It supports Vector Cosine Similarity and LLM-as-a-Judge evaluation.
27
+ Location: src/vectorwave/utils/replayer_semantic.py
26
28
  """
27
29
 
28
30
  def __init__(self):
@@ -39,22 +41,28 @@ class SemanticReplayer(VectorWaveReplayer):
39
41
  function_full_name: str,
40
42
  limit: int = 10,
41
43
  update_baseline: bool = False,
42
- similarity_threshold: Optional[float] = None, # [New]
43
- semantic_eval: bool = False # [New]
44
+ similarity_threshold: Optional[float] = None,
45
+ semantic_eval: bool = False
44
46
  ) -> Dict[str, Any]:
45
47
  """
46
- Overridden replay method to support semantic comparison options.
48
+ Retrieves past execution history of a specific function, re-executes it (Replay),
49
+ and validates the result using semantic comparison options.
50
+ Returns a dictionary containing diff_html for UI visualization.
47
51
  """
48
- # 1. Load Function dynamically (Reuse parent method logic if possible, but here we reimplement for clarity due to local vars)
49
- target_func = self._load_target_function(function_full_name)
50
- if isinstance(target_func, dict) and "error" in target_func:
51
- return target_func
52
+ # 1. Dynamic Function Loading (Dynamic Import)
53
+ try:
54
+ module_name, func_short_name = function_full_name.rsplit('.', 1)
55
+ module = importlib.import_module(module_name)
56
+ target_func = getattr(module, func_short_name)
57
+ except (ValueError, ImportError, AttributeError) as e:
58
+ logger.error(f"Could not load function: {function_full_name}. Error: {e}")
59
+ return {"error": f"Function loading failed: {e}"}
52
60
 
53
61
  is_async_func = inspect.iscoroutinefunction(target_func)
54
- func_short_name = function_full_name.rsplit('.', 1)[-1]
55
62
 
56
- # 2. Retrieve Data
63
+ # 2. Retrieve Test Data (Past Logs) from DB
57
64
  collection = self.client.collections.get(self.collection_name)
65
+
58
66
  filters = (
59
67
  wvc_query.Filter.by_property("function_name").equal(func_short_name) &
60
68
  wvc_query.Filter.by_property("status").equal("SUCCESS")
@@ -79,25 +87,26 @@ class SemanticReplayer(VectorWaveReplayer):
79
87
  logger.warning(f"No data found to test: {function_full_name}")
80
88
  return results
81
89
 
82
- print(f"🤖 [Semantic Replay] Testing {len(response.objects)} logs for '{function_full_name}'...")
90
+ logger.info(f"Starting Semantic Replay: {len(response.objects)} logs for '{function_full_name}'")
83
91
  if similarity_threshold:
84
- print(f" - Mode: Vector Similarity >= {similarity_threshold}")
92
+ logger.info(f" - Mode: Vector Similarity >= {similarity_threshold}")
85
93
  if semantic_eval:
86
- print(f" - Mode: Semantic Evaluation (LLM-as-a-Judge)")
94
+ logger.info(f" - Mode: Semantic Evaluation (LLM-as-a-Judge)")
87
95
 
88
96
  for obj in response.objects:
89
97
  results["total"] += 1
98
+
90
99
  inputs = self._extract_inputs(obj.properties, target_func)
91
100
  expected_output = self._deserialize_value(obj.properties.get("return_value"))
92
101
 
93
102
  try:
94
- # 3. Re-execution
103
+ # 3. Function Re-execution
95
104
  if is_async_func:
96
105
  actual_output = asyncio.run(target_func(**inputs))
97
106
  else:
98
107
  actual_output = target_func(**inputs)
99
108
 
100
- # 4. Enhanced Comparison
109
+ # 4. Result Validation (Semantic Comparison)
101
110
  is_match, match_reason = self._compare_results_semantic(
102
111
  expected_output,
103
112
  actual_output,
@@ -107,50 +116,49 @@ class SemanticReplayer(VectorWaveReplayer):
107
116
 
108
117
  if is_match:
109
118
  results["passed"] += 1
119
+ logger.debug(f"UUID {obj.uuid}: PASSED ({match_reason})")
110
120
  else:
111
121
  # 5. Handle Mismatch
112
122
  if update_baseline:
113
123
  self._update_baseline_value(collection, obj.uuid, actual_output)
114
124
  results["updated"] += 1
115
125
  results["passed"] += 1
116
- print(f" ⚠️ [Updated] UUID {obj.uuid} baseline updated.")
126
+ logger.info(f"UUID {obj.uuid}: Baseline UPDATED")
117
127
  else:
118
128
  results["failed"] += 1
129
+
130
+ # Generate Visual Diff (inherited from parent)
131
+ diff_html = self._generate_diff_html(expected_output, actual_output)
132
+
119
133
  results["failures"].append({
120
134
  "uuid": str(obj.uuid),
121
135
  "inputs": inputs,
122
136
  "expected": expected_output,
123
137
  "actual": actual_output,
124
- "reason": match_reason
138
+ "diff_html": diff_html, # UI visualization field
139
+ "reason": match_reason # Semantic failure reason
125
140
  })
126
- print(f" ❌ [Failed] UUID {obj.uuid} | {match_reason}")
127
- print(f" Expected: {str(expected_output)[:60]}...")
128
- print(f" Actual: {str(actual_output)[:60]}...")
141
+ logger.warning(f"UUID {obj.uuid}: FAILED ({match_reason})")
129
142
 
130
143
  except Exception as e:
131
144
  results["failed"] += 1
145
+ error_msg = f"Exception: {str(e)}"
146
+ logger.error(f"UUID {obj.uuid}: EXECUTION ERROR - {e}")
147
+
132
148
  results["failures"].append({
133
149
  "uuid": str(obj.uuid),
134
150
  "inputs": inputs,
135
151
  "expected": expected_output,
136
152
  "actual": "EXCEPTION_RAISED",
137
- "error": str(e),
153
+ "error": error_msg,
154
+ "diff_html": f"<div class='error' style='color:red; padding:10px; border:1px solid red; background:#fff0f0;"
155
+ f"'><strong>Runtime Error:</strong><pre>{traceback.format_exc()}</pre></div>",
138
156
  "traceback": traceback.format_exc()
139
157
  })
140
- print(f" ❌ [Error] UUID {obj.uuid}: {e}")
141
158
 
159
+ logger.info(f"Replay Finished. Passed: {results['passed']}, Failed: {results['failed']}, Updated: {results['updated']}")
142
160
  return results
143
161
 
144
- def _load_target_function(self, full_name: str):
145
- """Helper to load function (Refactored from original code for reuse)"""
146
- try:
147
- import importlib
148
- module_name, func_short_name = full_name.rsplit('.', 1)
149
- module = importlib.import_module(module_name)
150
- return getattr(module, func_short_name)
151
- except Exception as e:
152
- return {"error": str(e)}
153
-
154
162
  def _compare_results_semantic(self, expected: Any, actual: Any,
155
163
  similarity_threshold: Optional[float],
156
164
  semantic_eval: bool) -> tuple[bool, str]:
@@ -205,10 +213,10 @@ class SemanticReplayer(VectorWaveReplayer):
205
213
  prompt = f"""
206
214
  Compare two outputs. Are they semantically equivalent?
207
215
  Ignore minor formatting differences.
208
-
216
+
209
217
  Expected: {expected}
210
218
  Actual: {actual}
211
-
219
+
212
220
  Respond JSON: {{"equivalent": true}} or {{"equivalent": false}}
213
221
  """
214
222
  try:
@@ -221,4 +229,4 @@ class SemanticReplayer(VectorWaveReplayer):
221
229
  return json.loads(res.choices[0].message.content).get("equivalent", False)
222
230
  except Exception as e:
223
231
  logger.error(f"LLM Eval failed: {e}")
224
- return False
232
+ return False
@@ -0,0 +1,134 @@
1
+ import logging
2
+ from typing import Optional, Tuple, Any, Dict, Callable
3
+ import json
4
+ import inspect
5
+ from functools import wraps
6
+ from datetime import datetime, timezone
7
+ from uuid import uuid4
8
+
9
+ from weaviate.util import generate_uuid5
10
+
11
+ from ..models.db_config import get_weaviate_settings, WeaviateSettings
12
+ from ..monitoring.tracer import _create_input_vector_data, _deserialize_return_value, current_tracer_var, \
13
+ current_span_id_var
14
+ from ..database.db_search import search_similar_execution
15
+ from ..vectorizer.factory import get_vectorizer
16
+ from ..batch.batch import get_batch_manager
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def _check_and_return_cached_result(
22
+ func: Callable,
23
+ args: Tuple[Any, ...],
24
+ kwargs: Dict[str, Any],
25
+ function_name: str,
26
+ cache_threshold: float,
27
+ is_async: bool
28
+ ) -> Optional[Any]:
29
+ """
30
+ Checks the VectorWaveExecutions database for a semantically similar cached result.
31
+ If found, returns the deserialized result; otherwise, returns None.
32
+
33
+ This function should be called from the outer_wrapper of the @vectorize decorator.
34
+ """
35
+ if not cache_threshold: # Check if caching is effectively disabled (threshold 0 or None)
36
+ return None
37
+
38
+ settings: WeaviateSettings = get_weaviate_settings()
39
+ vectorizer = get_vectorizer()
40
+
41
+ if vectorizer is None:
42
+ # This case should ideally be caught in decorator setup, but defensive check remains.
43
+ logger.error(f"Cannot perform vectorization for caching on '{function_name}': Vectorizer is None.")
44
+ return None
45
+
46
+ try:
47
+ # (A) Create vectorization data
48
+ input_vector_data = _create_input_vector_data(
49
+ func_name=function_name,
50
+ args=args, # Original positional args
51
+ kwargs=kwargs, # Original keyword args
52
+ sensitive_keys=settings.sensitive_keys
53
+ )
54
+
55
+ # (B) Vectorize (Synchronous call is fine, as vectorizer.embed is sync)
56
+ input_vector = vectorizer.embed(input_vector_data['text'])
57
+
58
+ # (C) Search Cache (Needs to handle the fact that search_similar_execution is sync)
59
+ cached_log = search_similar_execution(
60
+ query_vector=input_vector,
61
+ function_name=function_name,
62
+ threshold=cache_threshold
63
+ )
64
+
65
+ if cached_log:
66
+ distance = cached_log['metadata'].get('distance')
67
+ logger.info(
68
+ f"[Cache Hit] '{function_name}' skipped. "
69
+ f"Distance: {distance:.4f}, "
70
+ f"Trace ID: {cached_log['uuid'][:8]}..."
71
+ )
72
+
73
+ # (D) Log CACHE_HIT event to DB (Analysis Requirement)
74
+ try:
75
+ batch_manager = get_batch_manager()
76
+
77
+ # 1. Resolve Trace Context
78
+ tracer = current_tracer_var.get()
79
+ parent_span_id = current_span_id_var.get()
80
+
81
+ if tracer:
82
+ trace_id = tracer.trace_id
83
+ else:
84
+ # If cache hit happens at the root (no active tracer), generate new ID
85
+ trace_id = str(uuid4())
86
+
87
+ # 2. Calculate function UUID (to link with metadata)
88
+ module_name = getattr(func, "__module__", "__main__")
89
+ func_identifier = f"{module_name}.{function_name}"
90
+ func_uuid = generate_uuid5(func_identifier)
91
+
92
+ # 3. Prepare properties
93
+ hit_properties = {
94
+ "trace_id": trace_id,
95
+ "span_id": str(uuid4()),
96
+ "parent_span_id": parent_span_id,
97
+ "function_name": function_name,
98
+ "function_uuid": func_uuid,
99
+ "timestamp_utc": datetime.now(timezone.utc).isoformat(),
100
+ "duration_ms": 0.0, # Zero duration for cache hits
101
+ "status": "CACHE_HIT",
102
+ "return_value": cached_log.get('return_value'),
103
+ }
104
+
105
+ # 4. Inject Global/Custom Tags
106
+ if settings.global_custom_values:
107
+ hit_properties.update(settings.global_custom_values)
108
+
109
+ if settings.custom_properties:
110
+ for key in settings.custom_properties:
111
+ if key in kwargs:
112
+ # Simple assignment for tags (complex masking not applied here for simplicity/perf)
113
+ hit_properties[key] = kwargs[key]
114
+
115
+ # 5. Add to batch (Async logging)
116
+ batch_manager.add_object(
117
+ collection=settings.EXECUTION_COLLECTION_NAME,
118
+ properties=hit_properties,
119
+ vector=input_vector # Reuse the query vector
120
+ )
121
+
122
+ except Exception as log_e:
123
+ # Non-blocking error: logging failure should not prevent returning the cached result
124
+ logger.error(f"Failed to log CACHE_HIT for '{function_name}': {log_e}")
125
+
126
+ # (E) Deserialize and return the cached result
127
+ return _deserialize_return_value(cached_log.get('return_value'))
128
+
129
+ return None
130
+
131
+ except Exception as e:
132
+ logger.error(f"Failed to check semantic cache for '{function_name}': {e}", exc_info=True)
133
+ # Cache failure must not stop execution, so return None to proceed to actual function call.
134
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vectorwave
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Summary: VectorWave: Seamless Auto-Vectorization Framework
5
5
  Author-email: junyeonggim <junyeonggim5@gmail.com>
6
6
  License-Expression: MIT
@@ -28,6 +28,7 @@ src/tests/search/test_execution_search.py
28
28
  src/tests/utils/__init__.py
29
29
  src/tests/utils/test_function_cahe.py
30
30
  src/tests/utils/test_replayer.py
31
+ src/tests/utils/test_return_caching.py
31
32
  src/tests/utils/test_status.py
32
33
  src/tests/vectorizer/__init__.py
33
34
  src/vectorwave/__init__.py
@@ -1,76 +0,0 @@
1
- import logging
2
- from typing import Optional, Tuple, Any, Dict, Callable
3
- import json
4
- import inspect
5
- from functools import wraps
6
-
7
- from weaviate.util import generate_uuid5
8
-
9
- from ..models.db_config import get_weaviate_settings, WeaviateSettings
10
- from ..monitoring.tracer import _create_input_vector_data, _deserialize_return_value
11
- from ..database.db_search import search_similar_execution
12
- from ..vectorizer.factory import get_vectorizer
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- def _check_and_return_cached_result(
18
- func: Callable,
19
- args: Tuple[Any, ...],
20
- kwargs: Dict[str, Any],
21
- function_name: str,
22
- cache_threshold: float,
23
- is_async: bool
24
- ) -> Optional[Any]:
25
- """
26
- Checks the VectorWaveExecutions database for a semantically similar cached result.
27
- If found, returns the deserialized result; otherwise, returns None.
28
-
29
- This function should be called from the outer_wrapper of the @vectorize decorator.
30
- """
31
- if not cache_threshold: # Check if caching is effectively disabled (threshold 0 or None)
32
- return None
33
-
34
- settings: WeaviateSettings = get_weaviate_settings()
35
- vectorizer = get_vectorizer()
36
-
37
- if vectorizer is None:
38
- # This case should ideally be caught in decorator setup, but defensive check remains.
39
- logger.error(f"Cannot perform vectorization for caching on '{function_name}': Vectorizer is None.")
40
- return None
41
-
42
- try:
43
- # (A) Create vectorization data
44
- input_vector_data = _create_input_vector_data(
45
- func_name=function_name,
46
- args=args, # Original positional args
47
- kwargs=kwargs, # Original keyword args
48
- sensitive_keys=settings.sensitive_keys
49
- )
50
-
51
- # (B) Vectorize (Synchronous call is fine, as vectorizer.embed is sync)
52
- input_vector = vectorizer.embed(input_vector_data['text'])
53
-
54
- # (C) Search Cache (Needs to handle the fact that search_similar_execution is sync)
55
- cached_log = search_similar_execution(
56
- query_vector=input_vector,
57
- function_name=function_name,
58
- threshold=cache_threshold
59
- )
60
-
61
- if cached_log:
62
- distance = cached_log['metadata'].get('distance')
63
- logger.info(
64
- f"[Cache Hit] '{function_name}' skipped. "
65
- f"Distance: {distance:.4f}, "
66
- f"Trace ID: {cached_log['uuid'][:8]}..."
67
- )
68
- # (D) Deserialize and return the cached result
69
- return _deserialize_return_value(cached_log.get('return_value'))
70
-
71
- return None
72
-
73
- except Exception as e:
74
- logger.error(f"Failed to check semantic cache for '{function_name}': {e}", exc_info=True)
75
- # Cache failure must not stop execution, so return None to proceed to actual function call.
76
- return None
File without changes
File without changes
File without changes
File without changes
File without changes