diff --git a/.github/workflows/contrib-tests.yml b/.github/workflows/contrib-tests.yml index 3abe257dfad6..9d7323617460 100644 --- a/.github/workflows/contrib-tests.yml +++ b/.github/workflows/contrib-tests.yml @@ -91,6 +91,17 @@ jobs: image: mongodb/mongodb-atlas-local:latest ports: - 27017:27017 + couchbase: + image: couchbase:enterprise-7.6.3 + ports: + - "8091-8095:8091-8095" + - "11210:11210" + - "9102:9102" + healthcheck: # checks couchbase server is up + test: ["CMD", "curl", "-v", "http://localhost:8091/pools"] + interval: 20s + timeout: 20s + retries: 5 steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -111,6 +122,9 @@ jobs: - name: Install mongodb when on linux run: | pip install -e .[retrievechat-mongodb] + - name: Install couchbase when on linux + run: | + pip install -e .[retrievechat-couchbase] - name: Install unstructured when python-version is 3.9 and on linux if: matrix.python-version == '3.9' run: | diff --git a/autogen/agentchat/contrib/agent_eval/__init__.py b/autogen/agentchat/contrib/agent_eval/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/autogen/agentchat/contrib/gpt_assistant_agent.py b/autogen/agentchat/contrib/gpt_assistant_agent.py index 0dcad27b16d5..244f5ed81894 100644 --- a/autogen/agentchat/contrib/gpt_assistant_agent.py +++ b/autogen/agentchat/contrib/gpt_assistant_agent.py @@ -209,10 +209,12 @@ def _invoke_assistant( for message in pending_messages: if message["content"].strip() == "": continue + # Convert message roles to 'user' or 'assistant', by calling _map_role_for_api, to comply with OpenAI API spec + api_role = self._map_role_for_api(message["role"]) self._openai_client.beta.threads.messages.create( thread_id=assistant_thread.id, content=message["content"], - role=message["role"], + role=api_role, ) # Create a new run to get responses from the assistant @@ -240,6 +242,28 @@ def _invoke_assistant( self._unread_index[sender] = len(self._oai_messages[sender]) + 1 return True, response + def _map_role_for_api(self, role: str) -> str: + """ + Maps internal message roles to the roles expected by the OpenAI Assistant API. + + Args: + role (str): The role from the internal message. + + Returns: + str: The mapped role suitable for the API. + """ + if role in ["function", "tool"]: + return "assistant" + elif role == "system": + return "system" + elif role == "user": + return "user" + elif role == "assistant": + return "assistant" + else: + # Default to 'assistant' for any other roles not recognized by the API + return "assistant" + def _get_run_response(self, thread, run): """ Waits for and processes the response of a run from the OpenAI assistant. diff --git a/autogen/agentchat/contrib/retrieve_user_proxy_agent.py b/autogen/agentchat/contrib/retrieve_user_proxy_agent.py index 10b70e0e9720..b247d7a158f6 100644 --- a/autogen/agentchat/contrib/retrieve_user_proxy_agent.py +++ b/autogen/agentchat/contrib/retrieve_user_proxy_agent.py @@ -178,7 +178,7 @@ def __init__( vector db. Default is None, SentenceTransformer with the given `embedding_model` will be used. If you want to use OpenAI, Cohere, HuggingFace or other embedding functions, you can pass it here, - follow the examples in `https://docs.trychroma.com/embeddings`. + follow the examples in `https://docs.trychroma.com/guides/embeddings`. - `customized_prompt` (Optional, str) - the customized prompt for the retrieve chat. Default is None. - `customized_answer_prefix` (Optional, str) - the customized answer prefix for the diff --git a/autogen/agentchat/contrib/vectordb/base.py b/autogen/agentchat/contrib/vectordb/base.py index d7d49d6200ca..c0519b5717f1 100644 --- a/autogen/agentchat/contrib/vectordb/base.py +++ b/autogen/agentchat/contrib/vectordb/base.py @@ -201,7 +201,7 @@ class VectorDBFactory: Factory class for creating vector databases. """ - PREDEFINED_VECTOR_DB = ["chroma", "pgvector", "mongodb", "qdrant"] + PREDEFINED_VECTOR_DB = ["chroma", "pgvector", "mongodb", "qdrant", "couchbase"] @staticmethod def create_vector_db(db_type: str, **kwargs) -> VectorDB: @@ -231,6 +231,10 @@ def create_vector_db(db_type: str, **kwargs) -> VectorDB: from .qdrant import QdrantVectorDB return QdrantVectorDB(**kwargs) + if db_type.lower() in ["couchbase", "couchbasedb", "capella"]: + from .couchbase import CouchbaseVectorDB + + return CouchbaseVectorDB(**kwargs) else: raise ValueError( f"Unsupported vector database type: {db_type}. Valid types are {VectorDBFactory.PREDEFINED_VECTOR_DB}." diff --git a/autogen/agentchat/contrib/vectordb/chromadb.py b/autogen/agentchat/contrib/vectordb/chromadb.py index 1ed8708409d3..bef4a1090219 100644 --- a/autogen/agentchat/contrib/vectordb/chromadb.py +++ b/autogen/agentchat/contrib/vectordb/chromadb.py @@ -14,6 +14,11 @@ except ImportError: raise ImportError("Please install chromadb: `pip install chromadb`") +try: + from chromadb.errors import ChromaError +except ImportError: + ChromaError = Exception + CHROMADB_MAX_BATCH_SIZE = os.environ.get("CHROMADB_MAX_BATCH_SIZE", 40000) logger = get_logger(__name__) @@ -84,7 +89,7 @@ def create_collection( collection = self.active_collection else: collection = self.client.get_collection(collection_name, embedding_function=self.embedding_function) - except ValueError: + except (ValueError, ChromaError): collection = None if collection is None: return self.client.create_collection( diff --git a/autogen/agentchat/contrib/vectordb/couchbase.py b/autogen/agentchat/contrib/vectordb/couchbase.py new file mode 100644 index 000000000000..66691fa2f2b7 --- /dev/null +++ b/autogen/agentchat/contrib/vectordb/couchbase.py @@ -0,0 +1,396 @@ +import json +import time +from datetime import timedelta +from typing import Any, Callable, Dict, List, Literal, Tuple, Union + +import numpy as np +from couchbase import search +from couchbase.auth import PasswordAuthenticator +from couchbase.cluster import Cluster, ClusterOptions +from couchbase.collection import Collection +from couchbase.management.search import SearchIndex +from couchbase.options import SearchOptions +from couchbase.vector_search import VectorQuery, VectorSearch +from sentence_transformers import SentenceTransformer + +from .base import Document, ItemID, QueryResults, VectorDB +from .utils import get_logger + +logger = get_logger(__name__) + +DEFAULT_BATCH_SIZE = 1000 +_SAMPLE_SENTENCE = ["The weather is lovely today in paradise."] +TEXT_KEY = "content" +EMBEDDING_KEY = "embedding" + + +class CouchbaseVectorDB(VectorDB): + """ + A vector database implementation that uses Couchbase as the backend. + """ + + def __init__( + self, + connection_string: str = "couchbase://localhost", + username: str = "Administrator", + password: str = "password", + bucket_name: str = "vector_db", + embedding_function: Callable = SentenceTransformer("all-MiniLM-L6-v2").encode, + scope_name: str = "_default", + collection_name: str = "_default", + index_name: str = None, + ): + """ + Initialize the vector database. + + Args: + connection_string (str): The Couchbase connection string to connect to. Default is 'couchbase://localhost'. + username (str): The username for Couchbase authentication. Default is 'Administrator'. + password (str): The password for Couchbase authentication. Default is 'password'. + bucket_name (str): The name of the bucket. Default is 'vector_db'. + embedding_function (Callable): The embedding function used to generate the vector representation. Default is SentenceTransformer("all-MiniLM-L6-v2").encode. + scope_name (str): The name of the scope. Default is '_default'. + collection_name (str): The name of the collection to create for this vector database. Default is '_default'. + index_name (str): Index name for the vector database. Default is None. + overwrite (bool): Whether to overwrite existing data. Default is False. + wait_until_index_ready (float | None): Blocking call to wait until the database indexes are ready. None means no wait. Default is None. + wait_until_document_ready (float | None): Blocking call to wait until the database documents are ready. None means no wait. Default is None. + """ + print( + "CouchbaseVectorDB", + connection_string, + username, + password, + bucket_name, + scope_name, + collection_name, + index_name, + ) + self.embedding_function = embedding_function + self.index_name = index_name + + # This will get the model dimension size by computing the embeddings dimensions + self.dimensions = self._get_embedding_size() + + try: + auth = PasswordAuthenticator(username, password) + cluster = Cluster(connection_string, ClusterOptions(auth)) + cluster.wait_until_ready(timedelta(seconds=5)) + self.cluster = cluster + + self.bucket = cluster.bucket(bucket_name) + self.scope = self.bucket.scope(scope_name) + self.collection = self.scope.collection(collection_name) + self.active_collection = self.collection + + logger.debug("Successfully connected to Couchbase") + except Exception as err: + raise ConnectionError("Could not connect to Couchbase server") from err + + def search_index_exists(self, index_name: str): + """Check if the specified index is ready""" + try: + search_index_mgr = self.scope.search_indexes() + index = search_index_mgr.get_index(index_name) + return index.is_valid() + except Exception: + return False + + def _get_embedding_size(self): + return len(self.embedding_function(_SAMPLE_SENTENCE)[0]) + + def create_collection( + self, + collection_name: str, + overwrite: bool = False, + get_or_create: bool = True, + ) -> Collection: + """ + Create a collection in the vector database and create a vector search index in the collection. + + Args: + collection_name: str | The name of the collection. + overwrite: bool | Whether to overwrite the collection if it exists. Default is False. + get_or_create: bool | Whether to get or create the collection. Default is True + """ + if overwrite: + self.delete_collection(collection_name) + + try: + collection_mgr = self.bucket.collections() + collection_mgr.create_collection(self.scope.name, collection_name) + + except Exception: + if not get_or_create: + raise ValueError(f"Collection {collection_name} already exists.") + else: + logger.debug(f"Collection {collection_name} already exists. Getting the collection.") + + collection = self.scope.collection(collection_name) + self.create_index_if_not_exists(index_name=self.index_name, collection=collection) + return collection + + def create_index_if_not_exists(self, index_name: str = "vector_index", collection=None) -> None: + """ + Creates a vector search index on the specified collection in Couchbase. + + Args: + index_name (str, optional): The name of the vector search index to create. Defaults to "vector_search_index". + collection (Collection, optional): The Couchbase collection to create the index on. Defaults to None. + """ + if not self.search_index_exists(index_name): + self.create_vector_search_index(collection, index_name) + + def get_collection(self, collection_name: str = None) -> Collection: + """ + Get the collection from the vector database. + + Args: + collection_name: str | The name of the collection. Default is None. If None, return the + current active collection. + + Returns: + Collection | The collection object. + """ + if collection_name is None: + if self.active_collection is None: + raise ValueError("No collection is specified.") + else: + logger.debug( + f"No collection is specified. Using current active collection {self.active_collection.name}." + ) + else: + self.active_collection = self.scope.collection(collection_name) + + return self.active_collection + + def delete_collection(self, collection_name: str) -> None: + """ + Delete the collection from the vector database. + + Args: + collection_name: str | The name of the collection. + """ + try: + collection_mgr = self.bucket.collections() + collection_mgr.drop_collection(self.scope.name, collection_name) + except Exception as e: + logger.error(f"Error deleting collection: {e}") + + def create_vector_search_index( + self, + collection, + index_name: Union[str, None] = "vector_index", + similarity: Literal["l2_norm", "dot_product"] = "dot_product", + ) -> None: + """Create a vector search index in the collection.""" + search_index_mgr = self.scope.search_indexes() + dims = self._get_embedding_size() + index_definition = { + "type": "fulltext-index", + "name": index_name, + "sourceType": "couchbase", + "sourceName": self.bucket.name, + "planParams": {"maxPartitionsPerPIndex": 1024, "indexPartitions": 1}, + "params": { + "doc_config": { + "docid_prefix_delim": "", + "docid_regexp": "", + "mode": "scope.collection.type_field", + "type_field": "type", + }, + "mapping": { + "analysis": {}, + "default_analyzer": "standard", + "default_datetime_parser": "dateTimeOptional", + "default_field": "_all", + "default_mapping": {"dynamic": True, "enabled": False}, + "default_type": "_default", + "docvalues_dynamic": False, + "index_dynamic": True, + "store_dynamic": True, + "type_field": "_type", + "types": { + f"{self.scope.name}.{collection.name}": { + "dynamic": False, + "enabled": True, + "properties": { + "embedding": { + "dynamic": False, + "enabled": True, + "fields": [ + { + "dims": dims, + "index": True, + "name": "embedding", + "similarity": similarity, + "type": "vector", + "vector_index_optimized_for": "recall", + } + ], + }, + "metadata": {"dynamic": True, "enabled": True}, + "content": { + "dynamic": False, + "enabled": True, + "fields": [ + { + "include_in_all": True, + "index": True, + "name": "content", + "store": True, + "type": "text", + } + ], + }, + }, + } + }, + }, + "store": {"indexType": "scorch", "segmentVersion": 16}, + }, + "sourceParams": {}, + } + + search_index_def = SearchIndex.from_json(json.dumps(index_definition)) + max_attempts = 10 + attempt = 0 + while attempt < max_attempts: + try: + search_index_mgr.upsert_index(search_index_def) + break + except Exception as e: + logger.debug(f"Attempt {attempt + 1}/{max_attempts}: Error creating search index: {e}") + time.sleep(3) + attempt += 1 + + if attempt == max_attempts: + logger.error(f"Error creating search index after {max_attempts} attempts.") + raise RuntimeError(f"Error creating search index after {max_attempts} attempts.") + + logger.info(f"Search index {index_name} created successfully.") + + def upsert_docs( + self, docs: List[Document], collection: Collection, batch_size=DEFAULT_BATCH_SIZE, **kwargs: Any + ) -> None: + if docs[0].get("content") is None: + raise ValueError("The document content is required.") + if docs[0].get("id") is None: + raise ValueError("The document id is required.") + + for i in range(0, len(docs), batch_size): + batch = docs[i : i + batch_size] + docs_to_upsert = dict() + for doc in batch: + doc_id = doc["id"] + embedding = self.embedding_function( + [doc["content"]] + ).tolist() # Gets new embedding even in case of document update + + doc_content = {TEXT_KEY: doc["content"], "metadata": doc.get("metadata", {}), EMBEDDING_KEY: embedding} + docs_to_upsert[doc_id] = doc_content + collection.upsert_multi(docs_to_upsert) + + def insert_docs( + self, + docs: List[Document], + collection_name: str = None, + upsert: bool = False, + batch_size=DEFAULT_BATCH_SIZE, + **kwargs, + ) -> None: + """Insert Documents and Vector Embeddings into the collection of the vector database. Documents are upserted in all cases.""" + if not docs: + logger.info("No documents to insert.") + return + + collection = self.get_collection(collection_name) + self.upsert_docs(docs, collection, batch_size=batch_size) + + def update_docs( + self, docs: List[Document], collection_name: str = None, batch_size=DEFAULT_BATCH_SIZE, **kwargs: Any + ) -> None: + """Update documents, including their embeddings, in the Collection.""" + collection = self.get_collection(collection_name) + self.upsert_docs(docs, collection, batch_size) + + def delete_docs(self, ids: List[ItemID], collection_name: str = None, batch_size=DEFAULT_BATCH_SIZE, **kwargs): + """Delete documents from the collection of the vector database.""" + collection = self.get_collection(collection_name) + # based on batch size, delete the documents + for i in range(0, len(ids), batch_size): + batch = ids[i : i + batch_size] + collection.remove_multi(batch) + + def get_docs_by_ids( + self, ids: List[ItemID] | None = None, collection_name: str = None, include: List[str] | None = None, **kwargs + ) -> List[Document]: + """Retrieve documents from the collection of the vector database based on the ids.""" + if include is None: + include = [TEXT_KEY, "metadata", "id"] + elif "id" not in include: + include.append("id") + + collection = self.get_collection(collection_name) + if ids is not None: + docs = [collection.get(doc_id) for doc_id in ids] + else: + # Get all documents using couchbase query + include_str = ", ".join(include) + query = f"SELECT {include_str} FROM {self.bucket.name}.{self.scope.name}.{collection.name}" + result = self.cluster.query(query) + docs = [] + for row in result: + docs.append(row) + + return [{k: v for k, v in doc.items() if k in include or k == "id"} for doc in docs] + + def retrieve_docs( + self, + queries: List[str], + collection_name: str = None, + n_results: int = 10, + distance_threshold: float = -1, + **kwargs, + ) -> QueryResults: + """Retrieve documents from the collection of the vector database based on the queries. + Note: Distance threshold is not supported in Couchbase FTS. + """ + + results: QueryResults = [] + for query_text in queries: + query_vector = np.array(self.embedding_function([query_text])).tolist()[0] + query_result = self._vector_search( + query_vector, + n_results, + **kwargs, + ) + results.append(query_result) + return results + + def _vector_search(self, embedding_vector: List[float], n_results: int = 10, **kwargs) -> List[Tuple[Dict, float]]: + """Core vector search using Couchbase FTS.""" + + search_req = search.SearchRequest.create( + VectorSearch.from_vector_query( + VectorQuery( + EMBEDDING_KEY, + embedding_vector, + n_results, + ) + ) + ) + + search_options = SearchOptions(limit=n_results, fields=["*"]) + result = self.scope.search(self.index_name, search_req, search_options) + + docs_with_score = [] + + for row in result.rows(): + doc = row.fields + doc["id"] = row.id + score = row.score + + docs_with_score.append((doc, score)) + + return docs_with_score diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index eabe6d6d4606..e19cbd56de2b 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -1658,8 +1658,8 @@ async def a_generate_function_call_reply( if messages is None: messages = self._oai_messages[sender] message = messages[-1] - if "function_call" in message: - func_call = message["function_call"] + func_call = message.get("function_call") + if func_call: func_name = func_call.get("name", "") func = self._function_map.get(func_name, None) if func and inspect.iscoroutinefunction(func): diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 2ebdf95b7d37..c6355a13b94d 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -1398,13 +1398,12 @@ async def a_resume( if not message_speaker_agent and message["name"] == self.name: message_speaker_agent = self - # Add previous messages to each agent (except their own messages and the last message, as we'll kick off the conversation with it) + # Add previous messages to each agent (except the last message, as we'll kick off the conversation with it) if i != len(messages) - 1: for agent in self._groupchat.agents: - if agent.name != message["name"]: - await self.a_send( - message, self._groupchat.agent_by_name(agent.name), request_reply=False, silent=True - ) + await self.a_send( + message, self._groupchat.agent_by_name(agent.name), request_reply=False, silent=True + ) # Add previous message to the new groupchat, if it's an admin message the name may not match so add the message directly if message_speaker_agent: diff --git a/autogen/coding/jupyter/docker_jupyter_server.py b/autogen/coding/jupyter/docker_jupyter_server.py index 83455e272380..e5896014a613 100644 --- a/autogen/coding/jupyter/docker_jupyter_server.py +++ b/autogen/coding/jupyter/docker_jupyter_server.py @@ -8,7 +8,7 @@ import uuid from pathlib import Path from types import TracebackType -from typing import Dict, Optional, Type, Union +from typing import Any, Dict, Optional, Type, Union import docker @@ -59,6 +59,7 @@ def __init__( stop_container: bool = True, docker_env: Dict[str, str] = {}, token: Union[str, GenerateToken] = GenerateToken(), + **docker_kwargs: Any, ): """Start a Jupyter kernel gateway server in a Docker container. @@ -77,6 +78,7 @@ def __init__( token (Union[str, GenerateToken], optional): Token to use for authentication. If GenerateToken is used, a random token will be generated. Empty string will be unauthenticated. + docker_kwargs (Any): Additional keyword arguments to pass to the docker container. """ if container_name is None: container_name = f"autogen-jupyterkernelgateway-{uuid.uuid4()}" @@ -118,6 +120,7 @@ def __init__( environment=env, publish_all_ports=True, name=container_name, + **docker_kwargs, ) _wait_for_ready(container) container_ports = container.ports diff --git a/autogen/coding/jupyter/jupyter_client.py b/autogen/coding/jupyter/jupyter_client.py index b3de374fce9b..787009dafe2f 100644 --- a/autogen/coding/jupyter/jupyter_client.py +++ b/autogen/coding/jupyter/jupyter_client.py @@ -39,6 +39,10 @@ def _get_headers(self) -> Dict[str, str]: return {} return {"Authorization": f"token {self._connection_info.token}"} + def _get_cookies(self) -> str: + cookies = self._session.cookies.get_dict() + return "; ".join([f"{name}={value}" for name, value in cookies.items()]) + def _get_api_base_url(self) -> str: protocol = "https" if self._connection_info.use_https else "http" port = f":{self._connection_info.port}" if self._connection_info.port else "" @@ -87,7 +91,7 @@ def restart_kernel(self, kernel_id: str) -> None: def get_kernel_client(self, kernel_id: str) -> JupyterKernelClient: ws_url = f"{self._get_ws_base_url()}/api/kernels/{kernel_id}/channels" - ws = websocket.create_connection(ws_url, header=self._get_headers()) + ws = websocket.create_connection(ws_url, header=self._get_headers(), cookie=self._get_cookies()) return JupyterKernelClient(ws) diff --git a/autogen/logger/file_logger.py b/autogen/logger/file_logger.py index 37bbbd25a523..07c9c3b76a76 100644 --- a/autogen/logger/file_logger.py +++ b/autogen/logger/file_logger.py @@ -90,7 +90,7 @@ def log_chat_completion( thread_id = threading.get_ident() source_name = None if isinstance(source, str): - source_name = source + source_name = getattr(source, "name", "unknown") else: source_name = source.name try: diff --git a/autogen/oai/client.py b/autogen/oai/client.py index 3ae37257b21e..4b77815e7eb7 100644 --- a/autogen/oai/client.py +++ b/autogen/oai/client.py @@ -12,10 +12,12 @@ from autogen.cache import Cache from autogen.io.base import IOStream from autogen.logger.logger_utils import get_current_ts -from autogen.oai.openai_utils import OAI_PRICE1K, get_key, is_valid_api_key +from autogen.oai.openai_utils import OAI_PRICE1K, get_key from autogen.runtime_logging import log_chat_completion, log_new_client, log_new_wrapper, logging_enabled from autogen.token_count_utils import count_token +from .rate_limiters import RateLimiter, TimeRateLimiter + TOOL_ENABLED = False try: import openai @@ -163,11 +165,7 @@ class OpenAIClient: def __init__(self, client: Union[OpenAI, AzureOpenAI]): self._oai_client = client - if ( - not isinstance(client, openai.AzureOpenAI) - and str(client.base_url).startswith(OPEN_API_BASE_URL_PREFIX) - and not is_valid_api_key(self._oai_client.api_key) - ): + if not isinstance(client, openai.AzureOpenAI) and str(client.base_url).startswith(OPEN_API_BASE_URL_PREFIX): logger.warning( "The API key specified is not a valid OpenAI format; it won't work with the OpenAI-hosted model." ) @@ -207,7 +205,9 @@ def create(self, params: Dict[str, Any]) -> ChatCompletion: """ iostream = IOStream.get_default() - completions: Completions = self._oai_client.chat.completions if "messages" in params else self._oai_client.completions # type: ignore [attr-defined] + completions: Completions = ( + self._oai_client.chat.completions if "messages" in params else self._oai_client.completions + ) # type: ignore [attr-defined] # If streaming is enabled and has messages, then iterate over the chunks of the response. if params.get("stream", False) and "messages" in params: response_contents = [""] * params.get("n", 1) @@ -279,7 +279,12 @@ def create(self, params: Dict[str, Any]) -> ChatCompletion: # Prepare the final ChatCompletion object based on the accumulated data model = chunk.model.replace("gpt-35", "gpt-3.5") # hack for Azure API - prompt_tokens = count_token(params["messages"], model) + try: + prompt_tokens = count_token(params["messages"], model) + except NotImplementedError as e: + # Catch token calculation error if streaming with customized models. + logger.warning(str(e)) + prompt_tokens = 0 response = ChatCompletion( id=chunk.id, model=chunk.model, @@ -422,8 +427,11 @@ def __init__(self, *, config_list: Optional[List[Dict[str, Any]]] = None, **base self._clients: List[ModelClient] = [] self._config_list: List[Dict[str, Any]] = [] + self._rate_limiters: List[Optional[RateLimiter]] = [] if config_list: + self._initialize_rate_limiters(config_list) + config_list = [config.copy() for config in config_list] # make a copy before modifying for config in config_list: self._register_default_client(config, openai_config) # could modify the config @@ -744,6 +752,7 @@ def yes_or_no_filter(context, response): return response continue # filter is not passed; try the next config try: + self._throttle_api_calls(i) request_ts = get_current_ts() response = client.create(params) except APITimeoutError as err: @@ -1037,3 +1046,20 @@ def extract_text_or_completion_object( A list of text, or a list of ChatCompletion objects if function_call/tool_calls are present. """ return response.message_retrieval_function(response) + + def _throttle_api_calls(self, idx: int) -> None: + """Rate limit api calls.""" + if self._rate_limiters[idx]: + limiter = self._rate_limiters[idx] + + assert limiter is not None + limiter.sleep() + + def _initialize_rate_limiters(self, config_list: List[Dict[str, Any]]) -> None: + for config in config_list: + # Instantiate the rate limiter + if "api_rate_limit" in config: + self._rate_limiters.append(TimeRateLimiter(config["api_rate_limit"])) + del config["api_rate_limit"] + else: + self._rate_limiters.append(None) diff --git a/autogen/oai/cohere.py b/autogen/oai/cohere.py index 3d38d86425fb..e9a89c9cabd8 100644 --- a/autogen/oai/cohere.py +++ b/autogen/oai/cohere.py @@ -148,7 +148,6 @@ def create(self, params: Dict) -> ChatCompletion: client_name = params.get("client_name") or "autogen-cohere" # Parse parameters to the Cohere API's parameters cohere_params = self.parse_params(params) - # Convert AutoGen messages to Cohere messages cohere_messages, preamble, final_message = oai_messages_to_cohere_messages(messages, params, cohere_params) @@ -169,6 +168,7 @@ def create(self, params: Dict) -> ChatCompletion: cohere_finish = "" max_retries = 5 + for attempt in range(max_retries): ans = None try: @@ -176,6 +176,7 @@ def create(self, params: Dict) -> ChatCompletion: response = client.chat_stream(**cohere_params) else: response = client.chat(**cohere_params) + except CohereRateLimitError as e: raise RuntimeError(f"Cohere exception occurred: {e}") else: @@ -303,6 +304,15 @@ def extract_to_cohere_tool_results(tool_call_id: str, content_output: str, all_t return temp_tool_results +def is_recent_tool_call(messages: list[Dict[str, Any]], tool_call_index: int): + messages_length = len(messages) + if tool_call_index == messages_length - 1: + return True + elif messages[tool_call_index + 1].get("role", "").lower() not in ("chatbot"): + return True + return False + + def oai_messages_to_cohere_messages( messages: list[Dict[str, Any]], params: Dict[str, Any], cohere_params: Dict[str, Any] ) -> tuple[list[dict[str, Any]], str, str]: @@ -322,7 +332,7 @@ def oai_messages_to_cohere_messages( cohere_messages = [] preamble = "" - + cohere_tool_names = set() # Tools if "tools" in params: cohere_tools = [] @@ -353,6 +363,7 @@ def oai_messages_to_cohere_messages( "description": tool["function"]["description"], "parameter_definitions": parameters, } + cohere_tool_names.add(tool["function"]["name"] or "") cohere_tools.append(cohere_tool) @@ -370,31 +381,48 @@ def oai_messages_to_cohere_messages( # 'content' field renamed to 'message' # tools go into tools parameter # tool_results go into tool_results parameter - messages_length = len(messages) for index, message in enumerate(messages): + if not message["content"]: + continue + if "role" in message and message["role"] == "system": # System message if preamble == "": preamble = message["content"] else: preamble = preamble + "\n" + message["content"] - elif "tool_calls" in message: + + elif message.get("tool_calls"): # Suggested tool calls, build up the list before we put it into the tool_results - for tool_call in message["tool_calls"]: + message_tool_calls = [] + for tool_call in message["tool_calls"] or []: + if (not tool_call.get("function", {}).get("name")) or tool_call.get("function", {}).get( + "name" + ) not in cohere_tool_names: + new_message = { + "role": "CHATBOT", + "message": message.get("name") + ":" + message["content"] + str(message["tool_calls"]), + } + cohere_messages.append(new_message) + continue + tool_calls.append(tool_call) + message_tool_calls.append( + { + "name": tool_call.get("function", {}).get("name"), + "parameters": json.loads(tool_call.get("function", {}).get("arguments") or "null"), + } + ) + + if not message_tool_calls: + continue # We also add the suggested tool call as a message new_message = { "role": "CHATBOT", - "message": message["content"], - "tool_calls": [ - { - "name": tool_call_.get("function", {}).get("name"), - "parameters": json.loads(tool_call_.get("function", {}).get("arguments") or "null"), - } - for tool_call_ in message["tool_calls"] - ], + "message": message.get("name") + ":" + message["content"], + "tool_calls": message_tool_calls, } cohere_messages.append(new_message) @@ -402,10 +430,19 @@ def oai_messages_to_cohere_messages( if not (tool_call_id := message.get("tool_call_id")): continue - # Convert the tool call to a result content_output = message["content"] + if tool_call_id not in [tool_call["id"] for tool_call in tool_calls]: + + new_message = { + "role": "CHATBOT", + "message": content_output, + } + cohere_messages.append(new_message) + continue + + # Convert the tool call to a result tool_results_chat_turn = extract_to_cohere_tool_results(tool_call_id, content_output, tool_calls) - if (index == messages_length - 1) or (messages[index + 1].get("role", "").lower() in ("user", "tool")): + if is_recent_tool_call(messages, index): # If the tool call is the last message or the next message is a user/tool message, this is a recent tool call. # So, we pass it into tool_results. tool_results.extend(tool_results_chat_turn) @@ -420,7 +457,7 @@ def oai_messages_to_cohere_messages( # Standard text message new_message = { "role": "USER" if message["role"] == "user" else "CHATBOT", - "message": message["content"], + "message": message.get("name") + ":" + message.get("content"), } cohere_messages.append(new_message) @@ -436,7 +473,7 @@ def oai_messages_to_cohere_messages( # So, we add a CHATBOT 'continue' message, if so. # Changed key from "content" to "message" (jaygdesai/autogen_Jay) if cohere_messages[-1]["role"].lower() == "user": - cohere_messages.append({"role": "CHATBOT", "message": "Please continue."}) + cohere_messages.append({"role": "CHATBOT", "message": "Please go ahead and follow the instructions!"}) # We return a blank message when we have tool results # TODO: Check what happens if tool_results aren't the latest message @@ -449,7 +486,7 @@ def oai_messages_to_cohere_messages( if cohere_messages[-1]["role"] == "USER": return cohere_messages[0:-1], preamble, cohere_messages[-1]["message"] else: - return cohere_messages, preamble, "Please continue." + return cohere_messages, preamble, "Please go ahead and follow the instructions!" def calculate_cohere_cost(input_tokens: int, output_tokens: int, model: str) -> float: diff --git a/autogen/oai/openai_utils.py b/autogen/oai/openai_utils.py index c5fa356d6487..ceb7ef90c933 100644 --- a/autogen/oai/openai_utils.py +++ b/autogen/oai/openai_utils.py @@ -100,19 +100,6 @@ def get_key(config: Dict[str, Any]) -> str: return json.dumps(config, sort_keys=True) -def is_valid_api_key(api_key: str) -> bool: - """Determine if input is valid OpenAI API key. - - Args: - api_key (str): An input string to be validated. - - Returns: - bool: A boolean that indicates if input is valid OpenAI API key. - """ - api_key_re = re.compile(r"^sk-([A-Za-z0-9]+(-+[A-Za-z0-9]+)*-)?[A-Za-z0-9]{32,}$") - return bool(re.fullmatch(api_key_re, api_key)) - - def get_config_list( api_keys: List[str], base_urls: Optional[List[str]] = None, diff --git a/autogen/oai/rate_limiters.py b/autogen/oai/rate_limiters.py new file mode 100644 index 000000000000..4b84a7f99400 --- /dev/null +++ b/autogen/oai/rate_limiters.py @@ -0,0 +1,36 @@ +import time +from typing import Protocol + + +class RateLimiter(Protocol): + def sleep(self, *args, **kwargs): ... + + +class TimeRateLimiter: + """A class to implement a time-based rate limiter. + + This rate limiter ensures that a certain operation does not exceed a specified frequency. + It can be used to limit the rate of requests sent to a server or the rate of any repeated action. + """ + + def __init__(self, rate: float): + """ + Args: + rate (int): The frequency of the time-based rate limiter (NOT time interval). + """ + self._time_interval_seconds = 1.0 / rate + self._last_time_called = 0.0 + + def sleep(self, *args, **kwargs): + """Synchronously waits until enough time has passed to allow the next operation. + + If the elapsed time since the last operation is less than the required time interval, + this method will block the execution by sleeping for the remaining time. + """ + if self._elapsed_time() < self._time_interval_seconds: + time.sleep(self._time_interval_seconds - self._elapsed_time()) + + self._last_time_called = time.perf_counter() + + def _elapsed_time(self): + return time.perf_counter() - self._last_time_called diff --git a/autogen/retrieve_utils.py b/autogen/retrieve_utils.py index 9393903ec86c..4fb53c7c9600 100644 --- a/autogen/retrieve_utils.py +++ b/autogen/retrieve_utils.py @@ -365,7 +365,7 @@ def create_vector_db_from_dir( embedding_function is not None. embedding_function (Optional, Callable): the embedding function to use. Default is None, SentenceTransformer with the given `embedding_model` will be used. If you want to use OpenAI, Cohere, HuggingFace or other embedding - functions, you can pass it here, follow the examples in `https://docs.trychroma.com/embeddings`. + functions, you can pass it here, follow the examples in `https://docs.trychroma.com/guides/embeddings`. custom_text_split_function (Optional, Callable): a custom function to split a string into a list of strings. Default is None, will use the default function in `autogen.retrieve_utils.split_text_to_chunks`. custom_text_types (Optional, List[str]): a list of file types to be processed. Default is TEXT_FORMATS. @@ -448,7 +448,7 @@ def query_vector_db( embedding_function is not None. embedding_function (Optional, Callable): the embedding function to use. Default is None, SentenceTransformer with the given `embedding_model` will be used. If you want to use OpenAI, Cohere, HuggingFace or other embedding - functions, you can pass it here, follow the examples in `https://docs.trychroma.com/embeddings`. + functions, you can pass it here, follow the examples in `https://docs.trychroma.com/guides/embeddings`. Returns: diff --git a/dotnet/eng/MetaInfo.props b/dotnet/eng/MetaInfo.props index 61aa63b5ec7a..c6eeaf843435 100644 --- a/dotnet/eng/MetaInfo.props +++ b/dotnet/eng/MetaInfo.props @@ -1,7 +1,7 @@ - 0.2.0 + 0.2.1 AutoGen https://microsoft.github.io/autogen-for-net/ https://github.com/microsoft/autogen diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_OpenAI_o1_preview.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_OpenAI_o1_preview.cs new file mode 100644 index 000000000000..52bc6381b9d5 --- /dev/null +++ b/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_OpenAI_o1_preview.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Connect_To_OpenAI_o1_preview.cs + +using AutoGen.Core; +using OpenAI; + +namespace AutoGen.OpenAI.Sample; + +public class Connect_To_OpenAI_o1_preview +{ + public static async Task RunAsync() + { + #region create_agent + var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("Please set environment variable OPENAI_API_KEY"); + var openAIClient = new OpenAIClient(apiKey); + + // until 2024/09/12 + // openai o1-preview doesn't support systemMessage, temperature, maxTokens, streaming output + // so in order to use OpenAIChatAgent with o1-preview, you need to set those parameters to null + var agent = new OpenAIChatAgent( + chatClient: openAIClient.GetChatClient("o1-preview"), + name: "assistant", + systemMessage: null, + temperature: null, + maxTokens: null, + seed: 0) + // by using RegisterMiddleware instead of RegisterStreamingMiddleware + // it turns an IStreamingAgent into an IAgent and disables streaming + .RegisterMiddleware(new OpenAIChatRequestMessageConnector()) + .RegisterPrintMessage(); + #endregion create_agent + + #region send_message + await agent.SendAsync("Can you write a piece of C# code to calculate 100th of fibonacci?"); + #endregion send_message + } +} diff --git a/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs b/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs index 44711c5e4289..b0085d0f33c6 100644 --- a/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs +++ b/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs @@ -34,7 +34,7 @@ public class OpenAIChatAgent : IStreamingAgent { private readonly ChatClient chatClient; private readonly ChatCompletionOptions options; - private readonly string systemMessage; + private readonly string? systemMessage; /// /// Create a new instance of . @@ -50,9 +50,9 @@ public class OpenAIChatAgent : IStreamingAgent public OpenAIChatAgent( ChatClient chatClient, string name, - string systemMessage = "You are a helpful AI assistant", - float temperature = 0.7f, - int maxTokens = 1024, + string? systemMessage = "You are a helpful AI assistant", + float? temperature = null, + int? maxTokens = null, int? seed = null, ChatResponseFormat? responseFormat = null, IEnumerable? functions = null) @@ -75,7 +75,7 @@ public OpenAIChatAgent( ChatClient chatClient, string name, ChatCompletionOptions options, - string systemMessage = "You are a helpful AI assistant") + string? systemMessage = "You are a helpful AI assistant") { this.chatClient = chatClient; this.Name = name; @@ -124,7 +124,7 @@ private IEnumerable CreateChatMessages(IEnumerable messag }); // add system message if there's no system message in messages - if (!oaiMessages.Any(m => m is SystemChatMessage)) + if (!oaiMessages.Any(m => m is SystemChatMessage) && systemMessage is not null) { oaiMessages = new[] { new SystemChatMessage(systemMessage) }.Concat(oaiMessages); } @@ -192,8 +192,8 @@ private ChatCompletionOptions CreateChatCompletionsOptions(GenerateReplyOptions? } private static ChatCompletionOptions CreateChatCompletionOptions( - float temperature = 0.7f, - int maxTokens = 1024, + float? temperature = 0.7f, + int? maxTokens = 1024, int? seed = null, ChatResponseFormat? responseFormat = null, IEnumerable? functions = null) diff --git a/dotnet/website/release_note/0.2.1.md b/dotnet/website/release_note/0.2.1.md new file mode 100644 index 000000000000..353cdd080de9 --- /dev/null +++ b/dotnet/website/release_note/0.2.1.md @@ -0,0 +1,7 @@ +# Release Notes for AutoGen.Net v0.2.1 🚀 + +## New Features 🌟 +- **Support for OpenAi o1-preview** : Added support for OpenAI o1-preview model ([#3522](https://github.com/microsoft/autogen/issues/3522)) + +## Example 📚 +- **OpenAI o1-preview**: [Connect_To_OpenAI_o1_preview](https://github.com/microsoft/autogen/blob/main/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_OpenAI_o1_preview.cs) \ No newline at end of file diff --git a/dotnet/website/release_note/toc.yml b/dotnet/website/release_note/toc.yml index 770ecdaadf36..133656687d8e 100644 --- a/dotnet/website/release_note/toc.yml +++ b/dotnet/website/release_note/toc.yml @@ -1,3 +1,6 @@ +- name: 0.2.1 + href: 0.2.1.md + - name: 0.2.0 href: 0.2.0.md diff --git a/notebook/agentchat_RetrieveChat.ipynb b/notebook/agentchat_RetrieveChat.ipynb index eee192c4f826..0b829835a0a3 100644 --- a/notebook/agentchat_RetrieveChat.ipynb +++ b/notebook/agentchat_RetrieveChat.ipynb @@ -31,6 +31,8 @@ "pip install pyautogen[retrievechat] flaml[automl]\n", "```\n", "\n", + "*You'll need to install `chromadb<=0.5.0` if you see issue like [#3551](https://github.com/microsoft/autogen/issues/3551).*\n", + "\n", "For more information, please refer to the [installation guide](/docs/installation/).\n", ":::\n", "````" @@ -2785,7 +2787,7 @@ ] }, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "autogen312", "language": "python", "name": "python3" }, diff --git a/setup.py b/setup.py index bd75dab16b89..904748d189a7 100644 --- a/setup.py +++ b/setup.py @@ -74,6 +74,7 @@ "retrievechat-pgvector": retrieve_chat_pgvector, "retrievechat-mongodb": [*retrieve_chat, "pymongo>=4.0.0"], "retrievechat-qdrant": [*retrieve_chat, "qdrant_client", "fastembed>=0.3.1"], + "retrievechat-couchbase": [*retrieve_chat, "couchbase>=4.3.0"], "autobuild": ["chromadb", "sentence-transformers", "huggingface-hub", "pysqlite3"], "teachable": ["chromadb"], "lmm": ["replicate", "pillow"], diff --git a/test/agentchat/contrib/vectordb/test_chromadb.py b/test/agentchat/contrib/vectordb/test_chromadb.py index ee4886f5154d..19b365db10ba 100644 --- a/test/agentchat/contrib/vectordb/test_chromadb.py +++ b/test/agentchat/contrib/vectordb/test_chromadb.py @@ -15,6 +15,11 @@ else: skip = False +try: + from chromadb.errors import ChromaError +except ImportError: + ChromaError = Exception + @pytest.mark.skipif(skip, reason="dependency is not installed") def test_chromadb(): @@ -26,12 +31,14 @@ def test_chromadb(): # test_delete_collection db.delete_collection(collection_name) - pytest.raises(ValueError, db.get_collection, collection_name) + pytest.raises((ValueError, ChromaError), db.get_collection, collection_name) # test more create collection collection = db.create_collection(collection_name, overwrite=False, get_or_create=False) assert collection.name == collection_name - pytest.raises(ValueError, db.create_collection, collection_name, overwrite=False, get_or_create=False) + pytest.raises( + (ValueError, ChromaError), db.create_collection, collection_name, overwrite=False, get_or_create=False + ) collection = db.create_collection(collection_name, overwrite=True, get_or_create=False) assert collection.name == collection_name collection = db.create_collection(collection_name, overwrite=False, get_or_create=True) diff --git a/test/agentchat/contrib/vectordb/test_couchbase.py b/test/agentchat/contrib/vectordb/test_couchbase.py new file mode 100644 index 000000000000..5fed6bcfa37d --- /dev/null +++ b/test/agentchat/contrib/vectordb/test_couchbase.py @@ -0,0 +1,151 @@ +import logging +import os +import random +from time import sleep + +import pytest +from dotenv import load_dotenv + +try: + + import couchbase + import sentence_transformers + + from autogen.agentchat.contrib.vectordb.couchbase import CouchbaseVectorDB +except ImportError: + print("skipping test_couchbase.py. It requires one to pip install couchbase or the extra [retrievechat-couchbase]") + logger = logging.getLogger(__name__) + logger.warning( + f"skipping {__name__}. It requires one to pip install couchbase or the extra [retrievechat-couchbase]" + ) + pytest.skip("Required modules not installed", allow_module_level=True) + +from couchbase.auth import PasswordAuthenticator +from couchbase.cluster import Cluster, ClusterOptions + +logger = logging.getLogger(__name__) + +# Get the directory of the current script +script_dir = os.path.dirname(os.path.abspath(__file__)) + +# Construct the absolute path to the .env file +env_path = os.path.join(script_dir, ".env") + +# Load the .env file +load_dotenv(env_path) +load_dotenv(".env") + +COUCHBASE_HOST = os.environ.get("CB_CONN_STR", "couchbase://localhost") +COUCHBASE_USERNAME = os.environ.get("CB_USERNAME", "Administrator") +COUCHBASE_PASSWORD = os.environ.get("CB_PASSWORD", "password") +COUCHBASE_BUCKET = os.environ.get("CB_BUCKET", "autogen_test_bucket") +COUCHBASE_SCOPE = os.environ.get("CB_SCOPE", "_default") +COUCHBASE_COLLECTION = os.environ.get("CB_COLLECTION", "autogen_test_vectorstore") +COUCHBASE_INDEX = os.environ.get("CB_INDEX_NAME", "vector_index") + +RETRIES = 10 +DELAY = 2 +TIMEOUT = 120.0 + + +def _empty_collections_and_delete_indexes(cluster: Cluster, bucket_name, scope_name, collections=None): + bucket = cluster.bucket(bucket_name) + try: + scope_manager = bucket.collections().get_all_scopes(scope_name=scope_name) + for scope_ in scope_manager: + all_collections = scope_.collections + for curr_collection in all_collections: + bucket.collections().drop_collection(scope_name, curr_collection.name) + except Exception as e: + logger.warning(f"Failed to drop collections: {e}") + + +@pytest.fixture +def db(): + print("Creating couchbase connection", COUCHBASE_HOST, COUCHBASE_USERNAME, COUCHBASE_PASSWORD) + cluster = Cluster(COUCHBASE_HOST, ClusterOptions(PasswordAuthenticator(COUCHBASE_USERNAME, COUCHBASE_PASSWORD))) + _empty_collections_and_delete_indexes(cluster, COUCHBASE_BUCKET, COUCHBASE_SCOPE) + vectorstore = CouchbaseVectorDB( + connection_string=COUCHBASE_HOST, + username=COUCHBASE_USERNAME, + password=COUCHBASE_PASSWORD, + bucket_name=COUCHBASE_BUCKET, + scope_name=COUCHBASE_SCOPE, + collection_name=COUCHBASE_COLLECTION, + index_name=COUCHBASE_INDEX, + ) + yield vectorstore + _empty_collections_and_delete_indexes(cluster, COUCHBASE_BUCKET, COUCHBASE_SCOPE) + + +_COLLECTION_NAMING_CACHE = [] + + +@pytest.fixture +def collection_name(): + collection_id = random.randint(0, 100) + while collection_id in _COLLECTION_NAMING_CACHE: + collection_id = random.randint(0, 100) + _COLLECTION_NAMING_CACHE.append(collection_id) + return f"{COUCHBASE_COLLECTION}_{collection_id}" + + +def test_couchbase(db, collection_name): + # db = CouchbaseVectorDB(path=".db") + with pytest.raises(Exception): + curr_col = db.get_collection(collection_name) + curr_col.upsert("1", {"content": "Dogs are lovely."}) + + collection = db.create_collection(collection_name, overwrite=True, get_or_create=True) + assert collection.name == collection_name + collection.upsert("1", {"content": "Dogs are lovely."}) + + # test_delete_collection + db.delete_collection(collection_name) + sleep(5) # wait for the collection to be deleted + with pytest.raises(Exception): + curr_col = db.get_collection(collection_name) + curr_col.upsert("1", {"content": "Dogs are lovely."}) + + # test more create collection + collection = db.create_collection(collection_name, overwrite=False, get_or_create=False) + assert collection.name == collection_name + pytest.raises(ValueError, db.create_collection, collection_name, overwrite=False, get_or_create=False) + collection = db.create_collection(collection_name, overwrite=True, get_or_create=False) + assert collection.name == collection_name + collection = db.create_collection(collection_name, overwrite=False, get_or_create=True) + assert collection.name == collection_name + + # test_get_collection + collection = db.get_collection(collection_name) + assert collection.name == collection_name + + # test_insert_docs + docs = [{"content": "doc1", "id": "1"}, {"content": "doc2", "id": "2"}, {"content": "doc3", "id": "3"}] + db.insert_docs(docs, collection_name, upsert=False) + res = db.get_collection(collection_name).get_multi(["1", "2"]).results + + assert res["1"].value["content"] == "doc1" + assert res["2"].value["content"] == "doc2" + + # test_update_docs + docs = [{"content": "doc11", "id": "1"}, {"content": "doc2", "id": "2"}, {"content": "doc3", "id": "3"}] + db.update_docs(docs, collection_name) + res = db.get_collection(collection_name).get_multi(["1", "2"]).results + assert res["1"].value["content"] == "doc11" + assert res["2"].value["content"] == "doc2" + + # test_delete_docs + ids = ["1"] + db.delete_docs(ids, collection_name) + with pytest.raises(Exception): + res = db.get_collection(collection_name).get(ids[0]) + + # test_retrieve_docs + queries = ["doc2", "doc3"] + res = db.retrieve_docs(queries, collection_name) + texts = [[item[0]["content"] for item in sublist] for sublist in res] + received_ids = [[item[0]["id"] for item in sublist] for sublist in res] + + assert texts[0] == ["doc2", "doc3"] + assert received_ids[0] == ["2", "3"] diff --git a/test/agentchat/test_function_and_tool_calling.py b/test/agentchat/test_function_and_tool_calling.py index 4e0775d014c0..97727fe5c321 100644 --- a/test/agentchat/test_function_and_tool_calling.py +++ b/test/agentchat/test_function_and_tool_calling.py @@ -33,6 +33,7 @@ async def _a_tool_func_error(arg1: str, arg2: str) -> str: _tool_use_message_1 = { "role": "assistant", "content": None, + "function_call": None, "tool_calls": [ { "id": "1", @@ -56,6 +57,7 @@ async def _a_tool_func_error(arg1: str, arg2: str) -> str: _tool_use_message_1_bad_json = { "role": "assistant", "content": None, + "function_call": None, "tool_calls": [ { "id": "1", diff --git a/test/oai/test_client.py b/test/oai/test_client.py index 443ec995de48..bd8b072e6127 100755 --- a/test/oai/test_client.py +++ b/test/oai/test_client.py @@ -4,6 +4,7 @@ import shutil import sys import time +from types import SimpleNamespace import pytest @@ -31,6 +32,40 @@ OAI_CONFIG_LIST = "OAI_CONFIG_LIST" +class _MockClient: + def __init__(self, config, **kwargs): + pass + + def create(self, params): + # can create my own data response class + # here using SimpleNamespace for simplicity + # as long as it adheres to the ModelClientResponseProtocol + + response = SimpleNamespace() + response.choices = [] + response.model = "mock_model" + + text = "this is a dummy text response" + choice = SimpleNamespace() + choice.message = SimpleNamespace() + choice.message.content = text + choice.message.function_call = None + response.choices.append(choice) + return response + + def message_retrieval(self, response): + choices = response.choices + return [choice.message.content for choice in choices] + + def cost(self, response) -> float: + response.cost = 0 + return 0 + + @staticmethod + def get_usage(response): + return {} + + @pytest.mark.skipif(skip, reason="openai>=1 not installed") def test_aoai_chat_completion(): config_list = config_list_from_json( @@ -322,6 +357,32 @@ def test_cache(): assert not os.path.exists(os.path.join(cache_dir, str(LEGACY_DEFAULT_CACHE_SEED))) +def test_throttled_api_calls(): + # Api calling limited at 0.2 request per second, or 1 request per 5 seconds + rate = 1 / 5.0 + + config_list = [ + { + "model": "mock_model", + "model_client_cls": "_MockClient", + # Adding a timeout to catch false positives + "timeout": 1 / rate, + "api_rate_limit": rate, + } + ] + + client = OpenAIWrapper(config_list=config_list, cache_seed=None) + client.register_model_client(_MockClient) + + n_loops = 2 + current_time = time.time() + for _ in range(n_loops): + client.create(messages=[{"role": "user", "content": "hello"}]) + + min_expected_time = (n_loops - 1) / rate + assert time.time() - current_time > min_expected_time + + if __name__ == "__main__": # test_aoai_chat_completion() # test_oai_tool_calling_extraction() @@ -329,5 +390,6 @@ def test_cache(): test_completion() # # test_cost() # test_usage_summary() - # test_legacy_cache() - # test_cache() + test_legacy_cache() + test_cache() + test_throttled_api_calls() diff --git a/test/oai/test_rate_limiters.py b/test/oai/test_rate_limiters.py new file mode 100644 index 000000000000..a04429c0dea2 --- /dev/null +++ b/test/oai/test_rate_limiters.py @@ -0,0 +1,21 @@ +import time + +import pytest + +from autogen.oai.rate_limiters import TimeRateLimiter + + +@pytest.mark.parametrize("execute_n_times", range(5)) +def test_time_rate_limiter(execute_n_times): + current_time_seconds = time.time() + + rate = 1 + rate_limiter = TimeRateLimiter(rate) + + n_loops = 2 + for _ in range(n_loops): + rate_limiter.sleep() + + total_time = time.time() - current_time_seconds + min_expected_time = (n_loops - 1) / rate + assert total_time >= min_expected_time diff --git a/test/oai/test_utils.py b/test/oai/test_utils.py index fd81d3f9f548..b7aeaa5c2549 100755 --- a/test/oai/test_utils.py +++ b/test/oai/test_utils.py @@ -12,7 +12,7 @@ from conftest import MOCK_OPEN_AI_API_KEY import autogen # noqa: E402 -from autogen.oai.openai_utils import DEFAULT_AZURE_API_VERSION, filter_config, is_valid_api_key +from autogen.oai.openai_utils import DEFAULT_AZURE_API_VERSION, filter_config # Example environment variables ENV_VARS = { @@ -414,24 +414,5 @@ def test_tags(): assert len(list_5) == 0 -def test_is_valid_api_key(): - assert not is_valid_api_key("") - assert not is_valid_api_key("sk-") - assert not is_valid_api_key("SK-") - assert not is_valid_api_key("sk-asajsdjsd2") - assert not is_valid_api_key("FooBar") - assert not is_valid_api_key("sk-asajsdjsd22372%23kjdfdfdf2329ffUUDSDS") - assert is_valid_api_key("sk-asajsdjsd22372X23kjdfdfdf2329ffUUDSDS") - assert is_valid_api_key("sk-asajsdjsd22372X23kjdfdfdf2329ffUUDSDS1212121221212sssXX") - assert is_valid_api_key("sk-proj-asajsdjsd22372X23kjdfdfdf2329ffUUDSDS12121212212") - assert is_valid_api_key("sk-0-asajsdjsd22372X23kjdfdfdf2329ffUUDSDS12121212212") - assert is_valid_api_key("sk-aut0gen-asajsdjsd22372X23kjdfdfdf2329ffUUDSDS12121212212") - assert is_valid_api_key("sk-aut0-gen-asajsdjsd22372X23kjdfdfdf2329ffUUDSDS12121212212") - assert is_valid_api_key("sk-aut0--gen-asajsdjsd22372X23kjdfdfdf2329ffUUDSDS12121212212") - assert not is_valid_api_key("sk-aut0-gen--asajsdjsd22372X23kjdfdfdf2329ffUUDSDS12121212212") - assert not is_valid_api_key("sk--aut0-gen-asajsdjsd22372X23kjdfdfdf2329ffUUDSDS12121212212") - assert is_valid_api_key(MOCK_OPEN_AI_API_KEY) - - if __name__ == "__main__": pytest.main() diff --git a/website/blog/2023-10-18-RetrieveChat/index.mdx b/website/blog/2023-10-18-RetrieveChat/index.mdx index 91b8b5012a3b..d5c78148e446 100644 --- a/website/blog/2023-10-18-RetrieveChat/index.mdx +++ b/website/blog/2023-10-18-RetrieveChat/index.mdx @@ -4,7 +4,7 @@ authors: thinkall tags: [LLM, RAG] --- -*Last update: August 14, 2024; AutoGen version: v0.2.35* +*Last update: September 23, 2024; AutoGen version: v0.2.35* ![RAG Architecture](img/retrievechat-arch.png) @@ -57,6 +57,8 @@ Please install pyautogen with the [retrievechat] option before using RAG agents. pip install "pyautogen[retrievechat]" ``` +*You'll need to install `chromadb<=0.5.0` if you see issue like [#3551](https://github.com/microsoft/autogen/issues/3551).* + RetrieveChat can handle various types of documents. By default, it can process plain text and PDF files, including formats such as 'txt', 'json', 'csv', 'tsv', 'md', 'html', 'htm', 'rtf', 'rst', 'jsonl', 'log', 'xml', 'yaml', 'yml' and 'pdf'. @@ -174,7 +176,7 @@ huggingface_ef = embedding_functions.HuggingFaceEmbeddingFunction( ) ``` -More examples can be found [here](https://docs.trychroma.com/embeddings). +More examples can be found [here](https://docs.trychroma.com/guides/embeddings). ### Customizing Text Split Function Before we can store the documents into a vector database, we need to split the texts into chunks. Although @@ -200,8 +202,8 @@ ragproxyagent = RetrieveUserProxyAgent( ### Customizing Vector Database -We are using chromadb as the default vector database, you can also use mongodb, pgvectordb and qdrantdb -by simply set `vector_db` to `mongodb`, `pgvector` and `qdrant` in `retrieve_config`, respectively. +We are using chromadb as the default vector database, you can also use mongodb, pgvectordb, qdrantdb and couchbase +by simply set `vector_db` to `mongodb`, `pgvector`, `qdrant` and `couchbase` in `retrieve_config`, respectively. To plugin any other dbs, you can also extend class `agentchat.contrib.vectordb.base`, check out the code [here](https://github.com/microsoft/autogen/blob/main/autogen/agentchat/contrib/vectordb/base.py). diff --git a/website/blog/2024-06-24-AltModels-Classes/index.mdx b/website/blog/2024-06-24-AltModels-Classes/index.mdx index 9c94094e7e4c..1f01fb9402a4 100644 --- a/website/blog/2024-06-24-AltModels-Classes/index.mdx +++ b/website/blog/2024-06-24-AltModels-Classes/index.mdx @@ -146,7 +146,7 @@ assistant = AssistantAgent( ```py -user_proxy.intiate_chat(assistant, message="Write python code to print Hello World!") +user_proxy.initiate_chat(assistant, message="Write python code to print Hello World!") ``` diff --git a/website/docs/FAQ.mdx b/website/docs/FAQ.mdx index 2798ae9375b2..a367a9b20635 100644 --- a/website/docs/FAQ.mdx +++ b/website/docs/FAQ.mdx @@ -37,7 +37,15 @@ Yes. You currently have two options: - Autogen can work with any API endpoint which complies with OpenAI-compatible RESTful APIs - e.g. serving local LLM via FastChat or LM Studio. Please check https://microsoft.github.io/autogen/blog/2023/07/14/Local-LLMs for an example. - You can supply your own custom model implementation and use it with Autogen. Please check https://microsoft.github.io/autogen/blog/2024/01/26/Custom-Models for more information. -## Handle Rate Limit Error and Timeout Error +## Handling API Rate Limits + +### Setting the API Rate Limit + +You can set the `api_rate_limit` in a `config_list` for an agent, which will be used to control the rate at which API requests are sent. + +- `api_rate_limit` (float): the maximum number of API requests allowed per second. + +### Handle Rate Limit Error and Timeout Error You can set `max_retries` to handle rate limit error. And you can set `timeout` to handle timeout error. They can all be specified in `llm_config` for an agent, which will be used in the OpenAI client for LLM inference. They can be set differently for different clients if they are set in the `config_list`. diff --git a/website/docs/installation/Optional-Dependencies.md b/website/docs/installation/Optional-Dependencies.md index 2d0067c9950e..7d17ce50e37d 100644 --- a/website/docs/installation/Optional-Dependencies.md +++ b/website/docs/installation/Optional-Dependencies.md @@ -9,7 +9,7 @@ the option `redis`: pip install "pyautogen[redis]" ``` -See [LLM Caching](Use-Cases/agent_chat.md#llm-caching) for details. +See [LLM Caching](/docs/topics/llm-caching) for details. ## IPython Code Executor @@ -49,6 +49,7 @@ Example notebooks: ```bash pip install "pyautogen[retrievechat]" ``` +*You'll need to install `chromadb<=0.5.0` if you see issue like [#3551](https://github.com/microsoft/autogen/issues/3551).* Alternatively `pyautogen` also supports PGVector and Qdrant which can be installed in place of ChromaDB, or alongside it. diff --git a/website/docs/topics/code-execution/user-defined-functions.ipynb b/website/docs/topics/code-execution/user-defined-functions.ipynb index ade02dfc0837..7697f7393c68 100644 --- a/website/docs/topics/code-execution/user-defined-functions.ipynb +++ b/website/docs/topics/code-execution/user-defined-functions.ipynb @@ -166,7 +166,7 @@ ], "source": [ "code = f\"\"\"\n", - "from {LocalCommandLineCodeExecutor.FUNCTIONS_MODULE} import add_two_numbers\n", + "from {executor.functions_module} import add_two_numbers\n", "\n", "print(add_two_numbers(1, 2))\n", "\"\"\"\n", @@ -207,7 +207,7 @@ ], "source": [ "code = f\"\"\"\n", - "from {LocalCommandLineCodeExecutor.FUNCTIONS_MODULE} import load_data\n", + "from {executor.functions_module} import load_data\n", "\n", "print(load_data())\n", "\"\"\"\n", @@ -359,12 +359,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mcode_executor_agent\u001b[0m (to code_writer):\n", + "\u001B[33mcode_executor_agent\u001B[0m (to code_writer):\n", "\n", "Please use the load_data function to load the data and please calculate the average age of all people.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mcode_writer\u001b[0m (to code_executor_agent):\n", + "\u001B[33mcode_writer\u001B[0m (to code_executor_agent):\n", "\n", "Below is the python code to load the data using the `load_data()` function and calculate the average age of all people. \n", "\n", @@ -385,21 +385,21 @@ "This code starts by importing the `load_data()` function. It then uses this function to load the data into a variable `df`. Afterwards, it calculates the average (mean) of the 'age' column in the DataFrame, before printing the result.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK (inferred language is python)...\u001b[0m\n", - "\u001b[33mcode_executor_agent\u001b[0m (to code_writer):\n", + "\u001B[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK (inferred language is python)...\u001B[0m\n", + "\u001B[33mcode_executor_agent\u001B[0m (to code_writer):\n", "\n", "exitcode: 0 (execution succeeded)\n", "Code output: The average age is 30.75\n", "\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mcode_writer\u001b[0m (to code_executor_agent):\n", + "\u001B[33mcode_writer\u001B[0m (to code_executor_agent):\n", "\n", "Great! The code worked fine. So, the average age of all people in the dataset is 30.75 years.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mcode_executor_agent\u001b[0m (to code_writer):\n", + "\u001B[33mcode_executor_agent\u001B[0m (to code_writer):\n", "\n", "\n", "\n", diff --git a/website/docs/topics/llm_configuration.ipynb b/website/docs/topics/llm_configuration.ipynb index 0c094f6531ed..a9c42592a866 100644 --- a/website/docs/topics/llm_configuration.ipynb +++ b/website/docs/topics/llm_configuration.ipynb @@ -63,6 +63,7 @@ " \n", " - `model` (str, required): The identifier of the model to be used, such as 'gpt-4', 'gpt-3.5-turbo'.\n", " - `api_key` (str, optional): The API key required for authenticating requests to the model's API endpoint.\n", + " - `api_rate_limit` (float, optional): Specifies the maximum number of API requests permitted per second.\n", " - `base_url` (str, optional): The base URL of the API endpoint. This is the root address where API calls are directed.\n", " - `tags` (List[str], optional): Tags which can be used for filtering.\n", "\n", @@ -72,6 +73,7 @@ " {\n", " \"model\": \"gpt-4\",\n", " \"api_key\": os.environ['OPENAI_API_KEY']\n", + " \"api_rate_limit\": 60.0, // Set to allow up to 60 API requests per second.\n", " }\n", " ]\n", " ```\n", @@ -80,6 +82,7 @@ " - `model` (str, required): The deployment to be used. The model corresponds to the deployment name on Azure OpenAI.\n", " - `api_key` (str, optional): The API key required for authenticating requests to the model's API endpoint.\n", " - `api_type`: `azure`\n", + " - `api_rate_limit` (float, optional): Specifies the maximum number of API requests permitted per second.\n", " - `base_url` (str, optional): The base URL of the API endpoint. This is the root address where API calls are directed.\n", " - `api_version` (str, optional): The version of the Azure API you wish to use.\n", " - `tags` (List[str], optional): Tags which can be used for filtering.\n", @@ -100,6 +103,7 @@ " \n", " - `model` (str, required): The identifier of the model to be used, such as 'llama-7B'.\n", " - `api_key` (str, optional): The API key required for authenticating requests to the model's API endpoint.\n", + " - `api_rate_limit` (float, optional): Specifies the maximum number of API requests permitted per second.\n", " - `base_url` (str, optional): The base URL of the API endpoint. This is the root address where API calls are directed.\n", " - `tags` (List[str], optional): Tags which can be used for filtering.\n", "\n", diff --git a/website/docs/topics/retrieval_augmentation.md b/website/docs/topics/retrieval_augmentation.md index 3c428f164868..7fdd39f31f92 100644 --- a/website/docs/topics/retrieval_augmentation.md +++ b/website/docs/topics/retrieval_augmentation.md @@ -56,6 +56,7 @@ ragproxyagent.initiate_chat( assistant, message=ragproxyagent.message_generator, problem=code_problem, search_string="spark" ) # search_string is used as an extra filter for the embeddings search, in this case, we only want to search documents that contain "spark". ``` +*You'll need to install `chromadb<=0.5.0` if you see issue like [#3551](https://github.com/microsoft/autogen/issues/3551).* ## Example Setup: RAG with Retrieval Augmented Agents with PGVector The following is an example setup demonstrating how to create retrieval augmented agents in AutoGen: diff --git a/website/yarn.lock b/website/yarn.lock index d6946e4ca57c..0aeb603db8d1 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -2582,22 +2582,6 @@ dependencies: "@types/ms" "*" -"@types/eslint-scope@^3.7.3": - version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" - integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.56.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.2.tgz#1c72a9b794aa26a8b94ad26d5b9aa51c8a6384bb" - integrity sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - "@types/estree-jsx@^1.0.0": version "1.0.3" resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.3.tgz#f8aa833ec986d82b8271a294a92ed1565bf2c66a" @@ -2605,7 +2589,7 @@ dependencies: "@types/estree" "*" -"@types/estree@*", "@types/estree@^1.0.0": +"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== @@ -2695,7 +2679,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -2917,10 +2901,10 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" - integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== dependencies: "@webassemblyjs/helper-numbers" "1.11.6" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" @@ -2935,10 +2919,10 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== -"@webassemblyjs/helper-buffer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" - integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== "@webassemblyjs/helper-numbers@1.11.6": version "1.11.6" @@ -2954,15 +2938,15 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== -"@webassemblyjs/helper-wasm-section@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" - integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" "@webassemblyjs/ieee754@1.11.6": version "1.11.6" @@ -2983,59 +2967,59 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" - integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-opt" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - "@webassemblyjs/wast-printer" "1.11.6" - -"@webassemblyjs/wasm-gen@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" - integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== - dependencies: - "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" "@webassemblyjs/ieee754" "1.11.6" "@webassemblyjs/leb128" "1.11.6" "@webassemblyjs/utf8" "1.11.6" -"@webassemblyjs/wasm-opt@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" - integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" -"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" - integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== dependencies: - "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/ast" "1.12.1" "@webassemblyjs/helper-api-error" "1.11.6" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" "@webassemblyjs/ieee754" "1.11.6" "@webassemblyjs/leb128" "1.11.6" "@webassemblyjs/utf8" "1.11.6" -"@webassemblyjs/wast-printer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" - integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== dependencies: - "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" "@xtuc/ieee754@^1.2.0": @@ -3056,10 +3040,10 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn-jsx@^5.0.0: version "5.3.2" @@ -3434,10 +3418,10 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -body-parser@1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== dependencies: bytes "3.1.2" content-type "~1.0.5" @@ -3447,7 +3431,7 @@ body-parser@1.20.2: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.11.0" + qs "6.13.0" raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -3501,14 +3485,14 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: fill-range "^7.1.1" -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.22.1, browserslist@^4.22.2: +browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.22.1, browserslist@^4.22.2: version "4.22.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== @@ -3560,6 +3544,17 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: get-intrinsic "^1.2.1" set-function-length "^1.1.1" +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -4307,6 +4302,15 @@ define-data-property@^1.0.1, define-data-property@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -4550,6 +4554,11 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -4557,10 +4566,10 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.15.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== +enhanced-resolve@^5.17.1: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -4632,6 +4641,18 @@ es-array-method-boxes-properly@^1.0.0: resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + es-module-lexer@^1.2.1: version "1.4.1" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5" @@ -4818,36 +4839,36 @@ execa@^5.0.0: strip-final-newline "^2.0.0" express@^4.17.3: - version "4.19.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" - integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + version "4.21.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" + integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.2" + body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.2.0" + finalhandler "1.3.1" fresh "0.5.2" http-errors "2.0.0" - merge-descriptors "1.0.1" + merge-descriptors "1.0.3" methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.7" + path-to-regexp "0.1.10" proxy-addr "~2.0.7" - qs "6.11.0" + qs "6.13.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" + send "0.19.0" + serve-static "1.16.2" setprototypeof "1.2.0" statuses "2.0.1" type-is "~1.6.18" @@ -4942,13 +4963,13 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== dependencies: debug "2.6.9" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" on-finished "2.4.1" parseurl "~1.3.3" @@ -5120,6 +5141,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -5268,7 +5300,7 @@ graceful-fs@4.2.10: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -5317,6 +5349,13 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.2.2" +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + has-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" @@ -6661,10 +6700,10 @@ memfs@^3.1.2, memfs@^3.4.3: dependencies: fs-monkey "^1.0.4" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== merge-stream@^2.0.0: version "2.0.0" @@ -7098,11 +7137,11 @@ micromark@^4.0.0: micromark-util-types "^2.0.0" micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": @@ -7564,10 +7603,10 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== path-to-regexp@2.2.1: version "2.2.1" @@ -8265,12 +8304,12 @@ qrcode.react@^3.1.0: resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" integrity sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q== -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== dependencies: - side-channel "^1.0.4" + side-channel "^1.0.6" queue-microtask@^1.2.2: version "1.2.3" @@ -9281,10 +9320,10 @@ semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: dependencies: lru-cache "^6.0.0" -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== dependencies: debug "2.6.9" depd "2.0.0" @@ -9334,15 +9373,15 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== dependencies: - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.18.0" + send "0.19.0" set-function-length@^1.1.1: version "1.1.1" @@ -9354,6 +9393,18 @@ set-function-length@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + set-function-name@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" @@ -9420,6 +9471,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -9781,7 +9842,7 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.3.7, terser-webpack-plugin@^5.3.9: +terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.9: version "5.3.10" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== @@ -10259,10 +10320,10 @@ vfile@^6.0.0, vfile@^6.0.1: unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -10365,33 +10426,32 @@ webpack-sources@^3.2.2, webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.73.0, webpack@^5.88.1: - version "5.89.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc" - integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== + dependencies: + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" - browserslist "^4.14.5" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" + enhanced-resolve "^5.17.1" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" + graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" schema-utils "^3.2.0" tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" - watchpack "^2.4.0" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" webpack-sources "^3.2.3" webpackbar@^5.0.2: