crewplus 0.2.56__tar.gz → 0.2.59__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.

Potentially problematic release.


This version of crewplus might be problematic. Click here for more details.

Files changed (24) hide show
  1. {crewplus-0.2.56 → crewplus-0.2.59}/PKG-INFO +1 -1
  2. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/services/init_services.py +8 -3
  3. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/vectorstores/milvus/vdb_service.py +101 -19
  4. {crewplus-0.2.56 → crewplus-0.2.59}/pyproject.toml +1 -1
  5. {crewplus-0.2.56 → crewplus-0.2.59}/LICENSE +0 -0
  6. {crewplus-0.2.56 → crewplus-0.2.59}/README.md +0 -0
  7. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/__init__.py +0 -0
  8. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/callbacks/__init__.py +0 -0
  9. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/callbacks/async_langfuse_handler.py +0 -0
  10. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/services/__init__.py +0 -0
  11. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/services/azure_chat_model.py +0 -0
  12. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/services/gemini_chat_model.py +0 -0
  13. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/services/model_load_balancer.py +0 -0
  14. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/services/tracing_manager.py +0 -0
  15. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/utils/__init__.py +0 -0
  16. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/utils/schema_action.py +0 -0
  17. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/utils/schema_document_updater.py +0 -0
  18. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/vectorstores/milvus/__init__.py +0 -0
  19. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/vectorstores/milvus/milvus_schema_manager.py +0 -0
  20. {crewplus-0.2.56 → crewplus-0.2.59}/crewplus/vectorstores/milvus/schema_milvus.py +0 -0
  21. {crewplus-0.2.56 → crewplus-0.2.59}/docs/GeminiChatModel.md +0 -0
  22. {crewplus-0.2.56 → crewplus-0.2.59}/docs/ModelLoadBalancer.md +0 -0
  23. {crewplus-0.2.56 → crewplus-0.2.59}/docs/VDBService.md +0 -0
  24. {crewplus-0.2.56 → crewplus-0.2.59}/docs/index.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crewplus
3
- Version: 0.2.56
3
+ Version: 0.2.59
4
4
  Summary: Base services for CrewPlus AI applications
5
5
  Author-Email: Tim Liu <tim@opsmateai.com>
6
6
  License: MIT
@@ -1,6 +1,7 @@
1
- import os
2
1
  import logging
2
+ import os
3
3
  from typing import Optional
4
+
4
5
  from .model_load_balancer import ModelLoadBalancer
5
6
 
6
7
  model_balancer = None
