vectorwave 0.2.0__tar.gz → 0.2.1__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 (87) hide show
  1. {vectorwave-0.2.0/src/vectorwave.egg-info → vectorwave-0.2.1}/PKG-INFO +1 -1
  2. {vectorwave-0.2.0 → vectorwave-0.2.1}/pyproject.toml +1 -1
  3. vectorwave-0.2.1/src/tests/core/llm/test_token_usage.py +112 -0
  4. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/core/generator.py +11 -14
  5. vectorwave-0.2.1/src/vectorwave/core/llm/base.py +47 -0
  6. vectorwave-0.2.1/src/vectorwave/core/llm/factory.py +13 -0
  7. vectorwave-0.2.1/src/vectorwave/core/llm/openai_client.py +79 -0
  8. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/database/db.py +32 -1
  9. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/database/db_search.py +32 -0
  10. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/search/rag_search.py +10 -17
  11. vectorwave-0.2.1/src/vectorwave/utils/__init__.py +0 -0
  12. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/utils/healer.py +14 -14
  13. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/utils/replayer_semantic.py +16 -13
  14. vectorwave-0.2.1/src/vectorwave/vectorizer/__init__.py +0 -0
  15. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/vectorizer/openai_vectorizer.py +16 -6
  16. {vectorwave-0.2.0 → vectorwave-0.2.1/src/vectorwave.egg-info}/PKG-INFO +1 -1
  17. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave.egg-info/SOURCES.txt +6 -0
  18. {vectorwave-0.2.0 → vectorwave-0.2.1}/LICENSE +0 -0
  19. {vectorwave-0.2.0 → vectorwave-0.2.1}/MANIFEST.in +0 -0
  20. {vectorwave-0.2.0 → vectorwave-0.2.1}/NOTICE +0 -0
  21. {vectorwave-0.2.0 → vectorwave-0.2.1}/Readme.md +0 -0
  22. {vectorwave-0.2.0 → vectorwave-0.2.1}/setup.cfg +0 -0
  23. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/__init__.py +0 -0
  24. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/batch/__init__.py +0 -0
  25. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/batch/test_batch.py +0 -0
  26. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/conftest.py +0 -0
  27. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/core/__init__.py +0 -0
  28. {vectorwave-0.2.0/src/tests/database → vectorwave-0.2.1/src/tests/core/llm}/__init__.py +0 -0
  29. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/core/test_decorator.py +0 -0
  30. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/core/test_semantic_caching.py +0 -0
  31. {vectorwave-0.2.0/src/tests/exception → vectorwave-0.2.1/src/tests/database}/__init__.py +0 -0
  32. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/database/test_archiver.py +0 -0
  33. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/database/test_db.py +0 -0
  34. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/database/test_db_search.py +0 -0
  35. {vectorwave-0.2.0/src/tests/models → vectorwave-0.2.1/src/tests/exception}/__init__.py +0 -0
  36. {vectorwave-0.2.0/src/tests/monitoring → vectorwave-0.2.1/src/tests/models}/__init__.py +0 -0
  37. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/models/test_db_config.py +0 -0
  38. {vectorwave-0.2.0/src/tests/monitoring/alert → vectorwave-0.2.1/src/tests/monitoring}/__init__.py +0 -0
  39. {vectorwave-0.2.0/src/tests/prediction → vectorwave-0.2.1/src/tests/monitoring/alert}/__init__.py +0 -0
  40. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/monitoring/alert/test_alerter.py +0 -0
  41. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/monitoring/test_async_trace.py +0 -0
  42. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/monitoring/test_tracer.py +0 -0
  43. {vectorwave-0.2.0/src/tests/search → vectorwave-0.2.1/src/tests/prediction}/__init__.py +0 -0
  44. {vectorwave-0.2.0/src/tests/utils → vectorwave-0.2.1/src/tests/search}/__init__.py +0 -0
  45. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/search/test_execution_search.py +0 -0
  46. {vectorwave-0.2.0/src/tests/vectorizer → vectorwave-0.2.1/src/tests/utils}/__init__.py +0 -0
  47. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/utils/test_function_cahe.py +0 -0
  48. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/utils/test_replayer.py +0 -0
  49. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/utils/test_return_caching.py +0 -0
  50. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/tests/utils/test_status.py +0 -0
  51. {vectorwave-0.2.0/src/vectorwave/batch → vectorwave-0.2.1/src/tests/vectorizer}/__init__.py +0 -0
  52. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/__init__.py +0 -0
  53. {vectorwave-0.2.0/src/vectorwave/core → vectorwave-0.2.1/src/vectorwave/batch}/__init__.py +0 -0
  54. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/batch/batch.py +0 -0
  55. {vectorwave-0.2.0/src/vectorwave/database → vectorwave-0.2.1/src/vectorwave/core}/__init__.py +0 -0
  56. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/core/core.py +0 -0
  57. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/core/decorator.py +0 -0
  58. {vectorwave-0.2.0/src/vectorwave/exception → vectorwave-0.2.1/src/vectorwave/core/llm}/__init__.py +0 -0
  59. {vectorwave-0.2.0/src/vectorwave/models → vectorwave-0.2.1/src/vectorwave/database}/__init__.py +0 -0
  60. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/database/archiver.py +0 -0
  61. {vectorwave-0.2.0/src/vectorwave/monitoring → vectorwave-0.2.1/src/vectorwave/exception}/__init__.py +0 -0
  62. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/exception/exceptions.py +0 -0
  63. {vectorwave-0.2.0/src/vectorwave/monitoring/alert → vectorwave-0.2.1/src/vectorwave/models}/__init__.py +0 -0
  64. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/models/db_config.py +0 -0
  65. {vectorwave-0.2.0/src/vectorwave/prediction → vectorwave-0.2.1/src/vectorwave/monitoring}/__init__.py +0 -0
  66. {vectorwave-0.2.0/src/vectorwave/search → vectorwave-0.2.1/src/vectorwave/monitoring/alert}/__init__.py +0 -0
  67. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/monitoring/alert/base.py +0 -0
  68. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/monitoring/alert/factory.py +0 -0
  69. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/monitoring/alert/null_alerter.py +0 -0
  70. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/monitoring/alert/webhook_alerter.py +0 -0
  71. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/monitoring/monitoring.py +0 -0
  72. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/monitoring/tracer.py +0 -0
  73. {vectorwave-0.2.0/src/vectorwave/utils → vectorwave-0.2.1/src/vectorwave/prediction}/__init__.py +0 -0
  74. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/prediction/predictor.py +0 -0
  75. {vectorwave-0.2.0/src/vectorwave/vectorizer → vectorwave-0.2.1/src/vectorwave/search}/__init__.py +0 -0
  76. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/search/execution_search.py +0 -0
  77. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/search/extended_search.py +0 -0
  78. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/utils/function_cache.py +0 -0
  79. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/utils/replayer.py +0 -0
  80. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/utils/return_caching_utils.py +0 -0
  81. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/utils/status.py +0 -0
  82. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/vectorizer/base.py +0 -0
  83. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/vectorizer/factory.py +0 -0
  84. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave/vectorizer/huggingface_vectorizer.py +0 -0
  85. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave.egg-info/dependency_links.txt +0 -0
  86. {vectorwave-0.2.0 → vectorwave-0.2.1}/src/vectorwave.egg-info/requires.txt +0 -0
  87. {vectorwave-0.2.0 → vectorwave-0.2.1}/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.2.0
