crewplus 0.2.70__tar.gz → 0.2.71__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.
- {crewplus-0.2.70 → crewplus-0.2.71}/PKG-INFO +1 -1
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/vectorstores/milvus/vdb_service.py +102 -104
- {crewplus-0.2.70 → crewplus-0.2.71}/pyproject.toml +1 -1
- {crewplus-0.2.70 → crewplus-0.2.71}/LICENSE +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/README.md +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/__init__.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/callbacks/__init__.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/callbacks/async_langfuse_handler.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/services/__init__.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/services/azure_chat_model.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/services/gemini_chat_model.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/services/init_services.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/services/model_load_balancer.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/services/tracing_manager.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/utils/__init__.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/utils/schema_action.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/utils/schema_document_updater.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/vectorstores/milvus/__init__.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/vectorstores/milvus/milvus_schema_manager.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/crewplus/vectorstores/milvus/schema_milvus.py +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/docs/GeminiChatModel.md +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/docs/ModelLoadBalancer.md +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/docs/VDBService.md +0 -0
- {crewplus-0.2.70 → crewplus-0.2.71}/docs/index.md +0 -0
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
# @Last Modified time: 2025-10-09
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
-
from typing import List, Dict, Union, Optional
|
|
8
|
+
from typing import List, Dict, Union, Optional, Any
|
|
9
9
|
from langchain_milvus import Milvus
|
|
10
10
|
from langchain_core.embeddings import Embeddings
|
|
11
11
|
from langchain_openai import AzureOpenAIEmbeddings
|
|
12
|
-
from pymilvus import MilvusClient, AsyncMilvusClient
|
|
12
|
+
from pymilvus import MilvusClient, AsyncMilvusClient, connections
|
|
13
13
|
import time
|
|
14
14
|
import asyncio
|
|
15
15
|
import uuid
|
|
16
|
+
from collections import defaultdict
|
|
16
17
|
|
|
17
18
|
from ...services.init_services import get_model_balancer
|
|
18
19
|
from .schema_milvus import SchemaMilvus, DEFAULT_SCHEMA
|
|
@@ -96,6 +97,7 @@ class VDBService(object):
|
|
|
96
97
|
_async_client: Optional[AsyncMilvusClient] = None
|
|
97
98
|
_instances: Dict[str, Milvus] = {}
|
|
98
99
|
_async_instances: Dict[str, Milvus] = {}
|
|
100
|
+
_async_instance_locks: Dict[str, asyncio.Lock] = defaultdict(asyncio.Lock)
|
|
99
101
|
|
|
100
102
|
schema: str
|
|
101
103
|
embedding_function: Embeddings
|
|
@@ -168,6 +170,10 @@ class VDBService(object):
|
|
|
168
170
|
# lazy-initialize async milvus
|
|
169
171
|
# self._async_client = self._initialize_async_milvus_client(provider)
|
|
170
172
|
|
|
173
|
+
# Do not initialize the async client here.
|
|
174
|
+
# It must be lazily initialized within an async context.
|
|
175
|
+
self._async_client: Optional[AsyncMilvusClient] = None
|
|
176
|
+
|
|
171
177
|
self.schema = schema
|
|
172
178
|
self.index_params = self.settings.get("index_params")
|
|
173
179
|
|
|
@@ -256,12 +262,16 @@ class VDBService(object):
|
|
|
256
262
|
|
|
257
263
|
async def aget_async_vector_client(self) -> AsyncMilvusClient:
|
|
258
264
|
"""
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
Returns:
|
|
262
|
-
AsyncMilvusClient: The initialized async client for interacting with the vector database.
|
|
265
|
+
Lazily initializes and returns the AsyncMilvusClient.
|
|
266
|
+
This ensures the client is created within the running event loop.
|
|
263
267
|
"""
|
|
264
|
-
|
|
268
|
+
if self._async_client is None:
|
|
269
|
+
self.logger.info("Lazily initializing AsyncMilvusClient...")
|
|
270
|
+
client_args = self._get_milvus_client_args(self._provider)
|
|
271
|
+
# Use the dedicated async alias
|
|
272
|
+
client_args['alias'] = self.async_alias
|
|
273
|
+
self._async_client = AsyncMilvusClient(**client_args)
|
|
274
|
+
return self._async_client
|
|
265
275
|
|
|
266
276
|
def get_vector_field(self, collection_name: str) -> str:
|
|
267
277
|
"""
|
|
@@ -370,6 +380,7 @@ class VDBService(object):
|
|
|
370
380
|
Asynchronously checks if a collection exists and creates it if it doesn't.
|
|
371
381
|
"""
|
|
372
382
|
try:
|
|
383
|
+
# Call the new lazy initializer for the async client
|
|
373
384
|
client = await self.aget_async_vector_client()
|
|
374
385
|
if check_existence and not await client.has_collection(collection_name):
|
|
375
386
|
self.logger.info(f"Collection '{collection_name}' does not exist. Creating it.")
|
|
@@ -498,130 +509,117 @@ class VDBService(object):
|
|
|
498
509
|
|
|
499
510
|
async def _get_or_create_async_client(self) -> AsyncMilvusClient:
|
|
500
511
|
"""
|
|
501
|
-
Lazily initializes
|
|
502
|
-
|
|
503
|
-
a
|
|
504
|
-
|
|
512
|
+
Lazily initializes the AsyncMilvusClient.
|
|
513
|
+
Based on grpcio source, the client MUST be initialized in a thread
|
|
514
|
+
with a running event loop. Therefore, we initialize it directly in the
|
|
515
|
+
main async context. The synchronous __init__ is fast enough not to
|
|
516
|
+
block the event loop meaningfully.
|
|
505
517
|
"""
|
|
506
518
|
if self._async_client is None:
|
|
507
|
-
self.logger.info("Lazily initializing AsyncMilvusClient...")
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
try:
|
|
512
|
-
# Check if an event loop exists in this new thread
|
|
513
|
-
asyncio.get_running_loop()
|
|
514
|
-
except RuntimeError: # 'RuntimeError: There is no current event loop...'
|
|
515
|
-
# If not, create and set a new one
|
|
516
|
-
loop = asyncio.new_event_loop()
|
|
517
|
-
asyncio.set_event_loop(loop)
|
|
518
|
-
|
|
519
|
-
# Now, with an event loop present in this thread, initialize the client.
|
|
520
|
-
# This is still a blocking call, but it's contained in the thread.
|
|
521
|
-
provider = self.settings.get("vector_store", {}).get("provider")
|
|
522
|
-
return self._initialize_async_milvus_client(provider)
|
|
523
|
-
|
|
524
|
-
self._async_client = await asyncio.to_thread(_create_with_loop)
|
|
519
|
+
self.logger.info("Lazily initializing AsyncMilvusClient directly in the main event loop...")
|
|
520
|
+
provider = self.settings.get("vector_store", {}).get("provider")
|
|
521
|
+
# This is a synchronous call, but it's lightweight and must run here.
|
|
522
|
+
self._async_client = self._initialize_async_milvus_client(provider)
|
|
525
523
|
|
|
526
524
|
return self._async_client
|
|
527
525
|
|
|
528
526
|
async def aget_vector_store(self, collection_name: str, embeddings: Embeddings = None, metric_type: str = "IP") -> Milvus:
|
|
529
527
|
"""
|
|
530
528
|
Asynchronously gets a vector store instance, creating it if it doesn't exist.
|
|
529
|
+
This version is optimized to handle high concurrency using a lock.
|
|
531
530
|
"""
|
|
532
531
|
if not collection_name:
|
|
533
532
|
self.logger.error("aget_vector_store called with no collection_name.")
|
|
534
533
|
raise ValueError("collection_name must be provided.")
|
|
535
534
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
self.logger.info(f"Creating new async vector store instance for collection: {collection_name}")
|
|
542
|
-
if embeddings is None:
|
|
543
|
-
embeddings = self.get_embeddings()
|
|
535
|
+
lock = self._async_instance_locks[collection_name]
|
|
536
|
+
async with lock:
|
|
537
|
+
if collection_name in self._async_instances:
|
|
538
|
+
self.logger.info(f"Returning existing async vector store instance for collection: {collection_name} (post-lock)")
|
|
539
|
+
return self._async_instances[collection_name]
|
|
544
540
|
|
|
545
|
-
|
|
541
|
+
self.logger.info(f"Creating new async vector store instance for collection: {collection_name}")
|
|
542
|
+
if embeddings is None:
|
|
543
|
+
embeddings = self.get_embeddings()
|
|
546
544
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
# self.logger.info("Embedding function is valid.")
|
|
551
|
-
# except Exception as e:
|
|
552
|
-
# self.logger.error(
|
|
553
|
-
# f"The provided embedding function is invalid and failed with error: {e}. "
|
|
554
|
-
# f"Cannot create a vector store for collection '{collection_name}'."
|
|
555
|
-
# )
|
|
556
|
-
# raise RuntimeError(f"Invalid embedding function provided.") from e
|
|
545
|
+
# CRITICAL: Ensure the shared async client is initialized *under the lock*
|
|
546
|
+
# before any operation that might use it.
|
|
547
|
+
await self._get_or_create_async_client()
|
|
557
548
|
|
|
558
|
-
|
|
559
|
-
"metric_type": metric_type,
|
|
560
|
-
"index_type": "AUTOINDEX",
|
|
561
|
-
"params": {}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
# Create a dedicated connection_args for the async path with the correct alias
|
|
565
|
-
async_conn_args = self.connection_args.copy()
|
|
566
|
-
async_conn_args['alias'] = self.async_alias
|
|
567
|
-
|
|
568
|
-
# For async operations, we MUST instantiate the Milvus object using the SYNCHRONOUS alias
|
|
569
|
-
# because its __init__ method is synchronous. This is now done in a separate thread.
|
|
570
|
-
vdb = await self._acreate_milvus_instance_with_retry(
|
|
571
|
-
collection_name=collection_name,
|
|
572
|
-
embeddings=embeddings,
|
|
573
|
-
index_params=index_params,
|
|
574
|
-
connection_args=async_conn_args # Pass the async-specific connection args
|
|
575
|
-
)
|
|
549
|
+
await self._aensure_collection_exists(collection_name, embeddings, check_existence=True)
|
|
576
550
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
vdb.aclient._using = self.async_alias
|
|
551
|
+
vdb = await self._acreate_milvus_instance_with_retry(
|
|
552
|
+
collection_name=collection_name,
|
|
553
|
+
embeddings=embeddings,
|
|
554
|
+
metric_type=metric_type
|
|
555
|
+
)
|
|
583
556
|
|
|
584
|
-
|
|
557
|
+
self.logger.info(f"Swapping to async alias for instance of collection {collection_name}")
|
|
558
|
+
vdb.aclient._using = self.async_alias
|
|
585
559
|
|
|
586
|
-
|
|
560
|
+
self._async_instances[collection_name] = vdb
|
|
561
|
+
return vdb
|
|
587
562
|
|
|
588
|
-
async def _acreate_milvus_instance_with_retry(
|
|
563
|
+
async def _acreate_milvus_instance_with_retry(
|
|
564
|
+
self,
|
|
565
|
+
embeddings: Embeddings,
|
|
566
|
+
collection_name: str,
|
|
567
|
+
metric_type: str = "IP",
|
|
568
|
+
) -> Milvus:
|
|
589
569
|
"""
|
|
590
|
-
Asynchronously creates a Milvus instance with
|
|
591
|
-
|
|
570
|
+
Asynchronously creates a Milvus instance with retry logic, ensuring the connection
|
|
571
|
+
is established in the target thread.
|
|
592
572
|
"""
|
|
593
|
-
retries =
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
def _create_instance():
|
|
597
|
-
# This synchronous function will be run in a thread
|
|
598
|
-
return Milvus(
|
|
599
|
-
embedding_function=embeddings,
|
|
600
|
-
collection_name=collection_name,
|
|
601
|
-
connection_args=conn_args,
|
|
602
|
-
index_params=index_params
|
|
603
|
-
)
|
|
604
|
-
|
|
605
|
-
self.logger.info(f"Creating Milvus instance for collection '{collection_name}' in a separate thread...")
|
|
606
|
-
self.logger.info(f"Connection args: {conn_args}")
|
|
573
|
+
retries = 3
|
|
574
|
+
last_exception = None
|
|
607
575
|
|
|
608
|
-
for attempt in range(retries
|
|
576
|
+
for attempt in range(retries):
|
|
609
577
|
try:
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
578
|
+
conn_args = self.connection_args.copy()
|
|
579
|
+
# Langchain's Milvus class will use the alias to find the connection.
|
|
580
|
+
conn_args["alias"] = self.sync_alias
|
|
581
|
+
|
|
582
|
+
def _create_instance_in_thread():
|
|
583
|
+
# --- START: CRITICAL FIX ---
|
|
584
|
+
# Manually connect within the thread before creating the Milvus instance.
|
|
585
|
+
# This ensures pymilvus registers the connection details for the current thread.
|
|
586
|
+
try:
|
|
587
|
+
connections.connect(**conn_args)
|
|
588
|
+
self.logger.info(f"Successfully connected to Milvus with alias '{self.sync_alias}' in thread.")
|
|
589
|
+
except Exception as e:
|
|
590
|
+
self.logger.error(f"Failed to manually connect in thread: {e}")
|
|
591
|
+
raise
|
|
592
|
+
|
|
593
|
+
# Now, creating the Milvus instance will find the existing connection via the alias.
|
|
594
|
+
instance = Milvus(
|
|
595
|
+
embedding_function=embeddings,
|
|
596
|
+
collection_name=collection_name,
|
|
597
|
+
connection_args=conn_args, # Pass args for completeness
|
|
598
|
+
# metric_type=metric_type, # <-- CRITICAL FIX: REMOVE THIS LINE
|
|
599
|
+
consistency_level="Strong",
|
|
600
|
+
# --- START: CRITICAL FIX ---
|
|
601
|
+
# Pass self.index_params to the Milvus constructor here
|
|
602
|
+
index_params=self.index_params,
|
|
603
|
+
# --- END: CRITICAL FIX ---
|
|
604
|
+
)
|
|
605
|
+
return instance
|
|
606
|
+
# --- END: CRITICAL FIX ---
|
|
607
|
+
|
|
608
|
+
self.logger.info(f"Attempt {attempt + 1}/{retries}: Creating Milvus instance for collection '{collection_name}' in a separate thread...")
|
|
609
|
+
vdb = await asyncio.to_thread(_create_instance_in_thread)
|
|
610
|
+
self.logger.info("Successfully created Milvus instance.")
|
|
611
|
+
return vdb
|
|
612
|
+
|
|
615
613
|
except Exception as e:
|
|
614
|
+
last_exception = e
|
|
616
615
|
self.logger.warning(
|
|
617
|
-
f"Attempt {attempt + 1}/{retries
|
|
616
|
+
f"Attempt {attempt + 1}/{retries} failed to create Milvus instance: {e}. Retrying in {2 ** attempt}s..."
|
|
618
617
|
)
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
raise RuntimeError(f"Could not connect to Milvus after {retries + 1} attempts.") from e
|
|
618
|
+
await asyncio.sleep(2 ** attempt)
|
|
619
|
+
|
|
620
|
+
raise RuntimeError(
|
|
621
|
+
f"Failed to create Milvus instance after {retries} retries."
|
|
622
|
+
) from last_exception
|
|
625
623
|
|
|
626
624
|
def _create_milvus_instance_with_retry(self, collection_name: str, embeddings: Embeddings, index_params: dict, connection_args: Optional[dict] = None) -> Milvus:
|
|
627
625
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|