@@ -26,9 +27,13 @@ def init_load_balancer(
26
27
  global model_balancer
27
28
  if model_balancer is None:
28
29
  # Use parameter if provided, otherwise check env var, then default
30
+ current_dir = os.path.dirname(os.path.abspath(__file__))
31
+ base_package_dir = os.path.dirname(os.path.dirname(current_dir))
32
+ default_config_path = os.path.join(base_package_dir, "_config", "models_config.json")
33
+
29
34
  final_config_path = config_path or os.getenv(
30
- "MODEL_CONFIG_PATH",
31
- "config/models_config.json"
35
+ "MODEL_CONFIG_PATH",
36
+ default_config_path
32
37
  )
33
38
  try:
34
39
  # 1. Create a local instance first.
@@ -16,7 +16,7 @@ import uuid
16
16
 
17
17
  from ...services.init_services import get_model_balancer
18
18
  from .schema_milvus import SchemaMilvus, DEFAULT_SCHEMA
19
- from .milvus_schema_manager import MilvusSchemaManager
19
+ #from .milvus_schema_manager import MilvusSchemaManager
20
20
 
21
21
  class VDBService(object):
22
22
  """
@@ -93,7 +93,7 @@ class VDBService(object):
93
93
  >>> assert vector_store is same_vector_store
94
94
  """
95
95
  _client: MilvusClient
96
- _async_client: AsyncMilvusClient
96
+ _async_client: Optional[AsyncMilvusClient] = None
97
97
  _instances: Dict[str, Milvus] = {}
98
98
  _async_instances: Dict[str, Milvus] = {}
99
99
 
@@ -154,6 +154,8 @@ class VDBService(object):
154
154
  self.logger.error(msg)
155
155
  raise ValueError(msg)
156
156
 
157
+ self._provider = provider # Store provider for lazy initialization
158
+
157
159
  # Create separate aliases for sync and async clients to avoid connection handler race conditions.
158
160
  self.sync_alias = f"crewplus-vdb-sync-{uuid.uuid4()}"
159
161
  self.async_alias = f"crewplus-vdb-async-{uuid.uuid4()}"
@@ -163,12 +165,13 @@ class VDBService(object):
163
165
  self.connection_args['alias'] = self.sync_alias
164
166
 
165
167
  self._client = self._initialize_milvus_client(provider)
166
- self._async_client = self._initialize_async_milvus_client(provider)
168
+ # lazy-initialize async milvus
169
+ # self._async_client = self._initialize_async_milvus_client(provider)
167
170
 
168
171
  self.schema = schema
169
172
  self.index_params = self.settings.get("index_params")
170
173
 
171
- self.schema_manager = MilvusSchemaManager(client=self._client, async_client=self._async_client)
174
+ #self.schema_manager = MilvusSchemaManager(client=self._client, async_client=self._async_client)
172
175
 
173
176
  self.logger.info("VDBService initialized successfully")
174
177
 
@@ -231,7 +234,7 @@ class VDBService(object):
231
234
  return AsyncMilvusClient(**client_args)
232
235
  except Exception as e:
233
236
  self.logger.error(f"Failed to initialize AsyncMilvusClient, trying again. Error: {e}")
234
- # Second attempt after failure
237
+ time.sleep(1) # sync sleep is fine, we are in a thread
235
238
  try:
236
239
  return AsyncMilvusClient(**client_args)
237
240
  except Exception as e_retry:
@@ -240,21 +243,25 @@ class VDBService(object):
240
243
 
241
244
  def get_vector_client(self) -> MilvusClient:
242
245
  """
243
- Returns the active MilvusClient instance.
246
+ Returns the active MilvusClient instance, initializing it if necessary.
244
247
 
245
248
  Returns:
246
249
  MilvusClient: The initialized client for interacting with the vector database.
247
250
  """
251
+ if self._client is None:
252
+ self.logger.debug("Initializing synchronous MilvusClient...")
253
+ self._client = self._initialize_milvus_client(self._provider)
254
+
248
255
  return self._client
249
256
 
250
- def get_async_vector_client(self) -> AsyncMilvusClient:
257
+ async def aget_async_vector_client(self) -> AsyncMilvusClient:
251
258
  """
252
- Returns the active AsyncMilvusClient instance.
259
+ Asynchronously returns the active AsyncMilvusClient instance, initializing it if necessary.
253
260
 
254
261
  Returns:
255
262
  AsyncMilvusClient: The initialized async client for interacting with the vector database.
256
263
  """
257
- return self._async_client
264
+ return await self._get_or_create_async_client()
258
265
 
259
266
  def get_vector_field(self, collection_name: str) -> str:
260
267
  """
@@ -363,7 +370,7 @@ class VDBService(object):
363
370
  Asynchronously checks if a collection exists and creates it if it doesn't.
364
371
  """
365
372
  try:
366
- client = self.get_async_vector_client()
373
+ client = await self.aget_async_vector_client()
367
374
  if check_existence and not await client.has_collection(collection_name):
368
375
  self.logger.info(f"Collection '{collection_name}' does not exist. Creating it.")
369
376
 
@@ -374,6 +381,9 @@ class VDBService(object):
374
381
  index_params=self.index_params
375
382
  )
376
383
 
