noesium 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.
Files changed (86) hide show
  1. noesium/core/__init__.py +4 -0
  2. noesium/core/agent/__init__.py +14 -0
  3. noesium/core/agent/base.py +227 -0
  4. noesium/core/consts.py +6 -0
  5. noesium/core/goalith/conflict/conflict.py +104 -0
  6. noesium/core/goalith/conflict/detector.py +53 -0
  7. noesium/core/goalith/decomposer/__init__.py +6 -0
  8. noesium/core/goalith/decomposer/base.py +46 -0
  9. noesium/core/goalith/decomposer/callable_decomposer.py +65 -0
  10. noesium/core/goalith/decomposer/llm_decomposer.py +326 -0
  11. noesium/core/goalith/decomposer/prompts.py +140 -0
  12. noesium/core/goalith/decomposer/simple_decomposer.py +61 -0
  13. noesium/core/goalith/errors.py +22 -0
  14. noesium/core/goalith/goalgraph/graph.py +526 -0
  15. noesium/core/goalith/goalgraph/node.py +179 -0
  16. noesium/core/goalith/replanner/base.py +31 -0
  17. noesium/core/goalith/replanner/replanner.py +36 -0
  18. noesium/core/goalith/service.py +26 -0
  19. noesium/core/llm/__init__.py +154 -0
  20. noesium/core/llm/base.py +152 -0
  21. noesium/core/llm/litellm.py +528 -0
  22. noesium/core/llm/llamacpp.py +487 -0
  23. noesium/core/llm/message.py +184 -0
  24. noesium/core/llm/ollama.py +459 -0
  25. noesium/core/llm/openai.py +520 -0
  26. noesium/core/llm/openrouter.py +89 -0
  27. noesium/core/llm/prompt.py +551 -0
  28. noesium/core/memory/__init__.py +11 -0
  29. noesium/core/memory/base.py +464 -0
  30. noesium/core/memory/memu/__init__.py +24 -0
  31. noesium/core/memory/memu/config/__init__.py +26 -0
  32. noesium/core/memory/memu/config/activity/config.py +46 -0
  33. noesium/core/memory/memu/config/event/config.py +46 -0
  34. noesium/core/memory/memu/config/markdown_config.py +241 -0
  35. noesium/core/memory/memu/config/profile/config.py +48 -0
  36. noesium/core/memory/memu/llm_adapter.py +129 -0
  37. noesium/core/memory/memu/memory/__init__.py +31 -0
  38. noesium/core/memory/memu/memory/actions/__init__.py +40 -0
  39. noesium/core/memory/memu/memory/actions/add_activity_memory.py +299 -0
  40. noesium/core/memory/memu/memory/actions/base_action.py +342 -0
  41. noesium/core/memory/memu/memory/actions/cluster_memories.py +262 -0
  42. noesium/core/memory/memu/memory/actions/generate_suggestions.py +198 -0
  43. noesium/core/memory/memu/memory/actions/get_available_categories.py +66 -0
  44. noesium/core/memory/memu/memory/actions/link_related_memories.py +515 -0
  45. noesium/core/memory/memu/memory/actions/run_theory_of_mind.py +254 -0
  46. noesium/core/memory/memu/memory/actions/update_memory_with_suggestions.py +514 -0
  47. noesium/core/memory/memu/memory/embeddings.py +130 -0
  48. noesium/core/memory/memu/memory/file_manager.py +306 -0
  49. noesium/core/memory/memu/memory/memory_agent.py +578 -0
  50. noesium/core/memory/memu/memory/recall_agent.py +376 -0
  51. noesium/core/memory/memu/memory_store.py +628 -0
  52. noesium/core/memory/models.py +149 -0
  53. noesium/core/msgbus/__init__.py +12 -0
  54. noesium/core/msgbus/base.py +395 -0
  55. noesium/core/orchestrix/__init__.py +0 -0
  56. noesium/core/py.typed +0 -0
  57. noesium/core/routing/__init__.py +20 -0
  58. noesium/core/routing/base.py +66 -0
  59. noesium/core/routing/router.py +241 -0
  60. noesium/core/routing/strategies/__init__.py +9 -0
  61. noesium/core/routing/strategies/dynamic_complexity.py +361 -0
  62. noesium/core/routing/strategies/self_assessment.py +147 -0
  63. noesium/core/routing/types.py +38 -0
  64. noesium/core/toolify/__init__.py +39 -0
  65. noesium/core/toolify/base.py +360 -0
  66. noesium/core/toolify/config.py +138 -0
  67. noesium/core/toolify/mcp_integration.py +275 -0
  68. noesium/core/toolify/registry.py +214 -0
  69. noesium/core/toolify/toolkits/__init__.py +1 -0
  70. noesium/core/tracing/__init__.py +37 -0
  71. noesium/core/tracing/langgraph_hooks.py +308 -0
  72. noesium/core/tracing/opik_tracing.py +144 -0
  73. noesium/core/tracing/token_tracker.py +166 -0
  74. noesium/core/utils/__init__.py +10 -0
  75. noesium/core/utils/logging.py +172 -0
  76. noesium/core/utils/statistics.py +12 -0
  77. noesium/core/utils/typing.py +17 -0
  78. noesium/core/vector_store/__init__.py +79 -0
  79. noesium/core/vector_store/base.py +94 -0
  80. noesium/core/vector_store/pgvector.py +304 -0
  81. noesium/core/vector_store/weaviate.py +383 -0
  82. noesium-0.1.0.dist-info/METADATA +525 -0
  83. noesium-0.1.0.dist-info/RECORD +86 -0
  84. noesium-0.1.0.dist-info/WHEEL +5 -0
  85. noesium-0.1.0.dist-info/licenses/LICENSE +21 -0
  86. noesium-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,383 @@
