praisonaiagents 0.0.141__py3-none-any.whl → 0.0.143__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.
@@ -0,0 +1,610 @@
1
+ """Tools for working with MongoDB databases and Atlas Vector Search.
2
+
3
+ Usage:
4
+ from praisonaiagents.tools import mongodb_tools
5
+ result = mongodb_tools.insert_document("my_collection", {"name": "test", "value": 42})
6
+
7
+ or
8
+ from praisonaiagents.tools import connect_mongodb, insert_document, vector_search
9
+ client = connect_mongodb("mongodb://localhost:27017/")
10
+ result = insert_document(client, "my_collection", {"name": "test"})
11
+ """
12
+
13
+ import logging
14
+ from typing import List, Dict, Any, Optional, Union, TYPE_CHECKING
15
+ from importlib import util
16
+ from datetime import datetime
17
+
18
+ if TYPE_CHECKING:
19
+ import pymongo
20
+ from pymongo import MongoClient
21
+ from pymongo.collection import Collection
22
+
23
+ class MongoDBTools:
24
+ """Tools for working with MongoDB databases and Atlas Vector Search."""
25
+
26
+ def __init__(self, connection_string: str = "mongodb://localhost:27017/", database_name: str = "praisonai"):
27
+ """Initialize MongoDBTools.
28
+
29
+ Args:
30
+ connection_string: MongoDB connection string
31
+ database_name: Name of the database to use
32
+ """
33
+ self.connection_string = connection_string
34
+ self.database_name = database_name
35
+ self._client = None
36
+ self._db = None
37
+
38
+ def _get_pymongo(self) -> Optional['pymongo']:
39
+ """Get pymongo module, with helpful error if not installed"""
40
+ if util.find_spec('pymongo') is None:
41
+ error_msg = "pymongo package is not available. Please install it using: pip install 'praisonaiagents[mongodb]'"
42
+ logging.error(error_msg)
43
+ return None
44
+ import pymongo
45
+ return pymongo
46
+
47
+ def _get_motor(self) -> Optional[Any]:
48
+ """Get motor module for async operations"""
49
+ if util.find_spec('motor') is None:
50
+ error_msg = "motor package is not available. Please install it using: pip install 'praisonaiagents[mongodb]'"
51
+ logging.error(error_msg)
52
+ return None
53
+ import motor.motor_asyncio
54
+ return motor.motor_asyncio
55
+
56
+ def _get_client(self) -> Optional['MongoClient']:
57
+ """Get or create MongoDB client"""
58
+ if self._client is None:
59
+ pymongo = self._get_pymongo()
60
+ if pymongo is None:
61
+ return None
62
+ try:
63
+ self._client = pymongo.MongoClient(
64
+ self.connection_string,
65
+ maxPoolSize=50,
66
+ minPoolSize=10,
67
+ maxIdleTimeMS=30000,
68
+ serverSelectionTimeoutMS=5000,
69
+ retryWrites=True,
70
+ retryReads=True
71
+ )
72
+ # Test connection
73
+ self._client.admin.command('ping')
74
+ self._db = self._client[self.database_name]
75
+ except Exception as e:
76
+ error_msg = f"Error connecting to MongoDB: {str(e)}"
77
+ logging.error(error_msg)
78
+ return None
79
+ return self._client
80
+
81
+ def _get_database(self):
82
+ """Get database instance"""
83
+ if self._db is None:
84
+ client = self._get_client()
85
+ if client is None:
86
+ return None
87
+ self._db = client[self.database_name]
88
+ return self._db
89
+
90
+ def _get_collection(self, collection_name: str) -> Optional['Collection']:
91
+ """Get collection instance"""
92
+ db = self._get_database()
93
+ if db is None:
94
+ return None
95
+ return db[collection_name]
96
+
97
+ def insert_document(
98
+ self,
99
+ collection_name: str,
100
+ document: Dict[str, Any],
101
+ metadata: Optional[Dict[str, Any]] = None
102
+ ) -> Dict[str, Any]:
103
+ """Insert a document into a collection.
104
+
105
+ Args:
106
+ collection_name: Name of the collection
107
+ document: Document to insert
108
+ metadata: Optional metadata to include
109
+
110
+ Returns:
111
+ Result dict with success status and inserted_id
112
+ """
113
+ try:
114
+ collection = self._get_collection(collection_name)
115
+ if collection is None:
116
+ return {"error": "Could not connect to MongoDB"}
117
+
118
+ # Add metadata and timestamp
119
+ doc_to_insert = document.copy()
120
+ if metadata:
121
+ doc_to_insert.update(metadata)
122
+ doc_to_insert["_created_at"] = datetime.utcnow()
123
+
124
+ result = collection.insert_one(doc_to_insert)
125
+ return {
126
+ "success": True,
127
+ "inserted_id": str(result.inserted_id),
128
+ "message": "Document inserted successfully"
129
+ }
130
+
131
+ except Exception as e:
132
+ error_msg = f"Error inserting document: {str(e)}"
133
+ logging.error(error_msg)
134
+ return {"error": error_msg}
135
+
136
+ def insert_documents(
137
+ self,
138
+ collection_name: str,
139
+ documents: List[Dict[str, Any]],
140
+ metadata: Optional[Dict[str, Any]] = None
141
+ ) -> Dict[str, Any]:
142
+ """Insert multiple documents into a collection.
143
+
144
+ Args:
145
+ collection_name: Name of the collection
146
+ documents: List of documents to insert
147
+ metadata: Optional metadata to include in all documents
148
+
149
+ Returns:
150
+ Result dict with success status and inserted_ids
151
+ """
152
+ try:
153
+ collection = self._get_collection(collection_name)
154
+ if collection is None:
155
+ return {"error": "Could not connect to MongoDB"}
156
+
157
+ # Add metadata and timestamp to all documents
158
+ docs_to_insert = []
159
+ for doc in documents:
160
+ doc_copy = doc.copy()
161
+ if metadata:
162
+ doc_copy.update(metadata)
163
+ doc_copy["_created_at"] = datetime.utcnow()
164
+ docs_to_insert.append(doc_copy)
165
+
166
+ result = collection.insert_many(docs_to_insert)
167
+ return {
168
+ "success": True,
169
+ "inserted_ids": [str(oid) for oid in result.inserted_ids],
170
+ "count": len(result.inserted_ids),
171
+ "message": f"Successfully inserted {len(result.inserted_ids)} documents"
172
+ }
173
+
174
+ except Exception as e:
175
+ error_msg = f"Error inserting documents: {str(e)}"
176
+ logging.error(error_msg)
177
+ return {"error": error_msg}
178
+
179
+ def find_documents(
180
+ self,
181
+ collection_name: str,
182
+ query: Dict[str, Any] = None,
183
+ limit: int = 10,
184
+ sort: Optional[List[tuple]] = None,
185
+ projection: Optional[Dict[str, int]] = None
186
+ ) -> Union[List[Dict[str, Any]], Dict[str, str]]:
187
+ """Find documents in a collection.
188
+
189
+ Args:
190
+ collection_name: Name of the collection
191
+ query: Query filter (default: empty dict for all documents)
192
+ limit: Maximum number of documents to return
193
+ sort: Sort specification as list of (field, direction) tuples
194
+ projection: Fields to include/exclude
195
+
196
+ Returns:
197
+ List of documents or error dict
198
+ """
199
+ try:
200
+ collection = self._get_collection(collection_name)
201
+ if collection is None:
202
+ return {"error": "Could not connect to MongoDB"}
203
+
204
+ query = query or {}
205
+ cursor = collection.find(query, projection)
206
+
207
+ if sort:
208
+ cursor = cursor.sort(sort)
209
+
210
+ cursor = cursor.limit(limit)
211
+
212
+ # Convert ObjectIds to strings for JSON serialization
213
+ results = []
214
+ for doc in cursor:
215
+ doc["_id"] = str(doc["_id"])
216
+ results.append(doc)
217
+
218
+ return results
219
+
220
+ except Exception as e:
221
+ error_msg = f"Error finding documents: {str(e)}"
222
+ logging.error(error_msg)
223
+ return {"error": error_msg}
224
+
225
+ def update_document(
226
+ self,
227
+ collection_name: str,
228
+ query: Dict[str, Any],
229
+ update: Dict[str, Any],
230
+ upsert: bool = False
231
+ ) -> Dict[str, Any]:
232
+ """Update a document in a collection.
233
+
234
+ Args:
235
+ collection_name: Name of the collection
236
+ query: Query to find the document
237
+ update: Update operations
238
+ upsert: Whether to insert if document doesn't exist
239
+
240
+ Returns:
241
+ Result dict with success status
242
+ """
243
+ try:
244
+ collection = self._get_collection(collection_name)
245
+ if collection is None:
246
+ return {"error": "Could not connect to MongoDB"}
247
+
248
+ # Add timestamp to update
249
+ if "$set" not in update:
250
+ update["$set"] = {}
251
+ update["$set"]["_updated_at"] = datetime.utcnow()
252
+
253
+ result = collection.update_one(query, update, upsert=upsert)
254
+ return {
255
+ "success": True,
256
+ "matched_count": result.matched_count,
257
+ "modified_count": result.modified_count,
258
+ "upserted_id": str(result.upserted_id) if result.upserted_id else None,
259
+ "message": "Document updated successfully"
260
+ }
261
+
262
+ except Exception as e:
263
+ error_msg = f"Error updating document: {str(e)}"
264
+ logging.error(error_msg)
265
+ return {"error": error_msg}
266
+
267
+ def delete_document(
268
+ self,
269
+ collection_name: str,
270
+ query: Dict[str, Any]
271
+ ) -> Dict[str, Any]:
272
+ """Delete a document from a collection.
273
+
274
+ Args:
275
+ collection_name: Name of the collection
276
+ query: Query to find the document to delete
277
+
278
+ Returns:
279
+ Result dict with success status
280
+ """
281
+ try:
282
+ collection = self._get_collection(collection_name)
283
+ if collection is None:
284
+ return {"error": "Could not connect to MongoDB"}
285
+
286
+ result = collection.delete_one(query)
287
+ return {
288
+ "success": True,
289
+ "deleted_count": result.deleted_count,
290
+ "message": f"Deleted {result.deleted_count} document(s)"
291
+ }
292
+
293
+ except Exception as e:
294
+ error_msg = f"Error deleting document: {str(e)}"
295
+ logging.error(error_msg)
296
+ return {"error": error_msg}
297
+
298
+ def create_vector_index(
299
+ self,
300
+ collection_name: str,
301
+ vector_field: str = "embedding",
302
+ dimensions: int = 1536,
303
+ similarity: str = "cosine",
304
+ index_name: str = "vector_index"
305
+ ) -> Dict[str, Any]:
306
+ """Create a vector search index for Atlas Vector Search.
307
+
308
+ Args:
309
+ collection_name: Name of the collection
310
+ vector_field: Name of the field containing vectors
311
+ dimensions: Number of dimensions in the vectors
312
+ similarity: Similarity metric ('cosine', 'euclidean', 'dotProduct')
313
+ index_name: Name of the index
314
+
315
+ Returns:
316
+ Result dict with success status
317
+ """
318
+ try:
319
+ collection = self._get_collection(collection_name)
320
+ if collection is None:
321
+ return {"error": "Could not connect to MongoDB"}
322
+
323
+ # Create Atlas Vector Search index
324
+ index_definition = {
325
+ "mappings": {
326
+ "dynamic": True,
327
+ "fields": {
328
+ vector_field: {
329
+ "type": "knnVector",
330
+ "dimensions": dimensions,
331
+ "similarity": similarity
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ try:
338
+ collection.create_search_index(index_definition, index_name)
339
+ return {
340
+ "success": True,
341
+ "message": f"Vector search index '{index_name}' created successfully"
342
+ }
343
+ except Exception as e:
344
+ if "already exists" in str(e).lower():
345
+ return {
346
+ "success": True,
347
+ "message": f"Vector search index '{index_name}' already exists"
348
+ }
349
+ raise
350
+
351
+ except Exception as e:
352
+ error_msg = f"Error creating vector index: {str(e)}"
353
+ logging.error(error_msg)
354
+ return {"error": error_msg}
355
+
356
+ def vector_search(
357
+ self,
358
+ collection_name: str,
359
+ query_vector: List[float],
360
+ vector_field: str = "embedding",
361
+ limit: int = 10,
362
+ num_candidates: int = 100,
363
+ score_threshold: float = 0.0,
364
+ filter_query: Optional[Dict[str, Any]] = None,
365
+ index_name: str = "vector_index"
366
+ ) -> Union[List[Dict[str, Any]], Dict[str, str]]:
367
+ """Perform vector similarity search using Atlas Vector Search.
368
+
369
+ Args:
370
+ collection_name: Name of the collection
371
+ query_vector: Vector to search for
372
+ vector_field: Name of the field containing vectors
373
+ limit: Maximum number of results to return
374
+ num_candidates: Number of candidates to consider
375
+ score_threshold: Minimum similarity score
376
+ filter_query: Optional additional filter
377
+ index_name: Name of the vector index to use
378
+
379
+ Returns:
380
+ List of search results or error dict
381
+ """
382
+ try:
383
+ collection = self._get_collection(collection_name)
384
+ if collection is None:
385
+ return {"error": "Could not connect to MongoDB"}
386
+
387
+ # Build aggregation pipeline for vector search
388
+ pipeline = [
389
+ {
390
+ "$vectorSearch": {
391
+ "index": index_name,
392
+ "path": vector_field,
393
+ "queryVector": query_vector,
394
+ "numCandidates": num_candidates,
395
+ "limit": limit
396
+ }
397
+ },
398
+ {
399
+ "$addFields": {
400
+ "score": {"$meta": "vectorSearchScore"}
401
+ }
402
+ }
403
+ ]
404
+
405
+ # Add score threshold filter if specified
406
+ if score_threshold > 0:
407
+ pipeline.append({
408
+ "$match": {
409
+ "score": {"$gte": score_threshold}
410
+ }
411
+ })
412
+
413
+ # Add additional filter if specified
414
+ if filter_query:
415
+ pipeline.append({
416
+ "$match": filter_query
417
+ })
418
+
419
+ # Execute search
420
+ results = []
421
+ for doc in collection.aggregate(pipeline):
422
+ doc["_id"] = str(doc["_id"])
423
+ results.append(doc)
424
+
425
+ return results
426
+
427
+ except Exception as e:
428
+ error_msg = f"Error performing vector search: {str(e)}"
429
+ logging.error(error_msg)
430
+ return {"error": error_msg}
431
+
432
+ def store_with_embedding(
433
+ self,
434
+ collection_name: str,
435
+ text: str,
436
+ embedding: List[float],
437
+ metadata: Optional[Dict[str, Any]] = None
438
+ ) -> Dict[str, Any]:
439
+ """Store text with its embedding vector.
440
+
441
+ Args:
442
+ collection_name: Name of the collection
443
+ text: Text content
444
+ embedding: Vector embedding
445
+ metadata: Optional metadata
446
+
447
+ Returns:
448
+ Result dict with success status
449
+ """
450
+ document = {
451
+ "text": text,
452
+ "embedding": embedding,
453
+ "metadata": metadata or {},
454
+ "created_at": datetime.utcnow()
455
+ }
456
+
457
+ return self.insert_document(collection_name, document)
458
+
459
+ def text_search(
460
+ self,
461
+ collection_name: str,
462
+ query: str,
463
+ limit: int = 10,
464
+ text_field: str = "text"
465
+ ) -> Union[List[Dict[str, Any]], Dict[str, str]]:
466
+ """Perform text search using MongoDB text indexes.
467
+
468
+ Args:
469
+ collection_name: Name of the collection
470
+ query: Search query
471
+ limit: Maximum number of results
472
+ text_field: Field to search in
473
+
474
+ Returns:
475
+ List of search results or error dict
476
+ """
477
+ try:
478
+ collection = self._get_collection(collection_name)
479
+ if collection is None:
480
+ return {"error": "Could not connect to MongoDB"}
481
+
482
+ # Create text index if it doesn't exist
483
+ try:
484
+ collection.create_index([(text_field, "text")])
485
+ except Exception:
486
+ pass # Index might already exist
487
+
488
+ # Perform text search
489
+ results = []
490
+ for doc in collection.find(
491
+ {"$text": {"$search": query}},
492
+ {"score": {"$meta": "textScore"}}
493
+ ).sort([("score", {"$meta": "textScore"})]).limit(limit):
494
+ doc["_id"] = str(doc["_id"])
495
+ results.append(doc)
496
+
497
+ return results
498
+
499
+ except Exception as e:
500
+ error_msg = f"Error performing text search: {str(e)}"
501
+ logging.error(error_msg)
502
+ return {"error": error_msg}
503
+
504
+ def get_stats(self, collection_name: str) -> Dict[str, Any]:
505
+ """Get collection statistics.
506
+
507
+ Args:
508
+ collection_name: Name of the collection
509
+
510
+ Returns:
511
+ Dict with collection statistics
512
+ """
513
+ try:
514
+ collection = self._get_collection(collection_name)
515
+ if collection is None:
516
+ return {"error": "Could not connect to MongoDB"}
517
+
518
+ stats = collection.estimated_document_count()
519
+ return {
520
+ "success": True,
521
+ "collection_name": collection_name,
522
+ "document_count": stats,
523
+ "database_name": self.database_name
524
+ }
525
+
526
+ except Exception as e:
527
+ error_msg = f"Error getting collection stats: {str(e)}"
528
+ logging.error(error_msg)
529
+ return {"error": error_msg}
530
+
531
+ def close(self):
532
+ """Close MongoDB connection."""
533
+ if self._client:
534
+ self._client.close()
535
+ self._client = None
536
+ self._db = None
537
+
538
+ # Create default instance for direct function access
539
+ _mongodb_tools = MongoDBTools()
540
+
541
+ # Export functions for direct use
542
+ insert_document = _mongodb_tools.insert_document
543
+ insert_documents = _mongodb_tools.insert_documents
544
+ find_documents = _mongodb_tools.find_documents
545
+ update_document = _mongodb_tools.update_document
546
+ delete_document = _mongodb_tools.delete_document
547
+ create_vector_index = _mongodb_tools.create_vector_index
548
+ vector_search = _mongodb_tools.vector_search
549
+ store_with_embedding = _mongodb_tools.store_with_embedding
550
+ text_search = _mongodb_tools.text_search
551
+ get_stats = _mongodb_tools.get_stats
552
+
553
+ def connect_mongodb(connection_string: str, database_name: str = "praisonai") -> MongoDBTools:
554
+ """Create a new MongoDB connection.
555
+
556
+ Args:
557
+ connection_string: MongoDB connection string
558
+ database_name: Database name to use
559
+
560
+ Returns:
561
+ MongoDBTools instance
562
+ """
563
+ return MongoDBTools(connection_string, database_name)
564
+
565
+ if __name__ == "__main__":
566
+ print("\n==================================================")
567
+ print("MongoDB Tools Demonstration")
568
+ print("==================================================\n")
569
+
570
+ # Test basic operations
571
+ print("1. Testing Document Operations")
572
+ print("------------------------------")
573
+
574
+ # Insert a document
575
+ result = insert_document("test_collection", {
576
+ "name": "Test Document",
577
+ "value": 42,
578
+ "tags": ["test", "mongodb"]
579
+ })
580
+ print(f"Insert result: {result}")
581
+
582
+ # Find documents
583
+ results = find_documents("test_collection", {"name": "Test Document"})
584
+ print(f"Find results: {results}")
585
+
586
+ print("\n2. Testing Vector Operations")
587
+ print("------------------------------")
588
+
589
+ # Create vector index
590
+ index_result = create_vector_index("vector_collection", dimensions=3)
591
+ print(f"Vector index result: {index_result}")
592
+
593
+ # Store document with embedding
594
+ embedding_result = store_with_embedding(
595
+ "vector_collection",
596
+ "This is a test document for vector search",
597
+ [0.1, 0.2, 0.3],
598
+ {"category": "test"}
599
+ )
600
+ print(f"Store with embedding result: {embedding_result}")
601
+
602
+ print("\n3. Testing Statistics")
603
+ print("------------------------------")
604
+
605
+ stats = get_stats("test_collection")
606
+ print(f"Collection stats: {stats}")
607
+
608
+ print("\n==================================================")
609
+ print("MongoDB Tools Demonstration Complete")
610
+ print("==================================================\n")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.141
3
+ Version: 0.0.143
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Python: >=3.10
@@ -32,6 +32,9 @@ Requires-Dist: fastapi>=0.115.0; extra == "api"
32
32
  Requires-Dist: uvicorn>=0.34.0; extra == "api"
33
33
  Provides-Extra: telemetry
34
34
  Requires-Dist: posthog>=3.0.0; extra == "telemetry"
35
+ Provides-Extra: mongodb
36
+ Requires-Dist: pymongo>=4.6.3; extra == "mongodb"
37
+ Requires-Dist: motor>=3.4.0; extra == "mongodb"
35
38
  Provides-Extra: all
36
39
  Requires-Dist: praisonaiagents[memory]; extra == "all"
37
40
  Requires-Dist: praisonaiagents[knowledge]; extra == "all"
@@ -40,3 +43,4 @@ Requires-Dist: praisonaiagents[llm]; extra == "all"
40
43
  Requires-Dist: praisonaiagents[mcp]; extra == "all"
41
44
  Requires-Dist: praisonaiagents[api]; extra == "all"
42
45
  Requires-Dist: praisonaiagents[telemetry]; extra == "all"
46
+ Requires-Dist: praisonaiagents[mongodb]; extra == "all"