384
+ #ensure using async connection alias
385
+ schema_milvus.aclient._using = self.async_alias
386
+
377
387
  schema_to_use = self.schema or DEFAULT_SCHEMA
378
388
  if not self.schema:
379
389
  self.logger.warning(f"No schema provided for VDBService. Using DEFAULT_SCHEMA for collection '{collection_name}'.")
@@ -498,6 +508,35 @@ class VDBService(object):
498
508
 
499
509
  return vdb
500
510
 
511
+ async def _get_or_create_async_client(self) -> AsyncMilvusClient:
512
+ """
513
+ Lazily initializes and returns the AsyncMilvusClient.
514
+ This runs the blocking constructor in a separate thread, but also creates
515
+ a temporary event loop inside that thread to satisfy the client's
516
+ initialization requirements.
517
+ """
518
+ if self._async_client is None:
519
+ self.logger.info("Lazily initializing AsyncMilvusClient...")
520
+
521
+ def _create_with_loop():
522
+ # This function runs in a separate thread via asyncio.to_thread
523
+ try:
524
+ # Check if an event loop exists in this new thread
525
+ asyncio.get_running_loop()
526
+ except RuntimeError: # 'RuntimeError: There is no current event loop...'
527
+ # If not, create and set a new one
528
+ loop = asyncio.new_event_loop()
529
+ asyncio.set_event_loop(loop)
530
+
531
+ # Now, with an event loop present in this thread, initialize the client.
532
+ # This is still a blocking call, but it's contained in the thread.
533
+ provider = self.settings.get("vector_store", {}).get("provider")
534
+ return self._initialize_async_milvus_client(provider)
535
+
536
+ self._async_client = await asyncio.to_thread(_create_with_loop)
537
+
538
+ return self._async_client
539
+
501
540
  async def aget_vector_store(self, collection_name: str, embeddings: Embeddings = None, metric_type: str = "IP") -> Milvus:
502
541
  """
503
542
  Asynchronously gets a vector store instance, creating it if it doesn't exist.
@@ -525,12 +564,12 @@ class VDBService(object):
525
564
  if embeddings is None:
526
565
  embeddings = self.get_embeddings()
527
566
 
528
- await self._aensure_collection_exists(collection_name, embeddings, check_existence=check_existence)
567
+ # await self._aensure_collection_exists(collection_name, embeddings, check_existence=check_existence)
529
568
 
530
569
  try:
531
- self.logger.debug(f"Testing embedding function for collection '{collection_name}'...")
570
+ self.logger.info(f"Testing embedding function for collection '{collection_name}'...")
532
571
  await embeddings.aembed_query("validation_test_string")
533
- self.logger.debug("Embedding function is valid.")
572
+ self.logger.info("Embedding function is valid.")
534
573
  except Exception as e:
535
574
  self.logger.error(
536
575
  f"The provided embedding function is invalid and failed with error: {e}. "
@@ -544,24 +583,67 @@ class VDBService(object):
544
583
  "params": {}
545
584
  }
546
585
 
586
+ # Create a dedicated connection_args for the async path with the correct alias
587
+ async_conn_args = self.connection_args.copy()
588
+ async_conn_args['alias'] = self.async_alias
589
+
547
590
  # For async operations, we MUST instantiate the Milvus object using the SYNCHRONOUS alias
548
- # because its __init__ method is synchronous.
549
- vdb = self._create_milvus_instance_with_retry(
591
+ # because its __init__ method is synchronous. This is now done in a separate thread.
592
+ vdb = await self._acreate_milvus_instance_with_retry(
550
593
  collection_name=collection_name,
551
594
  embeddings=embeddings,
552
595
  index_params=index_params,
553
- connection_args=self.connection_args # This uses the sync_alias by default
596
+ connection_args=async_conn_args # Pass the async-specific connection args
554
597
  )
555
598
 
556
599
  # After successful synchronous initialization, we hot-swap the alias on the
557
600
  # ASYNCHRONOUS client to ensure future async operations use the correct connection.
558
- self.logger.debug(f"Swapping to async alias for instance of collection {collection_name}")
601
+ self.logger.info(f"Swapping to async alias for instance of collection {collection_name}")
602
+ await self._get_or_create_async_client()
559
603
  vdb.aclient._using = self.async_alias
560
604
 
561
605
  self._async_instances[collection_name] = vdb
562
606
 
563
607
  return vdb
564
608
 
609
+ async def _acreate_milvus_instance_with_retry(self, collection_name: str, embeddings: Embeddings, index_params: dict, connection_args: Optional[dict] = None) -> Milvus:
610
+ """
611
+ Asynchronously creates a Milvus instance with a retry mechanism, running the synchronous
612
+ constructor in a separate thread to avoid blocking the event loop.
613
+ """
614
+ retries = 2
615
+ conn_args = connection_args if connection_args is not None else self.connection_args
616
+
617
+ def _create_instance():
618
+ # This synchronous function will be run in a thread
619
+ return Milvus(
620
+ embedding_function=embeddings,
621
+ collection_name=collection_name,
622
+ connection_args=conn_args,
623
+ index_params=index_params
624
+ )
625
+
626
+ self.logger.info(f"Creating Milvus instance for collection '{collection_name}' in a separate thread...")
627
+ self.logger.info(f"Connection args: {conn_args}")
628
+
629
+ for attempt in range(retries + 1):
630
+ try:
631
+ # Run the blocking constructor in a separate thread
632
+ vdb = await asyncio.to_thread(_create_instance)
633
+
634
+ self.logger.info(f"Successfully connected to Milvus for collection '{collection_name}' on attempt {attempt + 1}.")
635
+ return vdb # Return on success
636
+ except Exception as e:
637
+ self.logger.warning(
638
+ f"Attempt {attempt + 1}/{retries + 1} to connect to Milvus for collection '{collection_name}' failed: {e}"
639
+ )
640
+ if attempt < retries:
641
+ self.logger.info("Retrying in 3 seconds...")
642
+ await asyncio.sleep(3) # Use async sleep
643
+ else:
644
+ self.logger.error(f"Failed to connect to Milvus for collection '{collection_name}' after {retries + 1} attempts.")
645
+ raise RuntimeError(f"Could not connect to Milvus after {retries + 1} attempts.") from e
646
+
565
647
  def _create_milvus_instance_with_retry(self, collection_name: str, embeddings: Embeddings, index_params: dict, connection_args: Optional[dict] = None) -> Milvus:
566
648
  """
567
649
  Creates a Milvus instance with a retry mechanism for connection failures.
@@ -637,7 +719,7 @@ class VDBService(object):
637
719
  self.logger.info(f"Attempting to drop collection asynchronously: {collection_name}")
638
720
 
639
721
  try:
640
- client = self.get_async_vector_client()
722
+ client = await self.aget_async_vector_client()
641
723
  await client.drop_collection(collection_name=collection_name)
642
724
  self.logger.info(f"Successfully dropped collection asynchronously: {collection_name}")
643
725
  except Exception as e:
@@ -676,7 +758,7 @@ class VDBService(object):
676
758
  self.logger.info(f"Delete data by filter asynchronously:{filter}")
677
759
 
678
760
  try:
679
- client=self.get_async_vector_client()
761
+ client= await self.aget_async_vector_client()
680
762
  if collection_name is None or client is None or filter is None:
681
763
  return RuntimeError(f"collection_name must be not null or check out your client to link milvus")
682
764
  await client.delete(collection_name=collection_name, filter=filter)
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "crewplus"
9
- version = "0.2.56"
9
+ version = "0.2.59"
10
10
  description = "Base services for CrewPlus AI applications"
11
11
  authors = [
12
12
  { name = "Tim Liu", email = "tim@opsmateai.com" },
File without changes
File without changes
File without changes
File without changes