3
+ Version: 0.2.1
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.2.0"
7
+ version = "0.2.01"
8
8
  authors = [
9
9
  { name = "junyeonggim", email = "junyeonggim5@gmail.com" },
10
10
  ]
@@ -0,0 +1,112 @@
1
+ import pytest
2
+ from unittest.mock import MagicMock, patch
3
+ from vectorwave.core.llm.openai_client import VectorWaveOpenAIClient
4
+
5
+ @pytest.fixture
6
+ def mock_deps(monkeypatch):
7
+ """
8
+ Mocks the dependencies (BatchManager, OpenAI, Settings) of VectorWaveOpenAIClient.
9
+ """
10
+ # 1. Mock BatchManager (Target for verifying token usage persistence)
11
+ mock_batch = MagicMock()
12
+ mock_batch.add_object = MagicMock()
13
+ mock_get_batch = MagicMock(return_value=mock_batch)
14
+ monkeypatch.setattr("vectorwave.core.llm.openai_client.get_batch_manager", mock_get_batch)
15
+
16
+ # 2. Mock Settings (Bypasses API Key check)
17
+ mock_settings = MagicMock()
18
+ mock_settings.OPENAI_API_KEY = "sk-test-key"
19
+ mock_get_settings = MagicMock(return_value=mock_settings)
20
+ monkeypatch.setattr("vectorwave.core.llm.openai_client.get_weaviate_settings", mock_get_settings)
21
+
22
+ # 3. Mock OpenAI Class (Bypasses actual API calls)
23
+ mock_openai_cls = MagicMock()
24
+ monkeypatch.setattr("vectorwave.core.llm.openai_client.OpenAI", mock_openai_cls)
25
+
26
+ # Note: Clearing the lru_cache for the singleton instance is typically
27
+ # needed if the client is imported globally. We assume the current test
28
+ # setup allows for fresh initialization here.
29
+
30
+ return {
31
+ "batch": mock_batch,
32
+ "openai_cls": mock_openai_cls
33
+ }
34
+
35
+ def test_chat_completion_logs_token_usage(mock_deps):
36
+ """
37
+ [Case 1] Verify that token usage is logged as 'generation' type during chat completion.
38
+ """
39
+ # Arrange
40
+ # Get the mock client instance returned when OpenAI() is called
41
+ mock_client_instance = mock_deps["openai_cls"].return_value
42
+
43
+ # Mock OpenAI Response structure
44
+ mock_response = MagicMock()
45
+ mock_response.choices = [MagicMock(message=MagicMock(content="Test response"))]
46
+ mock_response.usage.total_tokens = 123 # Token count to test
47
+ mock_client_instance.chat.completions.create.return_value = mock_response
48
+
49
+ # Act
50
+ # Create a new client instance for the test
51
+ client = VectorWaveOpenAIClient()
52
+ result = client.create_chat_completion(
53
+ messages=[{"role": "user", "content": "Hi"}],
54
+ model="gpt-4-test",
55
+ category="test_chat_category"
56
+ )
57
+
58
+ # Assert
59
+ # 1. Verify that the function returns the expected result
60
+ assert result == "Test response"
61
+
62
+ # 2. Verify that BatchManager.add_object was called (log saved)
63
+ mock_batch = mock_deps["batch"]
64
+ mock_batch.add_object.assert_called_once()
65
+
66
+ # 3. Verify the properties of the saved log entry
67
+ call_kwargs = mock_batch.add_object.call_args.kwargs
68
+ props = call_kwargs["properties"]
69
+
70
+ assert call_kwargs["collection"] == "VectorWaveTokenUsage"
71
+ assert props["tokens"] == 123
72
+ assert props["model"] == "gpt-4-test"
73
+ assert props["category"] == "test_chat_category"
74
+ assert props["usage_type"] == "generation"
75
+
76
+ def test_create_embedding_logs_token_usage(mock_deps):
77
+ """
78
+ [Case 2] Verify that token usage is logged as 'embedding' type during embedding creation.
79
+ """
80
+ # Arrange
81
+ mock_client_instance = mock_deps["openai_cls"].return_value
82
+
83
+ # Mock OpenAI Response structure
84
+ mock_response = MagicMock()
85
+ mock_response.data = [MagicMock(embedding=[0.1, 0.2, 0.3])]
86
+ mock_response.usage.total_tokens = 45 # Token count to test
87
+ mock_client_instance.embeddings.create.return_value = mock_response
88
+
89
+ # Act
90
+ client = VectorWaveOpenAIClient()
91
+ result = client.create_embedding(
92
+ text="Test text",
93
+ model="text-embedding-3-small",
94
+ category="test_embed_category"
95
+ )
96
+
97
+ # Assert
98
+ # 1. Verify that the function returns the expected result
99
+ assert result == [0.1, 0.2, 0.3]
100
+
101
+ # 2. Verify that BatchManager.add_object was called
102
+ mock_batch = mock_deps["batch"]
103
+ mock_batch.add_object.assert_called_once()
104
+
105
+ # 3. Verify the properties of the saved log entry
106
+ call_kwargs = mock_batch.add_object.call_args.kwargs
107
+ props = call_kwargs["properties"]
108
+
109
+ assert call_kwargs["collection"] == "VectorWaveTokenUsage"
110
+ assert props["tokens"] == 45
111
+ assert props["usage_type"] == "embedding"
112
+ assert props["category"] == "test_embed_category"
@@ -7,6 +7,7 @@ from ..models.db_config import get_weaviate_settings
7
7
  from ..batch.batch import get_batch_manager