1
+ import logging
2
+ import uuid
3
+ from typing import Any, Dict, List, Mapping, Optional
4
+
5
+ try:
6
+ import weaviate
7
+ except ImportError:
8
+ raise ImportError(
9
+ "The 'weaviate' library is required. Please install it using 'pip install weaviate-client weaviate'."
10
+ )
11
+
12
+ import weaviate.classes.config as wvcc
13
+ from weaviate.classes.init import Auth
14
+ from weaviate.classes.query import Filter, MetadataQuery
15
+ from weaviate.util import get_valid_uuid
16
+
17
+ from .base import BaseVectorStore, OutputData
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class WeaviateVectorStore(BaseVectorStore):
23
+ def __init__(
24
+ self,
25
+ collection_name: str,
26
+ embedding_model_dims: int = 768,
27
+ cluster_url: str = None,
28
+ auth_client_secret: str = None,
29
+ additional_headers: dict = None,
30
+ ):
31
+ """
32
+ Initialize the Weaviate vector store.
33
+
34
+ Args:
35
+ collection_name (str): Name of the collection/class in Weaviate.
36
+ embedding_model_dims (int, optional): Dimensions of the embedding model.
37
+ client (WeaviateClient, optional): Existing Weaviate client instance. Defaults to None.
38
+ cluster_url (str, optional): URL for Weaviate server. Defaults to None.
39
+ auth_config (dict, optional): Authentication configuration for Weaviate. Defaults to None.
40
+ additional_headers (dict, optional): Additional headers for requests. Defaults to None.
41
+ """
42
+ super().__init__(embedding_model_dims)
43
+
44
+ if "localhost" in cluster_url:
45
+ self.client = weaviate.connect_to_local(headers=additional_headers)
46
+ else:
47
+ self.client = weaviate.connect_to_weaviate_cloud(
48
+ cluster_url=cluster_url,
49
+ auth_credentials=Auth.api_key(auth_client_secret),
50
+ headers=additional_headers,
51
+ )
52
+
53
+ # Weaviate capitalizes the first letter of collection names, so we need to handle this
54
+ self.collection_name = collection_name
55
+ self.weaviate_collection_name = collection_name.capitalize()
56
+ self.create_collection(embedding_model_dims)
57
+
58
+ def _parse_output(self, data: Dict) -> List[OutputData]:
59
+ """
60
+ Parse the output data.
61
+
62
+ Args:
63
+ data (Dict): Output data.
64
+
65
+ Returns:
66
+ List[OutputData]: Parsed output data.
67
+ """
68
+ keys = ["ids", "distances", "metadatas"]
69
+ values = []
70
+
71
+ for key in keys:
72
+ value = data.get(key, [])
73
+ if isinstance(value, list) and value and isinstance(value[0], list):
74
+ value = value[0]
75
+ values.append(value)
76
+
77
+ ids, distances, metadatas = values
78
+ max_length = max(len(v) for v in values if isinstance(v, list) and v is not None)
79
+
80
+ result = []
81
+ for i in range(max_length):
82
+ entry = OutputData(
83
+ id=ids[i] if isinstance(ids, list) and ids and i < len(ids) else None,
84
+ score=(distances[i] if isinstance(distances, list) and distances and i < len(distances) else None),
85
+ payload=(metadatas[i] if isinstance(metadatas, list) and metadatas and i < len(metadatas) else None),
86
+ )
87
+ result.append(entry)
88
+
89
+ return result
90
+
91
+ def create_collection(self, vector_size: int, distance: str = "cosine") -> None:
92
+ """
93
+ Create a new collection with the specified schema.
94
+
95
+ Args:
96
+ vector_size (int): Size of the vectors to be stored.
97
+ distance (str, optional): Distance metric for vector similarity. Defaults to "cosine".
98
+ """
99
+ if self.client.collections.exists(self.weaviate_collection_name):
100
+ logging.debug(f"Collection {self.collection_name} already exists. Skipping creation.")
101
+ return
102
+
103
+ properties = [
104
+ wvcc.Property(name="ids", data_type=wvcc.DataType.TEXT),
105
+ wvcc.Property(name="hash", data_type=wvcc.DataType.TEXT),
106
+ wvcc.Property(
107
+ name="metadata",
108
+ data_type=wvcc.DataType.TEXT,
109
+ description="Additional metadata",
110
+ ),
111
+ wvcc.Property(name="data", data_type=wvcc.DataType.TEXT),
112
+ wvcc.Property(name="created_at", data_type=wvcc.DataType.TEXT),
113
+ wvcc.Property(
114
+ name="category",
115
+ data_type=wvcc.DataType.TEXT,
116
+ index_filterable=True,
117
+ index_searchable=False, # Disable text search for exact matching
118
+ tokenization=wvcc.Tokenization.FIELD, # Use field tokenization for exact matching
119
+ ),
120
+ wvcc.Property(name="updated_at", data_type=wvcc.DataType.TEXT),
121
+ ]
122
+
123
+ vector_config = wvcc.Configure.Vectors.self_provided()
124
+
125
+ self.client.collections.create(
126
+ self.weaviate_collection_name,
127
+ vector_config=vector_config,
128
+ properties=properties,
129
+ )
130
+
131
+ def insert(
132
+ self,
133
+ vectors: List[List[float]],
134
+ payloads: Optional[List[Dict[str, Any]]] = None,
135
+ ids: Optional[List[str]] = None,
136
+ ) -> None:
137
+ """
138
+ Insert vectors into a collection.
139
+
140
+ Args:
141
+ vectors (list): List of vectors to insert.
142
+ payloads (list, optional): List of payloads corresponding to vectors. Defaults to None.
143
+ ids (list, optional): List of IDs corresponding to vectors. Defaults to None.
144
+ """
145
+ # Validate vector dimensions
146
+ self._validate_vector_dimensions(vectors)
147
+
148
+ logger.info(f"Inserting {len(vectors)} vectors into collection {self.collection_name}")
149
+ with self.client.batch.fixed_size(batch_size=100) as batch:
150
+ for idx, vector in enumerate(vectors):
151
+ object_id = ids[idx] if ids and idx < len(ids) else str(uuid.uuid4())
152
+ object_id = get_valid_uuid(object_id)
153
+
154
+ data_object = payloads[idx] if payloads and idx < len(payloads) else {}
155
+
156
+ # Ensure 'id' is not included in properties (it's used as the Weaviate object ID)
157
+ if "ids" in data_object:
158
+ del data_object["ids"]
159
+
160
+ batch.add_object(
161
+ collection=self.weaviate_collection_name,
162
+ properties=data_object,
163
+ uuid=object_id,
164
+ vector=vector,
165
+ )
166
+
167
+ def search(
168
+ self,
169
+ query: str,
170
+ vectors: List[float],
171
+ limit: int = 5,
172
+ filters: Optional[Dict[str, Any]] = None,
173
+ ) -> List[OutputData]:
174
+ """
175
+ Search for similar vectors.
176
+ """
177
+ logger.info(f"Searching in collection {self.weaviate_collection_name} with query: {query}")
178
+ logger.info(f"Vector dimensions: {len(vectors)}, limit: {limit}")
179
+
180
+ collection = self.client.collections.get(str(self.weaviate_collection_name))
181
+ filter_conditions = []
182
+ if filters:
183
+ for key, value in filters.items():
184
+ if value:
185
+ filter_conditions.append(Filter.by_property(key).equal(value))
186
+ combined_filter = Filter.all_of(filter_conditions) if filter_conditions else None
187
+
188
+ try:
189
+ # Try vector similarity search first
190
+ response = collection.query.near_vector(
191
+ near_vector=vectors,
192
+ limit=limit,
193
+ filters=combined_filter,
194
+ distance=0.99, # Use a very permissive distance threshold
195
+ # Return all properties to support custom fields
196
+ return_metadata=MetadataQuery(score=True),
197
+ )
198
+
199
+ # If no results from vector search, fall back to filtered object retrieval
200
+ if len(response.objects) == 0:
201
+ response = collection.query.fetch_objects(limit=limit, filters=combined_filter)
202
+
203
+ except Exception as e:
204
+ logger.error(f"Search failed with error: {e}")
205
+ raise
206
+
207
+ results = []
208
+ for obj in response.objects:
209
+ payload = obj.properties.copy()
210
+ payload["id"] = str(obj.uuid).split("'")[0] # Include the id in the payload
211
+ results.append(
212
+ OutputData(
213
+ id=str(obj.uuid),
214
+ score=(
215
+ 1 if obj.metadata.distance is None else 1 - obj.metadata.distance
216
+ ), # Convert distance to score
217
+ payload=payload,
218
+ )
219
+ )
220
+ return results
221
+
222
+ def delete(self, vector_id: str) -> None:
223
+ """
224
+ Delete a vector by ID.
225
+
226
+ Args:
227
+ vector_id: ID of the vector to delete.
228
+ """
229
+ collection = self.client.collections.get(str(self.weaviate_collection_name))
230
+ collection.data.delete_by_id(vector_id)
231
+
232
+ def update(
233
+ self, vector_id: str, vector: Optional[List[float]] = None, payload: Optional[Dict[str, Any]] = None
234
+ ) -> None:
235
+ """
236
+ Update a vector and its payload.
237
+
238
+ Args:
239
+ vector_id: ID of the vector to update.
240
+ vector (list, optional): Updated vector. Defaults to None.
241
+ payload (dict, optional): Updated payload. Defaults to None.
242
+ """
243
+ collection = self.client.collections.get(str(self.weaviate_collection_name))
244
+
245
+ if payload:
246
+ collection.data.update(uuid=vector_id, properties=payload)
247
+
248
+ if vector:
249
+ existing_data = self.get(vector_id)
250
+ if existing_data:
251
+ existing_data = dict(existing_data)
252
+ if "id" in existing_data:
253
+ del existing_data["id"]
254
+ existing_payload: Mapping[str, str] = existing_data
255
+ collection.data.update(uuid=vector_id, properties=existing_payload, vector=vector)
256
+
257
+ def close(self) -> None:
258
+ """
259
+ Close the Weaviate client connection.
260
+ """
261
+ if hasattr(self, "client") and self.client:
262
+ self.client.close()
263
+
264
+ def get(self, vector_id: str) -> Optional[OutputData]:
265
+ """
266
+ Retrieve a vector by ID.
267
+
268
+ Args:
269
+ vector_id: ID of the vector to retrieve.
270
+
271
+ Returns:
272
+ dict: Retrieved vector and metadata.
273
+ """
274
+ vector_id = get_valid_uuid(vector_id)
275
+ collection = self.client.collections.get(str(self.weaviate_collection_name))
276
+
277
+ response = collection.query.fetch_object_by_id(
278
+ uuid=vector_id,
279
+ )
280
+ if response is None:
281
+ return None
282
+
283
+ payload = response.properties.copy()
284
+ payload["id"] = str(response.uuid).split("'")[0]
285
+ results = OutputData(
286
+ id=str(response.uuid).split("'")[0],
287
+ score=1.0,
288
+ payload=payload,
289
+ )
290
+ return results
291
+
292
+ def list_collections(self) -> List[str]:
293
+ """
294
+ List all collections.
295
+
296
+ Returns:
297
+ list: List of collection names.
298
+ """
299
+ collections = self.client.collections.list_all()
300
+ logger.debug(f"collections: {collections}")
301
+ print(f"collections: {collections}")
302
+
303
+ # collections.list_all() returns a dict where keys are collection names
304
+ if isinstance(collections, dict):
305
+ collection_names = list(collections.keys())
306
+ else:
307
+ # Handle case where it returns a list of objects with .name
308
+ collection_names = [col.name if hasattr(col, "name") else str(col) for col in collections]
309
+
310
+ # Return simple list of collection names for test compatibility
311
+ # Convert back to original naming convention (uncapitalized)
312
+ original_names = []
313
+ for name in collection_names:
314
+ # If this matches our weaviate collection name, return the original collection name
315
+ if name == self.weaviate_collection_name:
316
+ original_names.append(self.collection_name)
317
+ else:
318
+ # For other collections, convert first letter back to lowercase
319
+ original_names.append(name[0].lower() + name[1:] if name else name)
320
+ return original_names
321
+
322
+ def delete_collection(self) -> None:
323
+ """Delete a collection."""
324
+ self.client.collections.delete(self.weaviate_collection_name)
325
+
326
+ def collection_info(self) -> Dict[str, Any]:
327
+ """
328
+ Get information about a collection.
329
+
330
+ Returns:
331
+ dict: Collection information.
332
+ """
333
+ try:
334
+ collection = self.client.collections.get(self.weaviate_collection_name)
335
+ # Get collection config/schema information
336
+ config = collection.config.get()
337
+ return {"name": self.collection_name, "config": str(config)}
338
+ except Exception as e:
339
+ logger.error(f"Error getting collection info: {e}")
340
+ return {"name": self.collection_name, "error": str(e)}
341
+
342
+ def list(self, filters: Optional[Dict[str, Any]] = None, limit: Optional[int] = None) -> List[OutputData]:
343
+ """
344
+ List all vectors in a collection.
345
+ """
346
+ collection = self.client.collections.get(self.weaviate_collection_name)
347
+ filter_conditions = []
348
+ if filters:
349
+ for key, value in filters.items():
350
+ if value:
351
+ filter_conditions.append(Filter.by_property(key).equal(value))
352
+ combined_filter = Filter.all_of(filter_conditions) if filter_conditions else None
353
+
354
+ try:
355
+ # Use fetch_objects with filters when filters are applied
356
+ if combined_filter:
357
+ response = collection.query.fetch_objects(
358
+ limit=limit,
359
+ filters=combined_filter,
360
+ # Return all properties to support custom fields
361
+ )
362
+ else:
363
+ # No filters, just fetch all objects
364
+ response = collection.query.fetch_objects(
365
+ limit=limit,
366
+ # Return all properties to support custom fields
367
+ )
368
+ except Exception as e:
369
+ logger.error(f"List failed with error: {e}")
370
+ raise
371
+
372
+ results = []
373
+ for obj in response.objects:
374
+ payload = obj.properties.copy()
375
+ payload["id"] = str(obj.uuid).split("'")[0]
376
+ results.append(OutputData(id=str(obj.uuid).split("'")[0], score=1.0, payload=payload))
377
+ return results
378
+
379
+ def reset(self) -> None:
380
+ """Reset the index by deleting and recreating it."""
381
+ logger.warning(f"Resetting index {self.collection_name}...")
382
+ self.delete_collection()
383
+ self.create_collection(self.embedding_model_dims)