8
8
  from ..vectorizer.factory import get_vectorizer
9
9
  from .decorator import PENDING_FUNCTIONS
10
+ from .llm.factory import get_llm_client
10
11
 
11
12
  logger = logging.getLogger(__name__)
12
13
 
@@ -15,20 +16,11 @@ try:
15
16
  except ImportError:
16
17
  OpenAI = None
17
18
 
18
- def _get_openai_client(settings):
19
- if OpenAI is None:
20
- logger.error("OpenAI library not installed. Install with 'pip install openai'.")
21
- return None
22
- if not settings.OPENAI_API_KEY:
23
- logger.error("OPENAI_API_KEY is missing in .env.")
24
- return None
25
- return OpenAI(api_key=settings.OPENAI_API_KEY)
26
-
27
19
 
28
20
  def generate_metadata_via_llm(source_code: str, func_name: str) -> Optional[Dict[str, str]]:
29
21
  """Call LLM to generate description and narrative from source code."""
30
22
  settings = get_weaviate_settings()
31
- client = _get_openai_client(settings)
23
+ client = get_llm_client()
32
24
  if not client:
33
25
  return None
34
26
 
@@ -47,17 +39,22 @@ def generate_metadata_via_llm(source_code: str, func_name: str) -> Optional[Dict
47
39
  """
48
40
 
49
41
  try:
50
- response = client.chat.completions.create(
42
+ # Refactored to use BaseLLMClient interface
43
+ response_text = client.create_chat_completion(
51
44
  model="gpt-4o-mini",
52
45
  messages=[
53
46
  {"role": "system", "content": "You are a technical documentation assistant. Output only JSON."},
54
47
  {"role": "user", "content": prompt}
55
48
  ],
56
49
  temperature=0.0,
57
- response_format={"type": "json_object"}
50
+ response_format={"type": "json_object"},
51
+ category="auto_doc"
58
52
  )
59
- content = response.choices[0].message.content
60
- return json.loads(content)
53
+
54
+ if response_text:
55
+ return json.loads(response_text)
56
+ return None
57
+
61
58
  except Exception as e:
62
59
  logger.error(f"LLM generation failed for '{func_name}': {e}")
63
60
  return None
@@ -0,0 +1,47 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List, Dict, Optional
3
+
4
+ class BaseLLMClient(ABC):
5
+ """
6
+ Abstract interface that all LLM Providers (OpenAI, Anthropic, etc.) must implement.
7
+ Implementations must handle internal token usage logging.
8
+ """
9
+
10
+ @abstractmethod
11
+ def create_embedding(self, text: str, model: str, category: str = "default") -> Optional[List[float]]:
12
+ """
13
+ Generates text embeddings.
14
+
15
+ Args:
16
+ text: The text to embed.
17
+ model: The name of the model to use.
18
+ category: Category for aggregating token usage (e.g., 'execution_log', 'auto_doc').
19
+
20
+ Returns:
21
+ The generated list of embedding vectors (None on failure).
22
+ """
23
+ pass
24
+
25
+ @abstractmethod
26
+ def create_chat_completion(
27
+ self,
28
+ messages: List[Dict],
29
+ model: str,
30
+ temperature: float = 0.1,
31
+ response_format: Optional[Dict] = None,
32
+ category: str = "default"
33
+ ) -> Optional[str]:
34
+ """
35
+ Generates a chat completion (response).
36
+
37
+ Args:
38
+ messages: List of conversation messages [{"role": "user", "content": "..."}].
39
+ model: The name of the model to use.
40
+ temperature: Parameter for controlling generation diversity.
41
+ response_format: Response format (e.g., {"type": "json_object"}).
42
+ category: Category for aggregating token usage (e.g., 'execution_log', 'auto_doc').
43
+
44
+ Returns:
45
+ The generated text response (None on failure).
46
+ """
47
+ pass
@@ -0,0 +1,13 @@
1
+ from .base import BaseLLMClient
2
+ from .openai_client import VectorWaveOpenAIClient
3
+
4
+ _llm_instance: BaseLLMClient = None
5
+
6
+
7
+ def get_llm_client() -> BaseLLMClient:
8
+ """Returns the singleton LLM client instance."""
9
+ global _llm_instance
10
+ if _llm_instance is None:
11
+ # Can be extended later to return different clients (Anthropic, etc.) based on settings (VECTORIZER, etc.)
12
+ _llm_instance = VectorWaveOpenAIClient()
13
+ return _llm_instance
@@ -0,0 +1,79 @@
1
+ # src/vectorwave/core/llm/openai_client.py
2
+ import logging
3
+ from typing import List, Dict, Optional
4
+ from datetime import datetime, timezone
5
+ from ...models.db_config import get_weaviate_settings
6
+ from ...batch.batch import get_batch_manager # [추가]
7
+ from .base import BaseLLMClient
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ try:
12
+ from openai import OpenAI
13
+ except ImportError:
14
+ OpenAI = None
15
+
16
+
17
+ class VectorWaveOpenAIClient(BaseLLMClient):
18
+ def __init__(self):
19
+ self.settings = get_weaviate_settings()
20
+ self.batch_manager = get_batch_manager()
21
+
22
+ if OpenAI is None or not self.settings.OPENAI_API_KEY:
23
+ self.client = None
24
+ else:
25
+ self.client = OpenAI(api_key=self.settings.OPENAI_API_KEY)
26
+
27
+ def _log_usage(self, tokens: int, model: str, usage_type: str, category: str):
28
+ if tokens > 0:
29
+ try:
30
+ self.batch_manager.add_object(
31
+ collection="VectorWaveTokenUsage",
32
+ properties={
33
+ "timestamp_utc": datetime.now(timezone.utc).isoformat(),
34
+ "model": model,
35
+ "usage_type": usage_type,
36
+ "category": category,
37
+ "tokens": tokens
38
+ }
39
+ )
40
+ except Exception as e:
41
+ logger.warning(f"Failed to log token usage: {e}")
42
+
43
+ def create_embedding(self, text: str, model: str = "text-embedding-3-small", category: str = "default") -> Optional[
44
+ List[float]]:
45
+ """
46
+ Returns: Vector list only (Tokens are logged internally)
47
+ """
48
+ if not self.client: return None
49
+ try:
50
+ text = text.replace("\n", " ")
51
+ res = self.client.embeddings.create(input=[text], model=model)
52
+
53
+ tokens = res.usage.total_tokens if res.usage else 0
54
+ self._log_usage(tokens, model, "embedding", category)
55
+
56
+ return res.data[0].embedding
57
+ except Exception as e:
58
+ logger.error(f"Embedding error: {e}")
59
+ return None
60
+
61
+ def create_chat_completion(self, messages: List[Dict], model: str = "gpt-4-turbo", temperature: float = 0.1,
62
+ response_format=None, category: str = "default") -> Optional[str]:
63
+ """
64
+ Returns: Content string only (Tokens are logged internally)
65
+ """
66
+ if not self.client: return None
67
+ try:
68
+ kwargs = {"model": model, "messages": messages, "temperature": temperature}
69
+ if response_format: kwargs["response_format"] = response_format
70
+
71
+ res = self.client.chat.completions.create(**kwargs)
72
+
73
+ tokens = res.usage.total_tokens if res.usage else 0
74
+ self._log_usage(tokens, model, "generation", category)
75
+
76
+ return res.choices[0].message.content
77
+ except Exception as e:
78
+ logger.error(f"Completion error: {e}")
79
+ return None
@@ -16,6 +16,7 @@ from weaviate.exceptions import WeaviateConnectionError as WeaviateClientConnect
16
16
  # Create module-level logger
17
17
  logger = logging.getLogger(__name__)
18
18
 
19
+
19
20
  # Code based on Weaviate v4 (latest) client.
20
21
 
21
22
  def get_weaviate_client(settings: WeaviateSettings) -> weaviate.WeaviateClient:
@@ -324,6 +325,36 @@ def create_execution_schema(client: weaviate.WeaviateClient, settings: WeaviateS
324
325
  raise SchemaCreationError(f"Error during execution schema creation: {e}")
325
326
 
326
327
 
328
+ def create_usage_schema(client: weaviate.WeaviateClient, settings: WeaviateSettings):
329
+ """
330
+ API call token analysis schema
331
+ """
332
+ collection_name = "VectorWaveTokenUsage"
333
+
334
+ if client.collections.exists(collection_name):
335
+ return client.collections.get(collection_name)
336
+
337
+ logger.info("Creating collection '%s'", collection_name)
338
+
339
+ properties = [
340
+ wvc.Property(name="timestamp_utc", data_type=wvc.DataType.DATE),
341
+ wvc.Property(name="model", data_type=wvc.DataType.TEXT),
342
+ wvc.Property(name="usage_type", data_type=wvc.DataType.TEXT), # "embedding", "generation" 등
343
+ wvc.Property(name="category", data_type=wvc.DataType.TEXT), # "execution_log", "auto_doc" 등
344
+ wvc.Property(name="tokens", data_type=wvc.DataType.INT),
345
+ ]
346
+
347
+ try:
348
+ return client.collections.create(
349
+ name=collection_name,
350
+ properties=properties,
351
+ vectorizer_config=wvc.Configure.Vectorizer.none(),
352
+ )
353
+ except Exception as e:
354
+ logger.error(f"Error creating usage schema: {e}")
355
+ raise SchemaCreationError(f"Error during usage schema creation: {e}")
356
+
357
+
327
358
  def initialize_database():
328
359
  """
329
360
  Helper function to initialize both the client and the two schemas.
@@ -334,8 +365,8 @@ def initialize_database():
334
365
  if client:
335
366
  create_vectorwave_schema(client, settings)
336
367
  create_execution_schema(client, settings)
368
+ create_usage_schema(client, settings)
337
369
  return client
338
370
  except Exception as e:
339
371
  logger.error("Failed to initialize VectorWave database: %s", e)
340
372
  return None
341
-
@@ -10,6 +10,7 @@ from ..models.db_config import get_weaviate_settings, WeaviateSettings
10
10
  from .db import get_cached_client
11
11
  from ..exception.exceptions import WeaviateConnectionError
12
12
  from ..vectorizer.factory import get_vectorizer
13
+ from weaviate.classes.aggregate import Metrics
13
14
 
14
15
  import uuid
15
16
  from datetime import datetime
@@ -461,3 +462,34 @@ def simulate_drift_check(
461
462
  except Exception as e:
462
463
  logger.error(f"Simulation failed: {e}")
463
464
  return {"error": str(e)}
465
+
466
+
467
+ def get_token_usage_stats() -> Dict[str, int]:
468
+ """VectorWaveTokenUsage collections based analysis"""
469
+ try:
470
+ client = get_cached_client()
471
+ if not client.collections.exists("VectorWaveTokenUsage"):
472
+ logger.warning("VectorWaveTokenUsage collection does not exist.")
473
+ return {"total_tokens": 0}
474
+
475
+ usage_col = client.collections.get("VectorWaveTokenUsage")
476
+
477
+ total_tokens = 0
478
+ stats = {}
479
+
480
+ for obj in usage_col.iterator():
481
+ props = obj.properties
482
+ tokens = int(props.get("tokens", 0))
483
+ category = props.get("category", "unknown")
484
+
485
+ total_tokens += tokens
486
+
487
+ cat_key = f"{category}_tokens"
488
+ stats[cat_key] = stats.get(cat_key, 0) + tokens
489
+
490
+ stats["total_tokens"] = total_tokens
491
+ return stats
492
+
493
+ except Exception as e:
494
+ logger.error(f"Stats error: {e}", exc_info=True)
495
+ return {}
@@ -3,6 +3,7 @@ import logging
3
3
  from ..database.db_search import search_functions
4
4
  from ..models.db_config import get_weaviate_settings
5
5
  from ..search.execution_search import find_by_trace_id
6
+ from ..core.llm.factory import get_llm_client
6
7
 
7
8
  try:
8
9
  from openai import OpenAI
@@ -13,17 +14,7 @@ logger = logging.getLogger(__name__)
13
14
 
14
15
 
15
16
  def _get_openai_client():
16
- """Loads the OpenAI client (includes configuration check)."""
17
- if OpenAI is None:
18
- logger.error("OpenAI library not found. Install with 'pip install openai'.")
19
- return None
20
-
21
- settings = get_weaviate_settings()
22
- if not settings.OPENAI_API_KEY:
23
- logger.error("OPENAI_API_KEY is missing in environment variables.")
24
- return None
25
-
26
- return OpenAI(api_key=settings.OPENAI_API_KEY)
17
+ return get_llm_client()
27
18
 
28
19
 
29
20
  def search_and_answer(query: str, model: str = "gpt-4-turbo", language: str = "en") -> str:
@@ -82,15 +73,16 @@ def search_and_answer(query: str, model: str = "gpt-4-turbo", language: str = "e
82
73
  return msg if language == 'en' else "❌ OpenAI 클라이언트를 초기화할 수 없습니다. (.env 설정 확인 필요)"
83
74
 
84
75
  try:
85
- response = client.chat.completions.create(
76
+ response_text = client.create_chat_completion(
86
77
  model=model,
87
78
  messages=[
88
79
  {"role": "system", "content": system_instruction},
89
80
  {"role": "user", "content": user_message}
90
81
  ],
91
- temperature=0.1
82
+ temperature=0.1,
83
+ category="rag_answer"
92
84
  )
93
- return response.choices[0].message.content
85
+ return response_text if response_text else "❌ Failed to generate response."
94
86
 
95
87
  except Exception as e:
96
88
  logger.error(f"LLM generation failed: {e}")
@@ -146,15 +138,16 @@ def analyze_trace_log(trace_id: str, model: str = "gpt-4-turbo", language: str =
146
138
  return msg if language == 'en' else "❌ OpenAI 클라이언트 초기화 실패"
147
139
 
148
140
  try:
149
- response = client.chat.completions.create(
141
+ response_text = client.create_chat_completion(
150
142
  model=model,
151
143
  messages=[
152
144
  {"role": "system", "content": system_instruction},
153
145
  {"role": "user", "content": log_summary}
154
146
  ],
155
- temperature=0.1
147
+ temperature=0.1,
148
+ category="trace_analysis"
156
149
  )
157
- return response.choices[0].message.content
150
+ return response_text if response_text else "❌ Failed to generate analysis."
158
151
 
159
152
  except Exception as e:
160
153
  error_msg = f"❌ Error during analysis: {e}"
File without changes
@@ -7,6 +7,7 @@ from datetime import datetime, timedelta, timezone
7
7
  from ..search.execution_search import find_executions
8
8
  from ..database.db_search import search_functions_hybrid
9
9
  from ..models.db_config import get_weaviate_settings
10
+ from ..core.llm.factory import get_llm_client
10
11
 
11
12
  try:
12
13
  from openai import OpenAI
@@ -23,16 +24,7 @@ class VectorWaveHealer:
23
24
  def __init__(self, model: str = "gpt-4-turbo"):
24
25
  self.settings = get_weaviate_settings()
25
26
  self.model = model
26
- self.client = self._init_openai()
27
-
28
- def _init_openai(self) -> Optional[OpenAI]:
29
- if OpenAI is None:
30
- logger.error("'openai' library is not installed.")
31
- return None
32
- if not self.settings.OPENAI_API_KEY:
33
- logger.error("OPENAI_API_KEY is not set.")
34
- return None
35
- return OpenAI(api_key=self.settings.OPENAI_API_KEY)
27
+ self.client = get_llm_client()
36
28
 
37
29
  def diagnose_and_heal(self, function_name: str, lookback_minutes: int = 60) -> str:
38
30
  """
@@ -85,15 +77,23 @@ class VectorWaveHealer:
85
77
  # 5. Call LLM
86
78
  print("🤖 Generating fix via LLM...")
87
79
  try:
88
- response = self.client.chat.completions.create(
80
+ response_text = self.client.create_chat_completion(
89
81
  model=self.model,
90
82
  messages=[
91
- {"role": "system", "content": "You are an expert Python debugger. Analyze the code and errors provided, then generate a fixed version of the code."},
83
+ {"role": "system", "content": "You are an expert Python debugger."
84
+ " Analyze the code and errors provided,"
85
+ " then generate a fixed version of the code."},
92
86
  {"role": "user", "content": prompt_context}
93
87
  ],
94
- temperature=0.1
88
+ temperature=0.1,
89
+ category="healer"
95
90
  )
96
- return response.choices[0].message.content
91
+
92
+ if response_text:
93
+ return response_text
94
+ else:
95
+ return "❌ LLM returned no response."
96
+
97
97
  except Exception as e:
98
98
  logger.error(f"LLM generation failed: {e}")
99
99
  return f"❌ Error occurred during LLM call: {e}"
@@ -6,6 +6,7 @@ import math
6
6
  import asyncio
7
7
  import inspect
8
8
  from typing import Any, Dict, List, Optional
9
+ from ..core.llm.factory import get_llm_client
9
10
 
10
11
  import weaviate.classes.query as wvc_query
11
12
 
@@ -29,13 +30,7 @@ class SemanticReplayer(VectorWaveReplayer):
29
30
 
30
31
  def __init__(self):
31
32
  super().__init__()
32
- self.openai_client = self._init_openai()
33
-
34
- def _init_openai(self) -> Optional[Any]:
35
- """Initialize OpenAI client if available and configured."""
36
- if OpenAI and self.settings.OPENAI_API_KEY:
37
- return OpenAI(api_key=self.settings.OPENAI_API_KEY)
38
- return None
33
+ self.openai_client = get_llm_client()
39
34
 
40
35
  def replay(self,
41
36
  function_full_name: str,
@@ -136,7 +131,7 @@ class SemanticReplayer(VectorWaveReplayer):
136
131
  "expected": expected_output,
137
132
  "actual": actual_output,
138
133
  "diff_html": diff_html, # UI visualization field
139
- "reason": match_reason # Semantic failure reason
134
+ "reason": match_reason # Semantic failure reason
140
135
  })
141
136
  logger.warning(f"UUID {obj.uuid}: FAILED ({match_reason})")
142
137
 
@@ -156,7 +151,8 @@ class SemanticReplayer(VectorWaveReplayer):
156
151
  "traceback": traceback.format_exc()
157
152
  })
158
153
 
159
- logger.info(f"Replay Finished. Passed: {results['passed']}, Failed: {results['failed']}, Updated: {results['updated']}")
154
+ logger.info(
155
+ f"Replay Finished. Passed: {results['passed']}, Failed: {results['failed']}, Updated: {results['updated']}")
160
156
  return results
161
157
 
162
158
  def _compare_results_semantic(self, expected: Any, actual: Any,
@@ -203,6 +199,7 @@ class SemanticReplayer(VectorWaveReplayer):
203
199
  norm1 = math.sqrt(sum(a * a for a in v1))
204
200
  norm2 = math.sqrt(sum(b * b for b in v2))
205
201
  return dot / (norm1 * norm2) if norm1 and norm2 else 0.0
202
+
206
203
  except Exception:
207
204
  return 0.0
208
205
 
@@ -220,13 +217,19 @@ class SemanticReplayer(VectorWaveReplayer):
220
217
  Respond JSON: {{"equivalent": true}} or {{"equivalent": false}}
221
218
  """
222
219
  try:
223
- res = self.openai_client.chat.completions.create(
220
+ # Refactored to use create_chat_completion
221
+ response_text = self.openai_client.create_chat_completion(
224
222
  model="gpt-4-turbo",
225
223
  messages=[{"role": "user", "content": prompt}],
226
224
  response_format={"type": "json_object"},
227
- temperature=0.0
225
+ temperature=0.0,
226
+ category="semantic_replay"
228
227
  )
229
- return json.loads(res.choices[0].message.content).get("equivalent", False)
228
+
229
+ if response_text:
230
+ return json.loads(response_text).get("equivalent", False)
231
+ return False
232
+
230
233
  except Exception as e:
231
234
  logger.error(f"LLM Eval failed: {e}")
232
- return False
235
+ return False
File without changes
@@ -1,5 +1,6 @@
1
1
  from .base import BaseVectorizer
2
2
  from typing import List
3
+ from ..core.llm.factory import get_llm_client
3
4
  import logging
4
5
 
5
6
  logger = logging.getLogger(__name__)
@@ -21,16 +22,25 @@ class OpenAIVectorizer(BaseVectorizer):
21
22
  if not api_key:
22
23
  raise ValueError("OpenAI API key is required for OpenAIVectorizer.")
23
24
 
24
- self.client = OpenAI(api_key=api_key)
25
+ self.client = get_llm_client()
25
26
  self.model = model
26
27
  logger.info("OpenAIVectorizer initialized with model '%s'.", self.model)
27
28
 
28
29
  def embed(self, text: str) -> List[float]:
29
30
  text = text.replace("\n", " ")
30
- response = self.client.embeddings.create(input=[text], model=self.model)
31
- return response.data[0].embedding
31
+ vector = self.client.create_embedding(
32
+ text=text,
33
+ model=self.model,
34
+ category="embedding"
35
+ )
36
+ return vector if vector else []
32
37
 
33
38
  def embed_batch(self, texts: List[str]) -> List[List[float]]:
34
- texts = [t.replace("\n", " ") for t in texts]
35
- response = self.client.embeddings.create(input=texts, model=self.model)
36
- return [d.embedding for d in response.data]
39
+ embeddings = []
40
+ for text in texts:
41
+ emb = self.embed(text)
42
+ if emb:
43
+ embeddings.append(emb)
44
+ else:
45
+ embeddings.append([])
46
+ return embeddings
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vectorwave
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: VectorWave: Seamless Auto-Vectorization Framework
5
5
  Author-email: junyeonggim <junyeonggim5@gmail.com>
6
6
  License-Expression: MIT
@@ -10,6 +10,8 @@ src/tests/batch/test_batch.py
10
10
  src/tests/core/__init__.py
11
11
  src/tests/core/test_decorator.py
12
12
  src/tests/core/test_semantic_caching.py
13
+ src/tests/core/llm/__init__.py
14
+ src/tests/core/llm/test_token_usage.py
13
15
  src/tests/database/__init__.py
14
16
  src/tests/database/test_archiver.py
15
17
  src/tests/database/test_db.py
@@ -43,6 +45,10 @@ src/vectorwave/core/__init__.py
43
45
  src/vectorwave/core/core.py
44
46
  src/vectorwave/core/decorator.py
45
47
  src/vectorwave/core/generator.py
48
+ src/vectorwave/core/llm/__init__.py
49
+ src/vectorwave/core/llm/base.py
50
+ src/vectorwave/core/llm/factory.py
51
+ src/vectorwave/core/llm/openai_client.py
46
52
  src/vectorwave/database/__init__.py
47
53
  src/vectorwave/database/archiver.py
48
54
  src/vectorwave/database/db.py
File without changes
File without changes
File without changes
File without changes
File without changes